Skip to content

opencode-http: finished runs are opaque — readable transcript + server-stdout split + gated SSE trace #306

Description

@pbean

The gap

The opencode-http adapter is the only adapter whose finished runs are opaque.

The claude and generic tmux adapters get a full replay for free: the pane scrollback is captured to <task>.log, so after a run you can read exactly what the agent said and did. The HTTP adapter has no pane to scrape — it drives the session over the API and observes GET /event — and <task>.log currently receives only the spawned opencode serve process's own stdout: startup INFO lines, "loading instance", request noise. None of the agent's actual work appears anywhere on disk.

The practical cost: when a run stalls, times out, or lands a wrong verdict, there's nothing to read back. Every other adapter supports "look at the log and see what happened"; this one does not, which makes exactly the failures that most need diagnosis the ones you can't diagnose.

Adopted approach (PR #279)

@jackmcintyre proposed and implemented the fix in #279, and we're adopting that PR. The shape:

  • Split the sinks per task. <task>.log becomes a curated, role-coloured, human-readable transcript; the server's own stdout moves to a separate <task>.server.out so diagnostics stop mixing into the transcript.
  • Render the transcript off the existing SSE dispatch in adapters/opencode_http.py — the frames are already being read and filtered by sessionID, so this is a second consumer of a stream we already have. No new connection, no polling, no change to the deterministic control loop.
  • Keep the complete structured SSE trace in a per-task JSONL sink for post-hoc replay.
  • Resolve the speaker by messageID (from message.updated.info.{id,role}) rather than "last role seen", because message.updated frames arrive out of order and a re-emit mid-turn would otherwise mislabel the assistant's reply.

Revisions on adoption

We're pushing these directly to the contributor's branch (maintainer edits), so authorship stays with them:

  • Repoint the spawn-failure error. It still cites <task>.log, but the server's stdout now lands in <task>.server.out — as written, a failed spawn points at the file that no longer holds the diagnostics.
  • Best-effort guard around the new render/emit calls in _dispatch_sse. An unexpected frame shape must not be able to take out the SSE reader thread upstream of the idle / error queue puts — the control loop's liveness cannot depend on the logging path.
  • Rename the trace to <task>.sse.jsonl, knob-gate it with an off-switch, and exclude per-token message.part.delta records. The deltas dominate the file and re-carry text that message.part.updated already delivers complete, so they cost a lot of bytes for little replay value.
  • Render tool activity. Live probe (see PR feat(opencode-http): readable run logs + structured event trace #279) shows agent tool use surfaces as message.part.updated with part.type == "tool" (part.tool, state.status pending→running→completed, state.input/state.output) — not command.executed, which never fires for tool use. The current render reads part.text, absent on tool parts, so the transcript shows no tool activity at all.
  • Correct the permission frame names and fields. 1.18.2 has no permission.updated; it is permission.asked (permission/patterns/metadata/always/tool), and permission.replied carries reply, not response.
  • Let session-less frames through the dispatch filter. file.edited carries only {"file": ...} with no sessionID, so it is dropped before the render runs. Safe to allowlist because bmad-loop runs one server per session.
  • Extend the module-docstring API pin list with the frame types this newly consumes: message.part.updated (complete-once semantics), message.part.delta, tool-typed parts, file.edited, permission.asked, permission.replied. That docstring is where the adapter's opencode contract is pinned (/event frame semantics, epoch-ms times, basic-auth shape), so anything newly read off the stream has to be named there with live-probe evidence behind it.
  • CHANGELOG entry + doc updates.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:adaptersCoding-CLI adapters and profilesblocked:prAn open PR in this repo would close or unblock itenhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions