Skip to content

v0.18.0: the sessions release — rail, epochs, compaction honesty#45

Merged
lroolle merged 10 commits into
mainfrom
sessions-layer-work
Jul 20, 2026
Merged

v0.18.0: the sessions release — rail, epochs, compaction honesty#45
lroolle merged 10 commits into
mainfrom
sessions-layer-work

Conversation

@lroolle

@lroolle lroolle commented Jul 20, 2026

Copy link
Copy Markdown
Member

The sessions-layer arc, rounds 1–11 plus release prep. The Sessions tab
becomes a real reconstruction of what the harness did to the
conversation: sessions, threads, model epochs, subagent branches, and
compaction — all on one rail.

Highlights (full story in CHANGELOG 0.18.0):

  • Sessions layer: messages -> threads -> sessions -> project; threads
    session-scoped; one-conversation sessions absorb their card into the
    header; short thread keys in URLs.
  • The session rail: turns as verdict dots, epochs as ring marks
    (a /model switch opens an epoch, never a thread), subagents attached
    as branch rows at their spawn turn with outcome inline, superseded
    exchanges grey at the ordinal they occupied.
  • Compaction reconstructed and displayed: post-compact packings merge
    back in (anchor merge for folds, full append for rewrites), full
    /compact continuations reunify structurally (same sid + same system
    identity block + parent quiet forever — harness strings are one vote,
    not the gate), and every boundary renders a break node with the
    context collapse in turns and tokens.
  • Fixes: false "rewound" labels on injected recaps and repacks,
    post-compact conversations splitting into a second thread (or being
    claimed as a subagent via summary-quoted dispatch prompts), parallel
    same-prompt Task spawns collapsing onto one dispatch, the t1+ jump
    bug, masked-star session URLs, weak cache hits shown green.
  • New regression net: a sanitized real triple-packing fixture cut by
    tests/sanitize-trace.ts (kept in-tree). 456 tests.

🤖 Generated with Claude Code

lroolle and others added 10 commits July 17, 2026 02:09
…ty UX

Session reconstruction (devlog 2026-07-17):
- Per-turn attribution is index-first, content-verified against the
  pair's assembled response (turnContentSig), content-scan on mismatch.
  Claude Code repacks history with ephemeral notice turns, so raw
  indices land on the wrong turns — the 5-model test session had three
  silently unattributed replies and two attributed by luck.
- Pairs matching nothing classify REWOUND (prefix-divergent: /rewind or
  an edited turn) or UNATTRIBUTED — marked in the conversation, never
  silently blank. Rewound exchanges mark their divergence point and
  link to the kept wire pair.
- A thread's model is a set (per-model requests/tokens/cost); the face
  model is the one with the most output tokens. "fable-5 · 63 turns"
  for five models' work becomes "fable-5 +4 models · 63 turns".
- compact rewind guard: a request whose final history message appears
  nowhere in the keeper's packing holds the only copy of an erased
  exchange — kept full, never stubbed. Verified pre/post-compact:
  identical turns and attribution on the real trace.
- Regression fixture cut from the controlled 5-model + /rewind session,
  sanitized with equality-preserving hash tokens (10KB zst, leak-swept).

Launch & continuity UX:
- cctrace is silent while the traced client owns the terminal: all
  output funnels through src/termlog.ts, buffered from spawn to exit,
  flushed after — mid-session continuity merges and crash-guard notes
  no longer corrupt the TUI.
- --continue/--resume preloads the resumed session speculatively
  (newest prior session id, or the explicit --resume id), so the UI
  opens populated; the first live request confirms the guess or evicts
  the preload with a client re-init.
