Skip to content

fix(native-chat): never refuse a user's write because the retry record is full - #26543

Merged
brennanb2025 merged 3 commits into
mainfrom
brennanb2025/chat-write-ledger-never-blocks
Oct 8, 2026
Merged

brennanb2025 merged 3 commits into
mainfrom
brennanb2025/chat-write-ledger-never-blocks

Conversation

@brennanb2025

@brennanb2025 brennanb2025 commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 9 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​93 $\color{#cf222e}{\Huge{\mathbf{−}}}$​91 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​2
Prod 18 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​29 $\color{#cf222e}{\Huge{\mathbf{−}}}$​104 $\color{#cf222e}{\Huge{\mathbf{−}}}$​75

ELI5

ELI5

When you send a chat message, Orca writes down a receipt for it, so that if the send is retried (say the connection drops) Orca replays the first answer instead of sending the message twice. Orca kept those receipts for a day and allowed at most 4,096 of them in total, and at most 512 per sender. When the pile was full, Orca refused every new message until old receipts aged out. Agents talking to each other through Orca fill that pile in an ordinary day, so a user's own message was refused with "Orca has received too many requests in the last day. Your message was not sent." This removes the limit: a receipt still lasts exactly as long as a retry can arrive, and nothing refuses a message because the pile is big.

What Changed

Before: on a busy day of multi-agent work, the composer refused the user's message (and setting changes, answers, and new agent launches) with "Orca has received too many requests in the last day." Nothing the user could do fixed it except waiting up to a day. Seen live on 2026-10-08: the receipts table held exactly 4,096 rows; the user's own sends were 313 of them, and the rest came from orchestration mail between agents, agent starts, and restart continuations, arriving at roughly 200 to 300 per hour. The same refusal also stopped agents from starting worker chats that night.

After: writes are never refused because of how many receipts exist. Retries still replay their recorded answer, a reused id with different contents is still refused, and an id older than the retry window is still refused rather than run again.

Mechanism. Three places counted retry records and refused new work at a fixed count:

  • the durable chat-write record (src/shared/agent-session-operation-ledger.ts): 512 per caller, 4,096 total;
  • the runtime's in-memory record of agent-session creates (terminal.createAgentSession, src/main/runtime/orca-runtime-create-agent-session.ts): the same limits;
  • the SSH relay's in-memory record of those creates (src/relay/pty-handler.ts): 4,096.

All three count limits are removed. Each record's lifetime is unchanged: it lives for the window in which a retry of its id could still arrive, and is pruned after that. The relay's separate limit on how many terminals can run at once is a real resource bound and is unchanged.

Older hosts. A paired Orca server or desktop that hasn't updated (for example, a phone talking to an older desktop) still sends this refusal. Clients keep understanding it, and the message now gives the user something to do: "Your message was not sent. Orca on the computer running this chat has hit a request limit. Update Orca there, then try again." (translated in every catalog).

Removed dead code. The desktop's agent-launch path had a fallback that re-ran a launch without a receipt when its own host refused with this limit. That launch always goes to the window's own main process, which ships in the same build and can no longer refuse this way, and an SSH relay of a different version can't connect at all, so the fallback is gone. If the refusal ever arrived anyway, the launch is reported as not started like any other refused launch.

Why

The limit existed to bound memory: a receipt can't be dropped early, or a late retry of it would run the work a second time. But enforcing that bound by refusing new work made unrelated background traffic block the user, and the user had no way to clear it. The common pattern for retry receipts keeps a receipt per command and replays it on a duplicate, with no count limit and no refusal when there are many.

Alternatives considered:

  • Raise the limits. Moves the cliff; a busier day hits it again.
  • Drop the oldest receipts when full. A retry of a dropped id would be accepted as new and could send a message or start an agent twice.
  • Drop old receipts and remember a cutoff time below which ids are refused. One busy sender would push the cutoff forward for everyone, and a device with a slightly slow clock could have its first send refused. That's the same kind of coupling in another form.
  • Keep the receipts in the database and look them up there instead of holding them all in memory. This is the right longer-term shape, and it is a follow-up PR (below). It is not needed to stop the refusal, and keeping this PR small makes it safe to take into the next release.

Cost without the limit. Measured on the live table: about 235 writes an hour on a busy multi-agent day, so about 5,600 receipts held at once (the old limit was suppressing some of that, so real demand may be a little higher). At that size a send spends about 2 to 3 ms of main-process time on receipt bookkeeping and the receipts take about 3.5 MB of memory. It grows linearly: about 9 ms per send at 20,000 receipts. Removing the limit also removed two per-caller counting scans from every admission. No Orca producer mints new ids in a retry loop; the one way to multiply rows is agents mailing a chat whose message queue is full (about one row per message), which is being fixed separately.

Differences from the common pattern, labelled:

  • Intended: receipts expire after the retry window (one day plus five minutes of clock-skew allowance) instead of being kept indefinitely. Ids carry their creation time and an id older than the window is refused as expired, so an expired receipt can never let an old retry run again.
  • Temporary: receipts are loaded into memory, copied per store transaction, and pruned when a write is admitted, rather than looked up in the database with an index. Follow-up: move the receipt record to indexed database lookups, with expiry cleanup that runs outside a user's write.
  • Temporary: the runtime's and the SSH relay's records of agent-session creates are in memory, so a restart forgets them. That was already true and isn't changed here. Follow-up: make create receipts durable, both on the runtime and on the relay.

Linked Issue

N/A, found in live use on 2026-10-08.

Visual Proof

Hidden test build on macOS, with a fresh profile whose receipts table was seeded to 4,096 unexpired rows from fake orchestration callers (as on the machine where this was found), then the same message sent in a native chat.

Before (base build): the send is refused, the text goes back into the composer, and the agent never receives it. The table stays at 4,096 rows.

Before: send refused with the too-many-requests notice

After (this PR): the same send goes through and the agent answers; the table grows past 4,096.

After: the same send goes through

After, a second send (and a composer setting change, not pictured) also go through; the table holds 4,100 rows.

After: a second send also goes through

Testing

  • src/shared/agent-session-operation-ledger.test.ts: a fresh id is admitted with 5,600 unexpired rows held by other callers and 600 by the same caller, and a retry of an earlier id still replays. This fails on main.

  • src/main/runtime/rpc/methods/agent-launch-restart-replay.test.ts: a desktop agent launch starts with 600 unexpired operations already recorded for the desktop. Before, this was refused at 512.

  • src/main/runtime/orca-runtime-agent-session-operation.test.ts: 4,097 creates from one caller all start, and a retry of the first still replays (fails with either old limit restored).

  • src/relay/pty-handler-spawn-admission.test.ts: the relay starts a new create with 4,096 records held and still replays an earlier one (fails with the old limit restored).

  • src/main/native-chat/agent-session-wire/structured-agent-session-refusal-retry.test.ts: the census of refusals the host can produce now lists the capacity refusal as not produced by this host.

  • src/shared/agent-session-refusal-notice.test.ts, use-structured-agent-session-options.test.tsx: the new wording, and that "try again" appears only where updating Orca is the fix.

  • Also ran: structured-agent-session-send-evidence.test.ts, agent-launch-through-host.test.ts, launch-agent-new-tab-host-route*.test.ts, structured-mailbox-pointer-host.test.ts. Typecheck: node, web and cli projects. Localization catalog, extraction and coverage checks.

  • Electron check (hidden window, driven by an automated agent): the before/after above.

  • I manually tested these changes locally

  • Automated tests added/updated, or explained why not below

Review

Agent skill upstream boundary

  • Not applicable, or this change copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.

Notes

  • Cross-platform: no platform-specific code.
  • SSH: the relay's create record loses its count limit too. The relay is installed per app version and a relay of another version can't connect, so a new desktop never meets an older relay's limit.
  • Mobile and paired servers: the wire contract is unchanged. Hosts stop producing one refusal code, and every client still decodes it.
  • Not verified live: a new client against an older paired host showing the new wording (unit tests only), and Claude or Codex chats (the Electron check used another agent; all go through the same host admission).

Checklist

  • This PR is small and focused
  • I explained what changed and why (ELI5, the user-facing before/after, the mechanism, and why over the alternatives)
  • Before/after screenshots or videos attached for UI changes, or N/A with reason
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, and path/shortcut impact considered (or N/A)
  • pnpm lint, pnpm typecheck, pnpm test, and pnpm build pass (or CI will cover; local preferred)

Author: @BrennanKB5

…d is full

The record that lets a retried chat write or agent launch replay instead of
running twice refused every new write once it held 512 ids for one caller or
4,096 in total. Background agent traffic fills that in a normal day, so the
user's own message was refused with "too many requests in the last day".

Remove the count limit from the durable record, the in-memory launch record
and the SSH relay's launch record; each entry still lives exactly as long as
the retry window it protects. Clients still understand the refusal from older
hosts, and its message now says to update Orca on that computer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@brennanb2025

Copy link
Copy Markdown
Contributor Author

Review summary

The problem. On 2026-10-08 the native-chat composer refused a message with "Orca has received too many requests in the last day. Your message was not sent." Orca keeps a one-day receipt for every chat write so a retry replays instead of running twice, and it refused all new writes once 4,096 receipts (or 512 from one sender) were held. Agents mailing each other through Orca filled that limit in a normal day (only 313 of the 4,096 were the user's own), so background traffic blocked the user, with nothing they could do but wait.

The user-facing change. Chat sends, setting changes, answers and agent starts are no longer refused because of how many receipts exist. Retries still replay, a reused id with different contents is still refused, and an id older than the retry window is still refused. If a user's phone or desktop talks to an older Orca that still has the limit, the notice now says "Orca on the computer running this chat has hit a request limit. Update Orca there, then try again."

Review. Three independent reviewers (correctness and compatibility, performance and runaway growth, and an architecture challenger who designed a fix from the problem alone before reading this one), then a review of the fix commit, then a release-readiness checklist. No P0, P1 or P2 issues. The architecture challenger arrived at the same design and agreed with keeping the database-backed receipt store as a separate follow-up, so this PR stays small enough to take into a release.

Fixed during review

  • Removed a desktop launch fallback that could no longer run: the launch always goes to the window's own main process, which ships in the same build and can no longer refuse this way.
  • Added tests showing the runtime's and the SSH relay's in-memory create records accept work past the old limit (each fails if the limit is put back).
  • Comments that still gave the removed limit as the reason for code; Spanish wording made formal like the rest of its block; a test group renamed to match what it tests.
  • PR description: corrected two sentences about the removed fallback and added measured costs.

Deferred (follow-ups, not blockers)

  • Receipts are still held in memory and copied on each store transaction. At the observed rate (about 235 writes an hour, about 5,600 receipts held) a send spends about 2 to 3 ms on this, and memory is about 3.5 MB. Cost grows linearly. The planned follow-up moves receipts to indexed database lookups, with expired-receipt cleanup outside the user's write (it also fixes a one-statement-per-row delete that runs inside that write).
  • The only realistic way to multiply receipts is agents mailing a chat whose 20-message queue is full (about one receipt per message). That is being fixed in the mail-nudge work.
  • Already true before this PR and unchanged: an agent-session create that never finishes keeps its in-memory record (durable create receipts are planned), and a separate mobile terminal limit (15-minute window, not reachable in practice) was passed to the limits audit.

What was verified

  • Electron check on a hidden macOS test build, driven by an automated agent: a profile seeded with 4,096 unexpired receipts. The base build refused the send with the old notice and the agent got nothing. This PR's build sent it, got the answer, accepted a second send and a setting change, and the table grew to 4,100. Screenshots are in the description.
  • Unit tests for the ledger, launch replay, runtime and relay create records, refusal census and notice wording. Typecheck for node, web and cli. The repo's CI static checks. Full CI is green on the final head 547f938 (21 passed, 22 skipped as not applicable, none failed).

Not verified

  • Live: a new client against an older paired host showing the new wording (unit tests only); mobile; Claude or Codex chats (the Electron check used another agent, and every chat goes through the same host admission); Windows and Linux (no platform-specific code).

Known residual risks (unproven, not blockers)

  • Downgrading Orca within a day of holding more than 4,096 receipts brings back the old refusal on the older build until receipts expire. No data is lost.
  • Nothing caps how many ids a paired phone or web client can mint. Paired clients are already trusted, and no Orca client mints ids in a loop; the cost is memory and per-write time for up to a day.

@brennanb2025
brennanb2025 marked this pull request as ready for review October 8, 2026 12:40
@coderabbitai

coderabbitai Bot commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 4e66f8b9-e774-49d5-9aed-a6397921893b
📥 Commits

Reviewing files that changed from the base of the PR and between 5252884 and 547f938.

📒 Files selected for processing (27)
  • src/main/native-chat/agent-session-wire/structured-agent-session-refusal-retry.test.ts
  • src/main/runtime/orca-runtime-agent-session-operation.test.ts
  • src/main/runtime/orca-runtime-core.ts
  • src/main/runtime/orca-runtime-create-agent-session.ts
  • src/main/runtime/orca-runtime-get-pty-record-for-pane-key.ts
  • src/main/runtime/orchestration/structured-mailbox-pointer-delivery.ts
  • src/main/runtime/orchestration/structured-mailbox-pointer-host.test.ts
  • src/main/runtime/orchestration/structured-mailbox-pointer-host.ts
  • src/main/runtime/rpc/methods/agent-launch-restart-replay.test.ts
  • src/relay/pty-handler-spawn-admission.test.ts
  • src/relay/pty-handler.ts
  • src/renderer/src/components/native-chat/use-structured-agent-session-options.test.tsx
  • src/renderer/src/i18n/en-runtime-required.json
  • src/renderer/src/i18n/locales/en.json
  • src/renderer/src/i18n/locales/es.json
  • src/renderer/src/i18n/locales/fr.json
  • src/renderer/src/i18n/locales/ja.json
  • src/renderer/src/i18n/locales/ko.json
  • src/renderer/src/i18n/locales/zh.json
  • src/renderer/src/lib/agent-launch-through-host.test.ts
  • src/renderer/src/lib/agent-launch-through-host.ts
  • src/shared/agent-session-operation-ledger.test.ts
  • src/shared/agent-session-operation-ledger.ts
  • src/shared/agent-session-refusal-notice.test.ts
  • src/shared/agent-session-refusal-notice.ts
  • src/shared/agent-session-refusal-reason-words.ts
  • src/shared/agent-session-write-notice-copy.ts
💤 Files with no reviewable changes (5)
  • src/main/runtime/orca-runtime-core.ts
  • src/renderer/src/lib/agent-launch-through-host.test.ts
  • src/renderer/src/lib/agent-launch-through-host.ts
  • src/main/runtime/orca-runtime-create-agent-session.ts
  • src/relay/pty-handler.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 7 remain after this review.


📝 Walkthrough

Walkthrough

The durable operation ledger, Orca runtime, and PTY handler no longer reject new operations because of per-caller, global, or map-size limits. Tests cover admission beyond the former limits and replay of existing operations. Launch handling and capacity notices now treat agent_session_operation_capacity as a refusal from an older host and direct users to update Orca.

Priority: ⬆️ High

Merge Risk: ⚪ Minimal · up to 547f9

Eligible writes and agent-session creates should no longer be refused solely because retry records reached the former limits. No actionable merge-blocking issue is established.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 60.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 5 functions across 15 files. (7 skipped: … Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: removing retry-record capacity refusals for native-chat writes.
Description check ✅ Passed The description is detailed and covers the user impact, implementation, rationale, alternatives, testing, visual proof, compatibility considerations, and known limitations. It leaves the linked issue …
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

Docstring coverage is 60.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 5 functions across 15 files. (7 skipped: 7 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant