Skip to main content

Module session_tree

Module session_tree 

Source
Expand description

P5-5 (design §2 module 21 session.tree: “D5 in-place tree, rewind-anywhere, branch summaries, entry labels”; §2.1 D-6 session.tree → core.session(tree-addressable transcript); §2.2 C7; §5.2 P5 row 5: “loaders are already tree-aware (C7 resolution); adds in-place rewind/branch/label on the native store”).

§What this is

Session and the composition layer’s session store already carry a linear transcript — messages: Vec<ChatMessage> — and the loaders already reconstruct MULTI-FILE tree structure on import (Session::reconstruct_tree, the C7 resolution’s “import-side preserved only”). What was missing is the native, IN-PLACE tree: the ability to address any turn by id, rewind the active pointer to an earlier one without deleting anything, explicitly fork a new branch, attach a short summary to an off-path branch, and label any node — all on supercode’s OWN session store (CC/PI’s defining in-place-tree feature, catalog D5).

§C7 — tree-with-linear-projection

Conflict 7 (design §2.2): session.tree’s in-place DAG is structurally incompatible with a strictly-linear export target (the CX rollout shape). The resolution the design commits to is core stays tree-with-linear-projection: SessionTree::linear_projection always derives the active branch’s message sequence deterministically by walking parent pointers from the root to the active leaf — this is what crate::session::Session::messages / the agent loop / exporters keep consuming unchanged. A tree session with zero branches (the common case, and the ONLY case before this module’s operations are ever invoked) is the degenerate single-path tree: its projection is byte-for-byte the same sequence crate::session::Session::messages already held — see the linear-projection regression test.

Exporting a branched tree to a linear-only format is SessionTree::splice_for_linear_export: it returns the active path (spliced, exactly like today’s linear export) plus a BranchSummary for every OFF-path branch. Nothing is deleted by this — the full tree (every node of every branch) stays intact in SessionTree / its <name>.tree.json sidecar (through the session store’s explicit tree writer); the summary is an added, human-readable POINTER (BranchSummary::branch names which branch the full data still lives under), never a replacement for it (§1.13 lossless).

§Lossless rewind (§1.13)

SessionTree::rewind never deletes a node. Moving the active branch’s leaf pointer backward leaves every node — including the ones the pointer used to point through — exactly where it was in the DAG. Whenever the rewind actually moves the pointer off the branch’s previous leaf, the OLD leaf is preserved under a freshly-named sibling branch (so it stays independently addressable/enumerable, not merely “still linked in but orphaned from every named branch”) — see the rewind-preservation regression test. The next turn appended after a rewind becomes a NEW child of the rewind target, i.e. a sibling of whatever used to follow it — exactly “rewind = fork at the rewind point” (module 21’s row).

§Off by default / byte-identical

Nothing in this module is on any hot path. A SessionTree is only ever constructed by an explicit caller (never implicitly by crate::session::Session loading/saving, never by a runtime agent loop) and its sidecar (<name>.tree.json) is only ever written by an explicit an explicit session-store tree-write call — so a session that never invokes any tree operation has no .tree.json file at all, and every existing linear read/write path (Session::to_native_jsonl/ from_native_str, SessionStore::save/load) is untouched byte-for-byte. capabilities.session_tree.enabled (§3.1, module 21; exposed by the composition layer’s SessionTree module switch and runtime configuration flags for tree enablement, summaries, and labels, allowing a caller to gate on — this module’s own API has no runtime dependency on that flag (a library caller can always use SessionTree directly, exactly like native store forking does not gate on any capability either).

Structs§

Branch
A named pointer into the tree: leaf is the node this branch currently ends at (its “current-leaf pointer”, module 21’s phrase). None only for a brand-new, still-empty tree’s implicit branch before any node exists.
BranchSummary
A branch-carried summary (module 21 “branch summaries”, the C7 lossy→sidecar-backed path): a short human-readable digest of a branch, paired with the pointer back to the full branch data (branch, a key into SessionTree::branches — the full nodes never move or get deleted, so this is always resolvable back to the source, §1.13).
SessionTree
The native in-place conversation tree (module 21). See the module doc comment for the full design (C7 tree-with-linear-projection, lossless rewind, branch summaries).
TreeNode
One addressable turn in the tree (module 21’s “entry”). Carries the full ChatMessage (this IS the full-fidelity source for a branched session — see the module doc’s “off by default” note: the plain linear transcript file remains the record for an UNBRANCHED session; this sidecar only exists once a tree operation actually ran), its parent/children links, and an optional human/agent-set label (module 21 “entry labels”).

Constants§

MAIN_BRANCH
The default/active branch name for a session that has never explicitly branched — the degenerate single-path tree’s one branch.

Traits§

BranchSummarizer
Injectable branch-summarization side-call (D-9), the module-21 analog of a caller-supplied branch summarizer — same shape, deliberately: a real implementation calls out to a cheap model; tests inject a deterministic fake. See SessionTree::summarize_branch_with’s doc comment for the “never blocks, never fails the caller” contract this trait’s Err feeds into.

Type Aliases§

NodeId
A tree-node id. Assigned by SessionTree’s monotonic allocator — a counter ("n0", "n1", …), not content-derived or random, so ids are deterministic and trivially testable, and so two nodes can never collide.