Skip to content

feat(service,host): a running agent's transcript is held open on Neon Realtime rather than polled - #3282

Merged
luke-agent[bot] merged 1 commit into
mainfrom
luke/live-agent-transcript
Oct 20, 2026
Merged

luke-agent[bot] merged 1 commit into
mainfrom
luke/live-agent-transcript

Conversation

@charleslpan

@charleslpan charleslpan commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

Stacked on #3232 (luke/neon-realtime-scope-inbox). This is the first PR of the Neon Realtime transcript work: it removes the service's 500 ms poll for a shown running agent's transcript. The renderer doesn't change.

What it does

The window still sends codingAgents.messages with its cursor. The host now answers it the way scope-inbox-follow.ts answers an inbox:

  1. On the first read, the host seals the transcript's live statements (GET /api/live/agent-transcript?id=[&call=]) and subscribes over Realtime. Subscriptions are kept in an RcMap keyed by agent and call, and let go 30 s after the last read.
  2. It reads the page through the existing route with wait=none. That returns at once with holds, which says whether today's held read would have kept waiting on that page.
  3. If holds is false, the host answers the window. If it's true, the host reads again only when the statements move, up to the same 20 s hold. At the end of the hold it reads one last time, as the service does today.

The page itself, including the usage priced from the AI Gateway catalog, is still assembled by the service.

Live statements (coder-host/transcript-live.ts) are deterministic, aliased with liveSelection, and read only the owner's rows:

  • messages: count and max seq.
  • turns: grouped by status, with count, stopped count, and when each last changed.
  • conversation: journal revision and title.
  • agent: the agent's title.
  • workers: each child conversation with its title and delegation call.

Together these move whenever the held read would wake: a message added or amended, a status change (including a line awaiting its turn), a title landing, or a delegation being recorded.

Realtime alone, with no held-read fallback:

  • If sealing doesn't answer, the host asks again every second until it's sealed or not found; a read waiting meanwhile is answered with the page as it stands when its hold ends.
  • If a subscription stops, it's marked broken, which has the waiting read read at once, then sealed and opened again after a second.
  • If the account doesn't reach the agent, the answer is not found.
  • Sealed statements stay in the host.

Behaviour changes worth knowing

  • Two status changes come from the clock alone, with no row changing: an agent passing STARTING_GRACE (5 min), and the title window (TITLE_TIMEOUT) closing. Today the service sees these within 500 ms. Here they're seen at the end of the hold, up to 20 s later. This only affects the last empty page, never a message.
  • An ended agent's tab also opens a short-lived subscription, because the host has to seal before it knows the service takes wait=none.

Tests

  • coder-host.test.ts (PGlite): a real coding turn is relayed event by event. After every event where the page past the cursor gains messages or changes status, the live rows have moved. holds matches the turn's state. Titles and delegations move them too, for the agent's own transcript and a worker's. Another account's statements read nothing.
  • live-app.test.ts: the transcript is sealed for the owner, a worker's under the child conversation its call opens. A stranger gets not found. With no Realtime, the endpoint answers unavailable.
  • coding-agents-app.test.ts: wait=none answers at once with holds, for the agent and for a worker whose delegation isn't recorded yet.
  • compose-coding-agents.test.ts (scripted Realtime): covers the at-once answer, a read held until the statements move, the empty page at the end of the hold, a stopped subscription read at once and resealed, not found, an unsealed transcript sealed again then read live, and sign-out.
  • Wire goldens for the three new live-wire schemas were recorded under LUKE_UPDATE_FIXTURES=1, and rewrites were regenerated.

pnpm check:affected passes apart from apps/desktop/src/main/app-menu.test.ts, which needs an Electron binary the sandbox doesn't have.

Next, if it stays clean

Assemble pages on the desktop from live rows, as pure functions in packages/features, and keep usage fetched.

Made with Luke


Summary by cubic

The host now answers a window's read of a running agent's transcript through Neon Realtime subscriptions instead of the service polling every 500 ms, while the window's read and the assembled page stay unchanged.

Behavior changes

  • Two status changes come from the clock alone (the 5-minute start grace, and the title-timeout window closing) and are now seen up to 20 s later at the end of the hold instead of within 500 ms; this only affects the last empty page, never a message.
  • An ended agent's tab still opens a short-lived subscription, because the host must seal before it knows the service takes wait=none.

