Skip to main content
Every agent and subagent in a run is a first-class, persisted node. The session tree is what makes a run reconstructable without reading the chat transcript — and what makes budget a hard boundary instead of telemetry. Inspect it:
voss session tree <root_id>
voss session tree <root_id> --json   # machine-readable export

The node

Each SessionTreeNode carries:
FieldMeaning
idNode id
root_idRoot of this run
parent_run_idParent node (null for the root)
envelope{ "limit": <tokens>, "spent": <tokens> }
terminal_stateExit reason + outcome once finalized
scopeGlob scope assigned to this node
roleRole that owns the node
rejected_raisesRecorded budget-raise attempts that were denied
transitionsBoard transition deltas
created_at / ended_atLifecycle timestamps

Budget fan-out

Children are allocated from the parent’s envelope, and the manager enforces the invariant on every allocation:
child.limit <= parent.limit - reserve - sum(existing child limits)
Over-allocation raises a budget allocation error. A node’s cap is non-extendable: an attempt to raise it is rejected and appended to rejected_raises with reason: "cap_raise_rejected" — the attempt itself becomes part of the record.
Budget is treated as a security boundary, not a counter. A child can never overspend its parent, and a rejected raise is auditable.

Terminal states

Every spawned node reaches a terminal state — including failures. The exit reason is one of:
done · max-iter · budget · interrupt · batch-invariant · timeout · killed · error
Failed, killed, and timed-out children are still finalized, so there are no orphan nodes.

Persistence and export

Nodes persist as individual files (mode 0o600):
.voss/sessions/<root_id>/<node_id>.json
The export reads every node under a root and returns { "root_id": ..., "nodes": [...] }, which is what the voss board renderer and voss session tree --json consume — and what an ADE audit view renders as a navigable tree.