agents proxy: opencode M3 — history on re-attach - #1949
Open
julia-ye wants to merge 2 commits into
Open
Conversation
GET /session/{id}/message is served from a one-shot replay_only
StreamSession pass, reconstructing each hosted run as its user message
(run.started's prompt text — the SSE wire delivers it as data.agent, not
the proto's user_input; both are accepted) and assistant message (token
deltas accumulated; reasoning as its own part). opencode's attach
replays history client-side by fetching this endpoint (?limit=100
observed live), so re-attaching a fresh proxy shows prior turns; the
session list now includes the bridged session whenever it has
replayable history, which is what --continue resumes through.
Ids are minted from turn event times with the real time-encoding, with
monotonic T allocation across turns — two turns sharing a millisecond
would otherwise interleave (caught by test). time.completed comes from
the run.completed event, not the run's max event time — late stragglers
(run.log after completion) otherwise inflate a 2s turn into a 20m badge
(found live). Also stubs GET /session/{id}/diff and /todo (empty
arrays, the TUI's post-turn refresh lookups).
Live-verified: a fresh proxy + `opencode attach --continue` renders
the session's six prior turns with correct prompts, answers, order, and
durations.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
harness-api's replay_only stream lingers ~8s after catching up (measured: 68 events, close at exactly 8.0s both runs), and an attach fetches history twice (session-list gating + the message list), so resume rendered ~16s late. History is now reconstructed once and cached — the mutex doubles as single-flight — warmed in the background off the facade's first request (the TUI's health preflight precedes the attach burst), and invalidated when a live turn completes. Warm-path timings: 8s -> 11ms. The 8s replay-close linger itself is a harness-api behavior worth fixing server-side (it also slows doctl agents logs); tracked outside this PR. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
SSharma-10
reviewed
Sep 4, 2026
Comment on lines
+18
to
+19
| // captured from a real server (see the capture doc). No cache: history is | ||
| // fetched per request, and the TUI asks once per attach. |
Contributor
There was a problem hiding this comment.
this header is stale after the cache commit, it still says "No cache: history is fetched per request", but history() below is cached + single-flight and invalidated on turn end. Worth updating so the file doc matches the behavior
SSharma-10
reviewed
Sep 4, 2026
Comment on lines
+64
to
+69
| f.histMu.Lock() | ||
| defer f.histMu.Unlock() | ||
| if f.histValid { | ||
| return f.hist, nil | ||
| } | ||
| msgs, err := f.fetchHistory(ctx) |
Contributor
There was a problem hiding this comment.
history() holds histMu for the whole replay (~8s). invalidateHistory() needs that same lock, so if a turn completes while history is still loading, the event loop can wait on that lock until the replay finishes
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #1948 (M2 — review that first; this diff is M3 only). Third slice of the opencode facade: re-attaching shows prior turns.
What's here
GET /session/{id}/messageserved from a one-shotreplay_onlyStreamSessionpass over the session's durable event history, reconstructed into the[{info, parts}]shape captured from a real server. opencode replays history client-side —attachfetches this endpoint (?limit=100observed live, honored) — so nothing is pushed on connect and codex's--replayflag machinery has no opencode equivalent.GET /sessionwhen it has replayable history (or was used through this proxy), which is whatopencode attach --continueand the session picker resume through a freshly started proxy. A truly fresh session still lists empty (the M0-captured fresh-attach state).GET /session/{id}/diffand/todostubs (empty arrays) — the TUI's post-turn and resume refresh lookups, previously 404s.Found while verifying live
run.started's prompt text arrives on the SSE wire asdata.agent, not the proto'suser_input— both keys are accepted (with the proto name preferred), so history reconstructs the user side of each turn.time.completedmust come from therun.completedevent, not the run's max event time: late stragglers (run.logafter completion) inflated a 2-second turn into a "20m 1s" duration badge.Testing
limithandling, fresh-session emptiness, failed-turn shape, history-gated session listing.opencode attach --continuerendered all six with correct prompts, answers, order, and durations.Not in this PR
Tool-call parts and real token/cost numbers in history and live turns (M4), permission round-trips (M5), stream reconnect (M6).
🤖 Generated with Claude Code