You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
burn waste --patterns (closed #11) currently infers retry-loop, consecutive-failure, and edit-revert patterns from per-turn fields on TurnRecord — toolCalls[].isError, retries, and the Edit pre/post-hashes — see packages/analyze/src/waste.ts (detectPatterns) and packages/analyze/src/waste.test.ts.
PR #77 landed ToolResultEventRecord exactly so these detectors don't have to keep reconstructing tool-result chronology from per-turn flags. From the PR's deferred-work list:
Consumer CLI surface — burn 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.
Issue #42's acceptance also explicitly calls this out:
The graph is rich enough that #11 can compute failures / retries / subagent terminal outcomes without source-specific hacks.
Today the ledger persists tool_result_event rows (via appendToolResultEvents / queryToolResultEvents from PR #77) but the waste-pattern detectors never read them. That keeps Codex / OpenCode pattern detection blocked on each parser duplicating per-turn isError population, hides the cancelled vs errored vs running distinction the new ToolResultStatus carries, and ignores non-tool_result event sources (subagent_notification, queue_event, progress_event, function_call_output).
Proposal
Migrate detectPatterns in @relayburn/analyze to take (turns, toolResultEvents) and use the event chronology as the primary signal, with TurnRecord.toolCalls[].isError retained as a fallback for sessions ingested before the graph was populated.
Retry loops. Group ToolResultEventRecords by toolUseId and walk consecutive events sharing the same argsHash (joined back to TurnRecord.toolCalls); count attempts whose terminal status === 'errored' to identify loops of length ≥ 3. Distinguish from cancelled (user/agent gave up — surface separately, not as a retry) and from running events whose terminal status never landed.
Consecutive failures. Walk events ordered by eventIndex per session; count any run of ≥ 3 distinct toolUseIds whose terminal status === 'errored'. Subagent terminal notifications (eventSource === 'subagent_notification' with status === 'errored') count as failures too — answers "how many subagent invocations ended badly?" which the per-turn path can't see.
Edit-revert. Already content-hash-driven on TurnRecord.toolCalls[].editPreHash / editPostHash; this detector is unaffected by the graph (no equivalent signal in ToolResultEventRecord). Leave as-is.
Compaction loss. Already keyed off CompactionEvent, independent of the graph. Leave as-is.
CLI plumbing.packages/cli/src/commands/waste.ts extends its queryAll slice with queryToolResultEvents({ since, project, sessionId, ... }) and threads both into detectPatterns.
Fallback. When toolResultEvents is empty for a session (legacy ingest), fall back to today's TurnRecord.toolCalls[].isError-based path so existing data still produces results.
Acceptance criteria
burn waste --patterns retries,failures produces identical output for a Claude session whose ToolResultEventRecords are present (vs the legacy per-turn path) on a fixture that has both populated.
A session with cancelled tool_result events (e.g. interrupted Bash) does not surface the cancelled run as a retry-loop or consecutive-failure event; cancellations are reported in their own bucket.
A session whose only failure evidence is a subagent_notification with status === 'errored' (no per-turn isError set) appears in the consecutive-failures detector when chained.
A session with no ToolResultEventRecords still produces the legacy detector output via the fallback.
--json output gains a per-finding eventSource annotation when the finding was derived from the graph (so consumers can tell graph-backed findings apart from fallback).
packages/analyze/src/waste.test.ts covers: graph-only, fallback-only, mixed input, cancellation isolation, subagent-notification failures, and the JSON shape.
Context
burn waste --patterns(closed #11) currently infers retry-loop, consecutive-failure, and edit-revert patterns from per-turn fields onTurnRecord—toolCalls[].isError,retries, and the Edit pre/post-hashes — seepackages/analyze/src/waste.ts(detectPatterns) andpackages/analyze/src/waste.test.ts.PR #77 landed
ToolResultEventRecordexactly so these detectors don't have to keep reconstructing tool-result chronology from per-turn flags. From the PR's deferred-work list:Issue #42's acceptance also explicitly calls this out:
Today the ledger persists
tool_result_eventrows (viaappendToolResultEvents/queryToolResultEventsfrom PR #77) but the waste-pattern detectors never read them. That keeps Codex / OpenCode pattern detection blocked on each parser duplicating per-turnisErrorpopulation, hides thecancelledvserroredvsrunningdistinction the newToolResultStatuscarries, and ignores non-tool_resultevent sources (subagent_notification,queue_event,progress_event,function_call_output).Proposal
Migrate
detectPatternsin@relayburn/analyzeto take(turns, toolResultEvents)and use the event chronology as the primary signal, withTurnRecord.toolCalls[].isErrorretained as a fallback for sessions ingested before the graph was populated.ToolResultEventRecords bytoolUseIdand walk consecutive events sharing the sameargsHash(joined back toTurnRecord.toolCalls); count attempts whose terminalstatus === 'errored'to identify loops of length ≥ 3. Distinguish fromcancelled(user/agent gave up — surface separately, not as a retry) and fromrunningevents whose terminal status never landed.eventIndexper session; count any run of ≥ 3 distincttoolUseIds whose terminalstatus === 'errored'. Subagent terminal notifications (eventSource === 'subagent_notification'withstatus === 'errored') count as failures too — answers "how many subagent invocations ended badly?" which the per-turn path can't see.TurnRecord.toolCalls[].editPreHash/editPostHash; this detector is unaffected by the graph (no equivalent signal inToolResultEventRecord). Leave as-is.CompactionEvent, independent of the graph. Leave as-is.packages/cli/src/commands/waste.tsextends itsqueryAllslice withqueryToolResultEvents({ since, project, sessionId, ... })and threads both intodetectPatterns.toolResultEventsis empty for a session (legacy ingest), fall back to today'sTurnRecord.toolCalls[].isError-based path so existing data still produces results.Acceptance criteria
burn waste --patterns retries,failuresproduces identical output for a Claude session whoseToolResultEventRecords are present (vs the legacy per-turn path) on a fixture that has both populated.cancelledtool_result events (e.g. interrupted Bash) does not surface the cancelled run as a retry-loop or consecutive-failure event; cancellations are reported in their own bucket.subagent_notificationwithstatus === 'errored'(no per-turnisErrorset) appears in the consecutive-failures detector when chained.ToolResultEventRecords populated (per Codex passive reader: populate SessionRelationshipRecord and ToolResultEventRecord #87 / OpenCode passive reader: populate SessionRelationshipRecord and ToolResultEventRecord #93) yields retry-loop / failure detection without per-source CLI branches.ToolResultEventRecords still produces the legacy detector output via the fallback.--jsonoutput gains a per-findingeventSourceannotation when the finding was derived from the graph (so consumers can tell graph-backed findings apart from fallback).packages/analyze/src/waste.test.tscovers: graph-only, fallback-only, mixed input, cancellation isolation, subagent-notification failures, and the JSON shape.Out of scope
attributeWasteper-tool-call attribution path (burn waste: per-tool-call and per-file spend attribution #3) — that's separate from pattern detection. Sized per-tool-call attribution againstToolResultEventRecord.contentLengthis a follow-up (Per-tool-call cost attribution: use UserTurnRecord byteLen for proportional allocation in by-tool and waste #102 covers theUserTurnRecordbyteLen side; an analogous tool-result side would be its own issue).TurnRecord.toolCalls[].isErroragainst the graph (no rewriting of existing ledger lines).ToolResultEventRecord— covered by Codex passive reader: populate SessionRelationshipRecord and ToolResultEventRecord #87 / OpenCode passive reader: populate SessionRelationshipRecord and ToolResultEventRecord #93.Refs
--subagent-tree) and burn diagnose: surface execution-graph (relationships + tool-result chronology) #111 (burn diagnose).