Fallbacks

  • If sealing is unavailable or comes from an older service, the old held read answers as before; a subscription stopping partway through a read hands that read to the held read.
  • An account that doesn't reach the agent answers not found; sealed statements stay on the host.

Written for commit 4cae45f. Summary will update on new commits.

View guided diff

@luke-agent
luke-agent Bot changed the base branch from main to luke/neon-realtime-scope-inbox October 17, 2026 01:22
@vercel

vercel Bot commented Oct 17, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
luke-web Ready Ready Preview Oct 20, 2026 8:33pm UTC

Request Review

@luke-agent luke-agent Bot closed this Oct 17, 2026
@luke-agent luke-agent Bot reopened this Oct 17, 2026
@luke-agent
luke-agent Bot force-pushed the luke/neon-realtime-scope-inbox branch from 0a6e6cd to 35d1920 Compare October 18, 2026 19:21
@luke-agent
luke-agent Bot force-pushed the luke/live-agent-transcript branch from 4cae45f to c4310b1 Compare October 18, 2026 19:59
@luke-agent
luke-agent Bot force-pushed the luke/neon-realtime-scope-inbox branch from 35d1920 to ff729c0 Compare October 18, 2026 21:12
@luke-agent
luke-agent Bot force-pushed the luke/live-agent-transcript branch from c4310b1 to 4da7abd Compare October 18, 2026 21:32
@luke-agent
luke-agent Bot force-pushed the luke/neon-realtime-scope-inbox branch from ff729c0 to 7c1c859 Compare October 19, 2026 18:52
@luke-agent
luke-agent Bot force-pushed the luke/live-agent-transcript branch from 4da7abd to f103d68 Compare October 19, 2026 18:52
@luke-agent
luke-agent Bot force-pushed the luke/neon-realtime-scope-inbox branch 3 times, most recently from 39ca561 to 40e1669 Compare October 20, 2026 01:43
@luke-agent
luke-agent Bot force-pushed the luke/live-agent-transcript branch from f103d68 to ed855dd Compare October 20, 2026 02:24
@luke-agent
luke-agent Bot force-pushed the luke/neon-realtime-scope-inbox branch 2 times, most recently from ff97170 to 9710e65 Compare October 20, 2026 15:50
@luke-agent
luke-agent Bot force-pushed the luke/live-agent-transcript branch from 446f346 to f749ce2 Compare October 20, 2026 19:29
@luke-agent
luke-agent Bot changed the base branch from luke/neon-realtime-scope-inbox to main October 20, 2026 19:29
@luke-agent luke-agent Bot closed this Oct 20, 2026
@luke-agent luke-agent Bot reopened this Oct 20, 2026
@luke-agent
luke-agent Bot force-pushed the luke/live-agent-transcript branch from f749ce2 to 0cdccf2 Compare October 20, 2026 19:58
… Realtime rather than polled

The service seals what a transcript's pages are read from at
GET /api/live/agent-transcript?id=[&call=] (live.agentTranscript on the oRPC
live contract): its messages, its turns by status, its conversation's journal
revision and title, the agent's title, and its workers' titles and
delegations, each joined through the reader's membership of the agent's
workspace and keyed. An agent's or a worker's transcript read with wait=none
answers at once, with whether the held read would have held on it.

The host answers the window's codingAgents.messages from those statements:
it reads the page at once, and while the service says it would hold, reads it
again only when they move, within the same 20 s hold. A seal the service did
not answer, or a subscription that stopped, is sealed again a second later; a
sign-out lets every subscription go. The renderer is unchanged.

The statements run through mutableFunctionsOf and the seal check in
live-statements.test.ts.

Co-authored-by: luke-agent[bot] <339870089+luke-agent[bot]@users.noreply.github.com>
@luke-agent
luke-agent Bot marked this pull request as ready for review October 20, 2026 20:30
@luke-agent
luke-agent Bot force-pushed the luke/live-agent-transcript branch from 0cdccf2 to 62391f1 Compare October 20, 2026 20:30
@luke-agent
luke-agent Bot enabled auto-merge October 20, 2026 20:30
@luke-agent
luke-agent Bot added this pull request to the merge queue Oct 20, 2026
Merged via the queue into main with commit 46db789 Oct 20, 2026
11 of 12 checks passed
@luke-agent
luke-agent Bot deleted the luke/live-agent-transcript branch October 20, 2026 20:48

This branch was successfully deployed

1 active deployment
Preview — 62391f17 Deployed Oct 20, 2026 by vercel[bot]
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.

1 participant