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/SessionStore 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 [tests::linear_projection_of_a_from_linear_tree_matches_the_source_messages].

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 (crate::store::SessionStore::save_tree); 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 [tests::rewind_preserves_the_rewound_past_as_a_recoverable_sibling_branch]. 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 crate::Agent’s loop) and its sidecar (<name>.tree.json) is only ever written by an explicit crate::store::SessionStore::save_tree 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; already wired into crate::modules::ModuleId::SessionTree since P3) is surfaced on crate::Config as crate::Config::session_tree_enabled/crate::Config::session_tree_branch_summaries/ crate::Config::session_tree_labels for a future caller (CLI/TUI) 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 crate::store::SessionStore::fork doesn’t 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 reduce::summarize::SpanSummarizer — 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::alloc_id — a monotonic counter ("n0", "n1", …), not content-derived or random, so ids are deterministic and trivially testable, and so two nodes can never collide.