Context
Claude Code's ~/.claude/projects/<slug>/<sessionId>.jsonl records carry a parentUuid field linking each row to its causal predecessor. Burn's reader currently groups rows into turns using time-window heuristics and row ordering (see crates/relayburn-sdk/src/reader/classifier.rs). That works most of the time but breaks under:
- Mid-stream interruptions (user cancels, resumes later — wall-clock gap between assistant rows in the same logical turn).
- Out-of-order JSONL flushes — Claude Code's writer is async; rows can land slightly out of timestamp order under load.
- Compaction artifacts — synthetic rows inserted at compaction time with timestamps that don't sit cleanly inside any turn window.
Prior art: agent-profiler sidesteps all of this by walking the UUID chain. Source: lib/claude-code/traces.js, findTurnRoot + sliceTurns. The rule:
Group records by walking backward up parentUuid to the nearest user-prompt ancestor — explicitly not timestamp-based. Handles re-ordered/torn JSONL.
Proposal
Replace the current turn-grouping heuristic with a UUID-chain walk:
- Build a
HashMap<Uuid, &Record> of all rows in the file.
- For each row, walk
parentUuid upward until you hit a user-prompt row (or None).
- That user-prompt UUID is the row's turn key.
- Fall back to timestamp grouping only for rows missing both
uuid and parentUuid (legacy/malformed).
Implementation sketch
crates/relayburn-sdk/src/reader/claude.rs (or wherever Claude turn grouping lives): introduce fn group_by_parent_chain(rows: &[Record]) -> HashMap<Uuid, Vec<&Record>>.
- Keep the existing time-window logic behind a feature flag or as the explicit fallback path for non-Claude harnesses.
- Add a fixture under
crates/relayburn-sdk/tests/fixtures/ with deliberately out-of-order rows and an interruption-resume pattern; assert turn membership matches the UUID chain, not the timestamp window.
Open questions
- Cycle detection. Trust Claude's writer? Or guard against accidental cycles with a visited set? Cheap; do it.
- Multiple roots per file. Long-running sessions with manual
/resume may produce multiple disjoint user-prompt roots; that's fine, they become separate turns.
- Codex equivalent. Codex rollouts don't have the same
parentUuid field. Keep this Claude-specific for now; document the asymmetry.
- Performance. O(N) build of the parent map + O(depth) walk per row. For a 50k-row session this is microseconds. No concern.
Acceptance
References
- agent-profiler:
lib/claude-code/traces.js — findTurnRoot, sliceTurns.
- burn:
crates/relayburn-sdk/src/reader/classifier.rs, crates/relayburn-sdk/src/reader/types.rs.
- Related: span-tree foundation (consumes the corrected grouping).
Context
Claude Code's
~/.claude/projects/<slug>/<sessionId>.jsonlrecords carry aparentUuidfield linking each row to its causal predecessor. Burn's reader currently groups rows into turns using time-window heuristics and row ordering (seecrates/relayburn-sdk/src/reader/classifier.rs). That works most of the time but breaks under:Prior art: agent-profiler sidesteps all of this by walking the UUID chain. Source:
lib/claude-code/traces.js,findTurnRoot+sliceTurns. The rule:Proposal
Replace the current turn-grouping heuristic with a UUID-chain walk:
HashMap<Uuid, &Record>of all rows in the file.parentUuidupward until you hit a user-prompt row (orNone).uuidandparentUuid(legacy/malformed).Implementation sketch
crates/relayburn-sdk/src/reader/claude.rs(or wherever Claude turn grouping lives): introducefn group_by_parent_chain(rows: &[Record]) -> HashMap<Uuid, Vec<&Record>>.crates/relayburn-sdk/tests/fixtures/with deliberately out-of-order rows and an interruption-resume pattern; assert turn membership matches the UUID chain, not the timestamp window.Open questions
/resumemay produce multiple disjoint user-prompt roots; that's fine, they become separate turns.parentUuidfield. Keep this Claude-specific for now; document the asymmetry.Acceptance
burn summary/burn hotspotsgolden tests on the cli-golden fixture.References
lib/claude-code/traces.js—findTurnRoot,sliceTurns.crates/relayburn-sdk/src/reader/classifier.rs,crates/relayburn-sdk/src/reader/types.rs.