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:
leafis the node this branch currently ends at (its “current-leaf pointer”, module 21’s phrase).Noneonly for a brand-new, still-empty tree’s implicit branch before any node exists. - Branch
Summary - 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 intoSessionTree::branches— the full nodes never move or get deleted, so this is always resolvable back to the source, §1.13). - Session
Tree - 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).
- Tree
Node - 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§
- Branch
Summarizer - 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’sErrfeeds 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.