Skip to content

Execution graph substrate: SessionRelationshipRecord + ToolResultEventRecord (#42)#77

Merged
willwashburn merged 4 commits intomainfrom
feat/execution-graph-42
Apr 26, 2026
Merged

Execution graph substrate: SessionRelationshipRecord + ToolResultEventRecord (#42)#77
willwashburn merged 4 commits intomainfrom
feat/execution-graph-42

Conversation

@willwashburn
Copy link
Copy Markdown
Member

@willwashburn willwashburn commented Apr 25, 2026

Refs #42.

This is a tractable first PR for the cross-source execution graph called for in #42. The issue calls out that several backlog items (subagent tree #8, waste-pattern detection #11, future archive work) implicitly assume normalized session relationships and tool-result chronology that today's TurnRecord is too lossy to carry. Rather than try to ship every source / consumer at once, this PR lays the foundation:

What landed

@relayburn/reader — two new record shapes (additive, v: 1):

  • SessionRelationshipRecordrelationshipType: 'root' | 'continuation' | 'fork' | 'subagent', plus parentToolUseId / agentId / subagentType / description for subagent edges. The schema covers all four relationship kinds the issue spells out; only root and subagent are populated in this PR (Claude historical logs don't surface fork / continuation evidence we don't already cover).
  • ToolResultEventRecord — chronological tool-output stream keyed by toolUseId and a per-session monotonic eventIndex. Status: running / completed / errored / cancelled / unknown. Discriminator eventSource covers tool_result / subagent_notification / queue_event / progress_event / function_call_output. Metadata-only — contentLength + contentHash, never raw bytes.

Claude passive reader (parseClaudeSession + parseClaudeSessionIncremental) now returns relationships and toolResultEvents alongside the existing turns / content / events arrays. Roots emit once per session id; one subagent row per distinct invocation discovered (joining Subagent.agentId); each tool_result block becomes a ToolResultEventRecord. Agent/Task spawn events are post-annotated with the resolved subagent's agentId so the two record types can be joined. The incremental parser respects the same endOffset deferral content/compaction events use, so resumed ingest doesn't double-emit.

@relayburn/ledger picks up two new LedgerLine kinds — relationship and tool_result_event — with matching appendRelationships / appendToolResultEvents writers, queryRelationships / queryToolResultEvents readers, and isSessionRelationshipLine / isToolResultEventLine guards. Both append-only; both dedup through the existing ~/.relayburn/ledger-index namespace via new relationshipIdHash (keyed on type + agentId + parentToolUseId) and toolResultEventIdHash (keyed on sessionId + toolUseId + eventIndex). rebuildIndex re-indexes both kinds.

@relayburn/cliburn ingest (runtime + hook paths) and burn claude now persist relationships + tool-result events when the Claude reader emits them. No new flags or output yet.

What's deferred

  • Codex passive-reader populationfunction_call_output, spawn_agent / wait relationships, terminal subagent notifications. The shape is in place; codex.ts wiring is a follow-up.
  • OpenCode passive-reader population — parent session ids, tool-result sizes / statuses where exposed.
  • Hook-path ingest (#7) emitting the same shapes — current Claude hook ingest goes through the reader, so it picks up everything the reader emits, but a future hook-only event source (e.g. subagent_notification) needs its own emitter.
  • Consumer CLI surfaceburn summary --subagent-tree, burn diagnose, burn waste --patterns, burn summary --by-relationship. The execution graph is now persisted, so these can be built on top.
  • Fork / continuation row population for Claude. Historical Claude logs surface parentUuid chains we already use for subagents; explicit fork/continuation evidence (across-file sourceSessionId, /resume markers) needs targeted parser work.

Test plan

  • pnpm install && pnpm run test:ts — 347 tests pass (added 8 new tests).
  • Reader: tool-result events emit with monotonic eventIndex, the right status / isError / contentLength / contentHash for the retry-loop fixture.
  • Reader: nested-subagent fixture yields one root + two subagent relationship rows with correct agentId / parentToolUseId / relatedSessionId / subagentType / description, and the Agent/Task spawn tool_result events get joined back to the right child invocation via agentId.
  • Ledger: round-trip appendRelationships / queryRelationships and appendToolResultEvents / queryToolResultEvents; dedup is keyed correctly so re-appending the same record doesn't grow the file; query filters work for sessionId (matching child or parent) and source.
  • Ledger: rebuildIndex re-indexes the new line kinds and re-appending after rebuild is a no-op.

🤖 Generated with Claude Code


Open in Devin Review

…EventRecord (#42)

First slice of the cross-source execution graph called for in #42. Introduces
two normalized, append-only record types that sit beside `TurnRecord` and
preserve passive-reader metadata that's currently flattened or lost — session
relationships (root / continuation / fork / subagent) and tool-result event
chronology keyed by `toolUseId`. Both ride at `v: 1`.

The Claude passive reader populates them on the first pass: one root row per
session id, one subagent row per discovered invocation (joining to the
existing `Subagent.agentId`), and one `ToolResultEventRecord` per tool_result
block in a user line, with monotonic `eventIndex` and per-toolUseId
`callIndex`. Spawn events (Agent/Task tool_results that resolved to a
sidechain) are post-annotated with the spawned subagent's `agentId` so
consumers can join the two record types.

The ledger picks up two new line kinds (`relationship` /
`tool_result_event`) with matching `appendRelationships` /
`appendToolResultEvents` writers and `queryRelationships` /
`queryToolResultEvents` readers. Both dedup through the existing
`ledger-index` namespace via new id-hash helpers, and `rebuildIndex`
re-indexes both kinds. Old readers skip unknown kinds — the existing
`isTurnLine` / `isStampLine` / `isCompactionLine` guards already filter to
known shapes.

`burn ingest` (runtime + hook paths) and `burn claude` now persist the new
records when the Claude reader emits them, so the substrate lands
automatically alongside turns. No new flags or output yet; this PR is purely
the foundation #8 (subagent tree) and #11 (waste / retry / terminal-outcome
analysis) should consume next.

Codex `function_call_output` and OpenCode SQLite-backed event population are
deferred to follow-up PRs, as is the cross-source query CLI surface
(`burn summary --subagent-tree`, `burn diagnose`, etc.) that #42 sketches.

Refs #42.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

⚠️ 1 issue in files not directly in the diff

⚠️ Root CHANGELOG.md missing [Unreleased] entry for cross-package work (AGENTS.md violation) (CHANGELOG.md:7)

This PR touches three packages (packages/reader, packages/ledger, packages/cli) and each package's CHANGELOG.md is updated under [Unreleased]. However, the root CHANGELOG.md has no corresponding entry. AGENTS.md states: "Update [Unreleased] only when the work spans packages or warrants a top-level summary; single-package work belongs only in that package's CHANGELOG." This PR clearly spans packages, so the root CHANGELOG should have an [Unreleased] entry describing the execution-graph substrate addition, consistent with how the existing MCP entry (CHANGELOG.md:11) was added for its cross-package work.

View 5 additional findings in Devin Review.

Open in Devin Review

…Devin review on #77)

Devin's review on PR #77 noted the root CHANGELOG.md was missing an
[Unreleased] entry for this PR's cross-package work, which violates the
AGENTS.md rule (line 38): "Update [Unreleased] only when the work spans
packages or warrants a top-level summary."

This PR touches reader, ledger, and cli, so it qualifies. Mirrors the
shape of the existing MCP entry one bullet up: top-level summary +
indented per-package bullets.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@willwashburn
Copy link
Copy Markdown
Member Author

Re: Devin review — root CHANGELOG.md missing [Unreleased] entry for cross-package work.

Fixed in e0ee4f1. Added an [Unreleased] bullet to the root CHANGELOG.md summarizing the execution-graph substrate, mirroring the shape of the existing MCP entry one bullet up (top-level summary + indented per-package bullets for reader / ledger / cli). Per AGENTS.md line 38, this PR qualifies because it spans three packages.

The other 5 findings only render on Devin's site and were not posted as line-anchored review comments here, so I can't address them through GitHub. If any are blockers, please re-post them as inline review comments.

# Conflicts:
#	packages/cli/CHANGELOG.md
#	packages/ledger/CHANGELOG.md
#	packages/reader/CHANGELOG.md
#	packages/reader/src/claude.test.ts
#	packages/reader/src/claude.ts
#	packages/reader/src/index.ts
devin-ai-integration[bot]

This comment was marked as resolved.

Per AGENTS.md, new unreleased work belongs under [Unreleased]. The #42
entry was sitting under the released [0.13.1] section because it landed
alongside the mcp-server PR before [Unreleased] was empty; with main
having since cut 0.14–0.18, the entry now strictly belongs above 0.18.0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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