Skip to content

agents: page session history backwards for bounded replay - #1903

Open
mishraishika24-pixel wants to merge 77 commits into
mainfrom
imishra/agents-stream-history-paging
Open

mishraishika24-pixel wants to merge 77 commits into
mainfrom
imishra/agents-stream-history-paging

Conversation

@mishraishika24-pixel

Copy link
Copy Markdown
Collaborator

A replay request no longer returns a session's whole transcript: the server caps it at its newest window and reports whether older events remain, so a client that wants full history has to walk backwards for it a page at a time.

do.LoadSessionHistory does that walk and stitches the pages back into chronological order, since they arrive newest-group first. Both readers of stored history go through it: agents logs and the codex proxy's --replay. Neither can print as it reads anymore -- the oldest events arrive from the last request -- so history is buffered before it renders.

agents logs gains --tail to stop the walk early, --page-size to size each request, and --before to read a single page and report the cursor for the one behind it. Its help text no longer claims a long session may replay only its recent activity; paging handles the per-request cap, and what remains unrecoverable is retention.

The vendored godo edits (before/limit query params, and an SSEReader comment hook so the has_more trailer is readable) mirror the change being upstreamed to digitalocean/godo and are in-tree until godo is re-vendored.

SSharma-10 and others added 30 commits May 28, 2026 23:40
doctl agent attach connected but printed nothing: the harness-api SSE
stream emits the SPI canonical event envelope (type/data/timestamp/
tenant_id, dot-separated event names) while vendored godo decoded
kind/payload/at/team_id, so every event fell through renderEvent.

- godo HostedAgentEvent: decode the SPI wire via UnmarshalJSON and switch
  the HostedAgentEventKind constants to the dot-separated SPI names.
- godo ErrorResponse: also read the nested {"error":{code,message}}
  envelope so failed calls show the server's reason, not a bare status.
- agents: align event payload structs/rendering and HITL tracking to the
  SPI data shapes, and acknowledge each submit with its run id so the
  agent's startup latency isn't mistaken for a hang (which caused
  duplicate submits).
- tests for the wire decode, rendering, nested-error surfacing, and ack.

The vendored godo edits mirror the fix being upstreamed to
digitalocean/godo and are in-tree until godo is re-vendored.

Co-authored-by: Cursor <cursoragent@cursor.com>
…1866)

* displayers: drop SandboxID column from hosted-agent session output

* add vendor files

---------

Co-authored-by: SSharma-10 <shivanisharma@digitalocean.com>
Vendors glamour and its transitive deps (chroma, goldmark, bluemonday,
etc.) and bumps lipgloss/termenv so `doctl agents` can render the
agent's markdown replies with syntax-highlighted code blocks. Also
re-syncs the pinned godo vendor tree (adds Session.SandboxID).

Co-authored-by: Cursor <cursoragent@cursor.com>
Buffer the agent's token stream and render each turn as markdown with
syntax-highlighted code blocks. Collapse HITL prompts to a compact,
color-coded approve/reject/defer menu that shows the command awaiting
approval, arrow-key selectable in a TTY. Correlate multiple pending
HITLs to their tool calls via a FIFO queue so no approval line is blank.

Co-authored-by: Cursor <cursoragent@cursor.com>
julia-ye and others added 25 commits July 28, 2026 12:44
Bump godo to the build that points StreamSession at the data plane's
`/v2/agents/sessions/{id}/events`, and handle what the new endpoint adds.

`agents logs` is unaffected: godo keeps replay-only reads on the control
plane's `/stream`, which is the only side holding the stored history.

The live stream carries `stream.state` transport control frames reporting
connection health. They are not session activity, so they never render and
never become the reconnect cursor. One of them needs acting on:
`superseded` means another connection from this device took the session
over, and it is the one stream end we must not reconnect from — re-attaching
would take it back, and the two windows would evict each other indefinitely.

Also drop the "one connected device per session" claim from `start-proxy`'s
help. Distinct devices now coexist; what actually conflicts is running the
proxy and `attach` from the same machine, since both stream as this device.

Test changes: the reconnect test reads the resume cursor from Last-Event-ID
instead of a replay_from query parameter, and the agentproxy harness serves
`/events` and opens every stream with a `stream.state` frame, so the codex
facade tests exercise a stream shaped like the real one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The godo bump in the previous commit updated go.mod/go.sum but not
vendor/, so `go test -mod=vendor` (what CI runs) failed with
"inconsistent vendoring": go.mod required the new pseudo-version while
vendor/modules.txt still declared v1.201.0-beta.1.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Bumps godo to pick up the replay-only move onto
`/v2/agents/sessions/{id}/events?replay_only=true`. `doctl agents logs` was
the last reader still pointed at the control plane's `/stream`; it now shares
the data-plane endpoint with `doctl agents attach`.

No change to how events are parsed or rendered -- the endpoint emits the same
canonical envelope, and the stream still ends after the last stored event,
which is what lets the command exit.

The help text no longer promises the *full* history. The data plane serves
history out of the durable event log rather than the control plane's
database, so it is bounded by retention and by a per-read cap; a long-idle or
unusually long session may replay only its more recent activity.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
)

Co-authored-by: Adil Hafeez <13196462+adilhafeez@users.noreply.github.com>
…story. Update handleConn to trigger AfterReply after successful JSON-RPC replies. Adjust Dispatch method to call AfterReply when a request is successful. Enhance replay logic to prevent race conditions with thread/start and thread/resume replies.
…ered after successful JSON-RPC replies in handleConn. This change aligns with the contract of AfterReply and improves the handling of replay sessions.
A replay request no longer returns a session's whole transcript: the
server caps it at its newest window and reports whether older events
remain, so a client that wants full history has to walk backwards for it
a page at a time.

do.LoadSessionHistory does that walk and stitches the pages back into
chronological order, since they arrive newest-group first. Both readers
of stored history go through it: `agents logs` and the codex proxy's
--replay. Neither can print as it reads anymore -- the oldest events
arrive from the last request -- so history is buffered before it renders.

`agents logs` gains --tail to stop the walk early, --page-size to size
each request, and --before to read a single page and report the cursor
for the one behind it. Its help text no longer claims a long session may
replay only its recent activity; paging handles the per-request cap, and
what remains unrecoverable is retention.

The vendored godo edits (before/limit query params, and an SSEReader
comment hook so the has_more trailer is readable) mirror the change being
upstreamed to digitalocean/godo and are in-tree until godo is re-vendored.

Co-authored-by: Cursor <cursoragent@cursor.com>
@sanpj2292
sanpj2292 requested a review from SSharma-10 August 6, 2026 17:08
@SSharma-10

Copy link
Copy Markdown
Contributor

Can you re-vendor from OHS_endpoints and remove the hand-patched vendor/github.com/digitalocean/godo changes?

Comment thread commands/agents.go
Comment on lines +1177 to +1178
if before != "" {
return runAgentsLogsPage(ctx, c, sessionID, before, pageSize)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: if both --before and --tail are set, this path wins and --tail is ignored. Might be worth rejecting that combination or calling it out in the flag help

Base automatically changed from feat/agents-subcommands to main September 22, 2026 03:54
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.

7 participants