Skip to content

harness-runtime: replay history before opening the live attach - #1979

Merged
SSharma-10 merged 1 commit into
digitalocean:feat/agents-subcommandsfrom
nkurraDO:nkurra/harness-attach-history-seam
Sep 22, 2026
Merged

SSharma-10 merged 1 commit into
digitalocean:feat/agents-subcommandsfrom
nkurraDO:nkurra/harness-attach-history-seam

Conversation

@nkurraDO

@nkurraDO nkurraDO commented Sep 21, 2026 •

Copy link
Copy Markdown
Contributor

What

attachToSession opened one connection whose catching_up (history) and live phases shared the wire — spliced apart server-side — with stdin read from the moment the streaming goroutine was launched, before any of that history had rendered.

Investigated how ui-aquarium's /events consumer does this (useSessionHistoryPager + useStreamSession in apps/ui-openharness): two sequential calls, not one — a finite replay_only history read first, then a live attach anchored strictly after that read's newest event (replayFrom: pager.newestEventId), with input held (ready = ... && !streamPending) until both settle. This PR gives doctl's interactive attach the same shape.

replayHistoryBeforeAttach reads history to completion synchronously, on the same goroutine that calls streamWithReconnect/runAttach right after:

  • stdin is never read until history has fully rendered — this closes off MARSOHS-1026's user-visible symptom (a resent/duplicate turn rendering live, indistinguishable from new output, interleaved with whatever the user just typed) as a client-side defense, independent of OHP's own ingest-side dedupe fix.
  • the live stream opens with ReplayFrom already seeded to the last history event's id — drainStream's existing cursor.set(ev.EventID) gives this for free — so there's no overlap window left for OHP to splice.

Best-effort on failure: a terminal error (auth, missing session) is reported once and returns, matching streamWithReconnect's own classifyStreamError handling — the live stream will report the same failure again on its own connect. A transient error falls through silently to the live stream's own catching_up window, which is the pre-existing behavior for that failure mode; this read isn't a new failure surface.

Doesn't touch agents logs / the codex proxy --replay path (#1903's backward paging) — different code path (one-shot batch read vs. this interactive attach seam).

Proof of testing

  • Added unit tests

New: TestReplayHistoryBeforeAttach_RendersHistoryAndSeedsCursor, TestReplayHistoryBeforeAttach_TerminalErrorReportsAndReturns, TestReplayHistoryBeforeAttach_TransientErrorFallsThroughSilently, TestReplayHistoryBeforeAttach_CancelledContextIsSilent.

go test ./commands/... and go vet ./commands/... clean, including the full existing TestAttach*/TestStreamWithReconnect*/TestDrainStream* suite (unchanged).

attachToSession previously opened a single connection whose catching_up
(history) and live phases shared the wire, spliced apart server-side, with
stdin read from the moment the goroutine was launched -- before any of that
history had rendered.

ui-aquarium's /events consumer (useSessionHistoryPager + useStreamSession)
instead does this as two sequential calls: a finite replay_only history read
first, then a live attach anchored strictly after that read's newest event,
with input held until both settle. Give doctl the same shape:
replayHistoryBeforeAttach reads history to completion synchronously, on the
same goroutine that calls streamWithReconnect/runAttach right after, so:

  - stdin is never read until history has fully rendered, closing off
    MARSOHS-1026's user-visible symptom (a resent/duplicate turn rendering
    live, indistinguishable from new output, interleaved with whatever the
    user just typed) as a client-side defense independent of OHP's own
    ingest-side dedupe.
  - the live stream opens with ReplayFrom already seeded to the last history
    event's id (drainStream's existing cursor.set(ev.EventID) does this for
    free), so there is no overlap window left for OHP to splice.

Best-effort: a terminal error is reported once and returns, matching
streamWithReconnect's own classifyStreamError handling; a transient error
falls through silently to the live stream's own catching_up window, which is
the pre-existing behavior for that failure mode.
@SSharma-10
SSharma-10 merged commit d07925a into digitalocean:feat/agents-subcommands Sep 22, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants