Repository navigation
feat(native-chat): serve the chat queue as its own bounded read - #26707
Draft
brennanb2025 wants to merge 3 commits into
Draft
brennanb2025 wants to merge 3 commits into
brennanb2025 wants to merge 3 commits into
Conversation
Hosts advertise agent-session.queue-pages.v1 and serve the queue as its own read: a summary on history/subscribe frames for readers that opt in with queueView 'paged-v1', plus byte-bounded queuedMessagesPage and queuedMessageRead methods. Shipped readers keep the full v1 list.
…t-queue-pages # Conflicts: # src/main/native-chat/agent-session-journal/queued-message-schema.ts # src/main/native-chat/agent-session-wire/structured-agent-session-host.ts # src/shared/protocol-version.ts
This branch has not been deployed
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.
ELI5
When an agent is busy, messages sent to its chat wait in a queue. Today every time a phone or a paired computer opens or reconnects to that chat, the host sends it the full text of every waiting message, attached to the chat history. A long queue makes that reply huge. To keep the reply under the network size limit, the host refuses new messages once the waiting text adds up to 1 MiB ("Too much text is waiting").
This PR teaches the host a second way to hand out the queue: a small summary (how many cards, from whom, which one blocks) rides the chat instead of the full list, and a device that wants the cards asks for them a page at a time, with each card's full text fetched only when it is opened. Nothing uses the new way yet; the next PRs switch desktop and phone to it and then remove the 1 MiB refusal.
What Changed
Problem. The host treats its queue as one value: history replies and live chat frames carry
queuedMessages, every waiting and returned card with its full body. So the size of the whole backlog rides every chat open and reconnect, and the host has to reject otherwise valid messages (the 1 MiB body-sum bound) to keep those frames under the 4 MiB remote limit.User-facing change. None in this PR. Every existing desktop, phone and paired computer gets exactly the same frames as before (a test compares the serialized frames). The host now advertises a new capability,
agent-session.queue-pages.v1, that no client uses yet.Mechanism (host and shared contract only; no UI).
agent-session.queue-pages.v1inprotocol-version.ts. Its comment says that clients must check for it before sending the new request field, because older hosts reject unknown params. Person-typed queueing (agent-session.queued-messages.v1) stays off.agentSession.historyandagentSession.subscribeacceptqueueView: 'paged-v1'. Wherever a reply or frame would have attachedqueuedMessages, an opted-in reader getsqueueSummaryinstead:generation,revision,total, counts by source (person / agent / unknown), the newest person card id, the returned card that blocks the queue, and whether Resume is available. The summary is at most 8 KiB and carries no bodies or id lists.queuePauseandnextQueuedMessageIdstill ride as before.agentSession.queuedMessagesPage. A keyset page of compact previews, optionally filtered by source, anchored by a cursor or by a message id ("around this card"). Each preview has at most 512 bytes of text, up to three sender labels, the hold, the returned reason and failure kind, andbodyBytes. Each page is built until the next row would push the complete RPC reply envelope over 512 KiB. If a cursor is from an old generation, or its card has moved or is gone, the reply isstale, never a misleadingly empty queue.agentSession.queuedMessageRead. The exact stored body of one card. When the body is larger than the reply budget, it comes in ordered parts of the original JSON. Cards that have settled come back readable:gone,handed-offorstate-changed, not an error.body.from), and unknown or malformed provenance never counts as a person. No column is added and there is no schema-version bump, so older builds still open the database. All queue reads share D1's single "readable row" condition.Why
The root cause of the queue limits is that the queue's whole contents travel with every chat read. The cure is to serve the queue as its own read, separate from the chat, and refresh it when it changes. This PR adds that read, plus a fixed byte budget for each reply, which a phone link needs. Desktop and phone switch to it in the next PR. After that, the last PR in this sequence removes the size refusal and stops publishing the full list.
Alternatives considered:
Differences from the common pattern (the common pattern serves queued messages as a separate read, invalidated on change, not attached to every history frame):
queuedMessageslist is still published to readers that don't opt in, and the 1 MiB refusal still applies. Both are removed in the last PR of this sequence, after desktop and phone use the new read.Linked Issue
N/A (maintainer). Stacked on #26651 (reads the queue by row headers).
Visual Proof
N/A: host and wire contract only. No client uses the new capability yet, and existing frames are unchanged (pinned by serialized-shape tests).
Testing
Targeted vitest (76 files, 825 tests) covers the new page, body and summary builders; full reply envelopes stay within budget, including emoji, escaping, long ids and huge metadata; walking cursors both ways reaches every row; reassembled bodies equal the stored body; stale cursors; "around" anchors; and a 10,000-card summary that query-plan checks and JSON.parse spies show loads no bodies. They also cover subscriber coalescing and idle release; byte-identical legacy frames; RPC gates, strict params and the mobile allowlist; paired/SSH routing; and the D1 and existing queue suites.
orca-ci-checks(lint, code quality, localization, typecheck): 23/23.Cross-version wire test: CI runs it. Locally, extracting the release checkout timed out twice before any assertion ran, so it is not counted as passing here.
I manually tested these changes locally (no UI or client change in this PR)
Automated tests added/updated, or explained why not below
Agent skill upstream boundary
Notes
IF NOT EXISTS, and their builds are best-effort.Author: @BrennanKB5