Skip to content

Strategy C: OpenCode server event-stream intercept for finer-than-file attribution grain #92

Description

@willwashburn

Context

PR #72 landed strategy D only. Strategy C is explicitly deferred and intentionally lower-priority — it is the only path to per-tool-call grain on OpenCode but is only worth pursuing if a concrete attribution question needs that resolution.

From PR #72:

Strategy C — OpenCode server event-stream intercept. Per the issue, only worth pursuing if a concrete attribution question needs finer-than-file grain.

From #63:

OpenCode has a server mode with an event stream. Subscribing to that would give real per-event parity (tool-call grain, not file grain). Codex has no equivalent surface I'm aware of — the rollout JSONL is the only observation point, and (B) is the ceiling there.

Worth pursuing only if a concrete attribution question (per-tool-call cost, mid-turn retry detection) needs finer grain than (B) provides.

Relevant call sites:

  • packages/cli/src/commands/opencode.ts — current ingest is post-exit, file-grain only.
  • packages/reader/src/opencode.ts — file-grain reader; already populates parentID -> isSidechain.
  • The ~/.local/share/opencode/storage/{session,message}/ JSON files are derivative of the live event stream; the stream itself is the higher-fidelity source.

This issue exists primarily as a decision-point tracker: do not implement until at least one of the trigger questions below has a real backlog item attached.

Proposal

When triggered, build a thin client that subscribes to OpenCode's server event stream and feeds events through the same normalized TurnRecord / ContentRecord / appendTurns path the file-grain reader uses, with per-tool-call grain instead of per-message rollups.

Trigger questions (file an explicit dependent issue if any of these become active work):

  1. Per-tool-call cost attribution for OpenCode (e.g. "which Bash invocation in turn N consumed the most input tokens?")
  2. Mid-turn retry / failure-streak detection on OpenCode (currently file-grain rollups collapse retries)
  3. Real-time waste signaling on OpenCode (file-grain doesn't expose it within the same turn)

Sketch (do not build until triggered):

  • New module packages/reader/src/opencode-stream.ts with a subscribe(opts) function that yields normalized events.
  • New CLI subcommand burn opencode --stream (or fold into burn watch --opencode-stream) that connects to the local OpenCode server, subscribes, and pipes events to appendTurns / appendContent.
  • Cursor model needs an event-id high-watermark (not byte offset) since the stream is not file-backed.
  • Cross-check stream-derived turns against file-derived turns (after wrapper exits) to detect drift; one is canonical, the other is the validator.
  • Spawn-tag attribution still flows from the env-var contract (Wire RELAYBURN_* env-var contract into burn claude/codex/opencode (#63) #72) — this issue does not change that.

Tests (when implemented):

  • Mock OpenCode server emits a fixture event sequence; assert turn/content shape matches the file-grain reader for the same logical session.
  • Disconnection mid-stream: cursor advances only past the last fully-consumed event.
  • Reconnect at high-watermark resumes without duplicates (appendTurns dedup is the safety net).

Acceptance criteria

(Apply only when this issue is activated by a triggering question.)

  • At least one trigger question above is linked from a concrete backlog item before work starts.
  • burn opencode --stream (or equivalent) ingests at per-tool-call grain.
  • File-grain reader continues to work as a fallback when the server isn't running.
  • Stream- and file-derived attribution agree for the same logical session within a tolerance documented in tests.

Out of scope

  • Codex equivalent — Codex has no event-stream surface; rollout JSONL is the ceiling.
  • Replacing the file-grain reader.
  • OpenCode upstream changes.

Refs

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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