- --version and the banner print the commit hash: baked in by make
  build --define, git fallback on source runs (guarded so an npm
  install inside another repo never reports that repo's HEAD).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…n tab

The Session tab gains the third grouping level (session-tab.md decision 1):
messages -> threads -> sessions -> project.

- Threads are session-scoped: the grouping key is <sid>|<wire key>, so two
  sessions whose first message is identical (/clear mid-run) never merge
  into one thread. "" stays an honest no-session-id bucket.
- Threads pane: >=2 session ids render collapsible per-session sections,
  newest activity first — header carries short sid (click = copy full),
  wall-clock range, request count, error rollup. Fold state survives live
  re-renders keyed by sid. Single-session traces render flat: zero new
  chrome, the common case pays nothing.
- Routes: #/session/<sid8>[/<thread-key>] resolves sid-prefix after the
  thread-key back-compat check; old links keep working. [ / ] switch
  sessions from the keyboard.
- Default focus is the newest session's main thread; a NEW session id
  appearing mid-run follows only while tailing — reading history is never
  yanked.
- Request rows (the selected thread's list) mark rewound / compact-folded /
  failed requests inline; thread meta counts rewound exchanges; assistant
  turns that switch models prefix "→ model".
- grok recap classification now reads the conv header instead of a key
  prefix (the sid prefix broke the old check — caught by tests).

Verified through the dom-stub (the real page script executes): zero-chrome
single-session, ordered sections, sid-prefix routing, grammar-clean markup.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The default thread label was "fable-5 +4 models · 63 turns" — the model
occupied the identity slot, which read as "threads are grouped by model"
(they aren't; a thread is a conversation that can span models via
/model). Clean split:

- label names the conversation: "63 turns" / "[explore] search for..." /
  "prewarm"; the kind badge already says chat/agent/utility
- the model becomes a quiet right-aligned chip on the card ("fable-5 +4"
  after mid-thread switches, per-model split in the tooltip — one
  modelTitle helper shared with the convo-pane chip)
- selection emphasis moves up to the SESSION container: a faint accent
  wash on the section summary and the selected thread head, replacing
  the accent border (edges read as chrome — same rule that fixed
  user-turn emphasis)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The session card takes the bordered-box treatment threads used to have;
threads flatten to divided rows inside it; the selected thread's request
rows nest below. Containment is drawn by structure, not indentation.

Header gets real hierarchy instead of a flat mid-dot line: identity
left (short sid, strong, click-to-copy + conversation turns), quiet
attributes right-aligned (HH:MM range — no seconds, no degenerate
ranges — request count, errors). The word "session" is gone: the box
is the session.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…supersession

Claude Code rewrites history under a live conversation: ephemeral
recaps are injected, answered, and dropped; auto-compact folds old
tool_use turns into text and shifts every index; a manual /compact
replaces everything with a continuation summary — which also mints a
new first-message sig, and the summary quotes old Task dispatch
prompts verbatim, so the agent-linker could claim the post-compact
MAIN CHAT as a subagent. The spine-only reconstruction flagged all of
it as "rewound" and silently lost every post-compact turn.

- anchor merge: requests following the spine align against it (the
  deepest shared turn, 2 verified neighbors — boilerplate turns like
  system notifications collide on sig alone) and append the genuinely
  new turns at their timeline position
- full-append fallback: no anchor + a 10+ turn drop = a full rewrite;
  the whole post-compact packing appends — the summary turn is a real
  timeline event
- compact gate: a 10+ turn drop below the running max marks the
  repack (notice wobbles are 1-3, a /rewind steps back a few);
  pre-compact pairs judged against a post-compact spine classify
  unattributed, never superseded
- reunification: a continuation thread merges into the same session's
  deepest-history conversation, decided structurally — same real sid,
  same system identity block (first non-billing block; subagents and
  utilities differ), smaller start, parent quiet forever. The harness
  preamble text is one extra vote, not the gate, so a reworded or
  customized harness degrades gracefully
- unique dispatch claiming: parallel Tasks with identical prompts
  pair 1:1 with their threads in wire order (all three used to match
  the first tool_use — wrong labels, collapsed branch links)
- t.compactions exports each boundary (at/pairId/fromTurns/toTurns/
  mode) for the display layer
- turnSnippet: command blocks arrive in BOTH orders on the wire
  (/model puts <command-name> first, /codex:status and skill
  invocations put <command-message> first); args join the preview;
  the harness's "Base directory for this skill" expansion never wins
- summarizeCache: a <90% cache hit is a warning, not a green — most
  of the prompt was re-billed at full input price

Verified against two real traces: a 513->440 auto-compact (fold) and
a triple-packing manual-/compact session (full rewrite + false agent
claim). All thresholds regression-locked by synthetic fixtures; the
sanitized /rewind fixture stays green — it caught two wrong designs
along the way.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…oundaries

The session body was three unrelated row grammars stacked: epoch rows
(glyph + T0), turn rows (dot + ordinal), and detached thread CARDS for
subagents — even though the spawning turn is known. It didn't read as
one session.

One principle now: the session is a single rail. A continuous
accent-tinted line runs down a shared gutter (.rgut); every row is a
node on it:

- turn dots (existing cache/error verdict colors) sit ON the line,
  punched out with a bg halo; user turns stay hollow rings
- epoch heads become hollow accent rings (.enode) — the 3-circle
  git glyph is gone from rows; the rail itself is the branch metaphor
- subagent threads attach AT their spawn turn as branch rows: a CSS
  elbow off the rail, label + outcome inline ("[explore] map the
  repo · 2 turns · $0.0035"), content indented one outline level.
  The detached card disappears while the parent is selected and
  returns whenever it isn't — nothing becomes unreachable
- superseded exchanges stay grey nodes at the ordinal they occupied
- compact boundaries render explicitly: a break node (two slanted
  hairlines, the axis-break glyph) + "COMPACTED · 513 -> 440 turns"
  row at the boundary; hover spells the context collapse in turns AND
  tokens, fold vs full rewrite, and the honest sentence — everything
  above survives only in the summary/folded form; click opens the
  first post-compact wire request. The convo pane draws a dashed
  divider at the same index, and a rendered continuation-summary turn
  is tagged (position-first via t.compactions, preamble fallback) —
  never displayed as something the user typed

Grammar-checked against hostile wire content; verified through the
real page script on both compaction traces.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e header

Live runs, view serves, and snapshots all pass traceFile through
PageMeta; view resolves the project from the log dir's parent when
it's a standard ./.cctrace, so a served trace stops claiming the
trace filename as its project name.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
session-tab.md gains the settled concept ("a thread is one
conversation as the user experiences it" + the event table: /model ->
epoch, compact -> boundary event, /clear -> new session), the session
rail, the compact boundary display, and the structural-over-strings
detection rationale (incl. why capture-time metadata stamping was
rejected and the request-diff refactor direction). CLAUDE.md/SKILL.md
updated to match.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A sanitized cut of a real session that survived a fold compaction and
two continuation packings, with parallel same-prompt subagents and the
false-agent-claim hazard in one file. Pins every threshold calibrated
against live wire mess (the 10+ turn drop, the 2-neighbor anchor, the
system identity block, unique dispatch claiming) — the tripwire for
when Claude Code changes its repacking behavior.

tests/sanitize-trace.ts is the cutter, kept this time: equality-
preserving hash tokens (same string -> same token, so grouping,
attribution, and the anchor merge behave as on the original — whole-
string hashing is strictly harsher than the runtime's capped-prefix
compares), marker prefixes preserved (<system-reminder>, command
wrappers, the continuation preamble, billing-header system blocks),
sids mapped to deterministic fake uuids, zero original text.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CHANGELOG entry for the sessions-layer arc (hierarchy, rail, epochs,
compaction honesty, attribution), version bump, README session bullets
(en + zh) brought up to the 0.18 story.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@lroolle
lroolle merged commit d74f621 into main Jul 20, 2026
1 check passed
@lroolle
lroolle deleted the sessions-layer-work branch July 20, 2026 10:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant