Skip to content

burn waste --patterns: consume ToolResultEventRecord chronology (replace TurnRecord-only inference) #113

@willwashburn

Description

@willwashburn

Context

burn waste --patterns (closed #11) currently infers retry-loop, consecutive-failure, and edit-revert patterns from per-turn fields on TurnRecordtoolCalls[].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 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.

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 Codex or OpenCode session with 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.
  • 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.

Out of scope

Refs

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions