Skip to content

native chat: /clear sent while the agent works waits in line (rebuild of #26730) - #27928

Closed
brennanb2025 wants to merge 4 commits into
mainfrom
brennanb2025/clear-waits-v2
Closed

brennanb2025 wants to merge 4 commits into
mainfrom
brennanb2025/clear-waits-v2

Conversation

@brennanb2025

@brennanb2025 brennanb2025 commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 5 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​600 $\color{#cf222e}{\Huge{\mathbf{−}}}$​43 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​557
Prod 19 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​377 $\color{#cf222e}{\Huge{\mathbf{−}}}$​110 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​267

ELI5

In native chat, typing /clear while the agent is still working is refused today: "The agent is still working. Run /clear when it's done." Nothing is kept, so you have to remember to come back and type it again.

With this change, /clear waits in line like any other message you send. It shows up as a "/clear" card in the queue above the composer. When its turn comes, Orca clears the chat's context itself, in the same chat and tab, and anything you queued after the /clear then runs in the fresh context, in the order you sent it.

This supersedes #26730, which built the same feature with a much larger "wait for background tasks" machine. This rebuild is about a fifth of its production size (see "Why this is smaller" below). #26730 stays open until we decide to close it.

What Changed

What you see (desktop and phone)

Situation Before After
/clear while a reply is running Refused under the composer; nothing queued A "/clear" card joins the queue. It uses the existing command-card look of a queued /compact: Delete, no Edit, never steers into the running reply, no Send while the agent works
The reply ends and nothing else is running — The card disappears and the existing "Context cleared" divider appears in the same chat. The cards queued after it then send one by one into the fresh context
The reply ends but background tasks are still running — The card comes back with the reason already used for a refused /clear: "Background tasks are still running. Wait for the background tasks to finish." Cards behind it wait ("a message ahead needs attention", existing caption). You stop the tasks from the task strip and press Send on the card, which clears; or press Delete, and the cards behind it run without the clear
Stop while the card waits — The queue pauses as usual; Resume runs the clear, then the rest
Quit and reopen with the card waiting — The card is held, as every card is after a reopen. Send on it runs the clear and lets the cards behind it continue
After Stop or a reopen, you send a new message while the card is held — Your new message goes first (the existing "the person acting now wins" rule for every held card), then the held /clear runs and clears that exchange too
/clear while idle Clears at once (or is refused when background tasks run) Unchanged

No new strings, notices, captions or controls. Every word shown is one main already uses.

How it works (all on the execution host, so local, SSH and WSL behave the same)

  • Accepting it. The /clear request now asks the queue first, through the same accept rule a queued /compact uses. If the agent is working, the request becomes a command card ({ text: '/clear', command: { name: 'clear' } }, the existing card shape) and answers "queued". Otherwise the immediate clear runs exactly as on main.
  • Running it. When the queue's drain reaches a /clear card, it never hands the card to the agent. It runs main's existing clear on the session's lane: check that a clear is allowed, stop the agent, check again, then commit. Send on a held /clear card runs the same code. That code moved out of structured-conversation-command.ts into structured-conversation-clear.ts so the request, the drain and Send share it.
  • What the clear touches. The clear's single write (divider, record boundary, queue bookkeeping) now also withdraws the /clear card, and rolls back entirely if the card is gone, so a card never runs twice and never disappears without its clear. Only cards queued before the /clear card are treated as old context (main's existing pause and withdrawal); cards after it are left alone, so they drain into the fresh context. A clear run from its card also lifts the queue's pause the way a sent turn would, so the cards behind it continue; a typed /clear still never lifts your Stop, as on main.
  • When it's refused. If the clear isn't allowed at its turn (in practice, background tasks still running), the card moves to "returned" with the refusal, using a new table transition (waiting → returned, without a sent message). A returned card already holds the cards behind it until you press Send or Delete.
  • Clients. Desktop and phone each drop one command === 'compact' condition, so /clear asks to wait in line under exactly the same gate as /compact. Card rendering is unchanged: a /clear card is an ordinary command card.

Wire compatibility. No new capability, method or published field. Clients send delivery: 'queue-if-active' with /clear only when the host advertises both agent-session.queued-commands.v1 and agent-session.queued-messages.v1, the same gate /compact uses. queued-messages.v1 is still dark (no shipped host advertises it), so an older host never receives delivery on /clear, and an older client never sends it. Ordering rule: this PR must land before queued-messages.v1 is turned on. A host advertising it without this change would refuse a newer client's /clear. The rule is written beside the queued-messages.v1 note in protocol-version.ts.

Why

  • Queueing /clear is an Orca product choice. The common pattern refuses a context clear while the agent is busy, or has no in-place clear at all.

  • Which parts follow the common pattern:

    • The host owns the queue and drains it in order. This matches.
    • Work queued after a context change runs in the fresh context. This matches.
    • Intended difference: the host runs the /clear card itself when its turn comes. It's the smallest way to give the queue a context change, and it reuses main's clear unchanged.
  • Return, not wait. At the card's turn, the common pattern doesn't make the queue wait for background work. When the clear is refused, this rebuild returns the card rather than holding it in a new waiting state.

  • Why this is smaller than Native chat: /clear waits in line while the agent works #26730. Native chat: /clear waits in line while the agent works #26730 was +1,087/−339 production lines across 53 files. This is +377/−110 raw across 19 files, about 210 net code lines, four of them comment-only. These are gone:

    • the background-task wait and the plumbing that published it to clients;
    • its wake-ups (child-work changes, lease hand-off listeners, Claude stop-ending tracking);
    • the new capability agent-session.queued-clear.v1;
    • the client Send and caption rules, and six locale strings.

    What's left is what has to exist: accept as a card, run on the drain or on Send, scope the clear to the cards before it, and return on refusal.

Differences, labelled:

  • Temporary: the /clear request (immediate or queued) still records its acceptance in the operation ledger. /compact has moved to the newer command receipts (native chat: queued-message actions and question answers accept through their own receipt #26988). Moving /clear today would put two record-store receipts in one write, which loses the first. Follow-up: move it when the ledger co-write marked TEMPORARY in structured-agent-session-command-receipt.ts is retired.
  • Temporary: images never queue (queued messages are text-only), so an image sent while a /clear card waits reaches the agent first and is then cleared. Follow-up: images join the queue.
  • Intended: a refused /clear at its turn uses the same refusal rule as an immediate /clear. That keeps one rule for both paths. Whether /clear may run beside background tasks is a separate decision for both paths.
  • Intended (open question for the product owner): after a Stop or a reopen, a message you send runs before the held /clear. It's the same rule as every held card. In live QA, the existing "Send message?" dialog didn't tell the testers that the held item is a /clear that will clear their new exchange. A possible follow-up is to name the held /clear in that dialog.

Linked Issue

N/A, maintainer follow-up. Supersedes #26730 (which superseded #25842).

Visual Proof

Before (main): /clear while the agent works is refused with "The agent is still working. Run /clear when it's done." under the composer, and nothing is kept (unchanged on main; no new UI here, so no before-shot was taken).

After, real Claude and real Codex on a hidden rig. The queued-messages feature was turned on with a local, uncommitted edit, since it's still off by default.

/clear sent mid-turn waits as a card (Claude) Its turn: one "Context cleared" divider in the same tab; the follow-ups run with no memory (Claude)
mid-turn clear card cleared, follow-ups run
Same with Codex Background task at its turn: the card comes back; the cards behind it wait
codex cleared returned clear card
Quit and reopen, then Send on the held card: cleared, and both follow-ups drain After Stop, a new message runs first (existing dialog), then the held /clear clears it too
reopen then send send message dialog

Testing

  • New host suite structured-agent-session-queued-clear.test.ts (20 cases). It covers:
    • a mid-turn card; the clear at its turn (one divider, same chat, no message to the agent); cards behind it draining in order into a fresh provider context;
    • background tasks at its turn (returned, cards behind it held, a new send not trapped); Send after the tasks end; Delete;
    • Send mid-turn refused; Stop then Resume; reopen then Send (it never runs twice, and the cards behind it continue);
    • an injected commit failure (rolls back whole, card returned, Send retries);
    • a resent request; an id that cleared at once and is resent mid-turn; Send over a paused queue (only cards before the clear are paused);
    • a typed /clear with a held clear card (spends only that card and keeps your Stop);
    • Send after a failed receipt write; idle /clear with and without background tasks (unchanged from main).
  • Updated: structured-agent-session-queued-compact.test.ts (a /clear asking to wait is now a card), the desktop structured-conversation-command-send.test.ts, and the phone mobile-structured-composer-command.test.ts.
  • Run locally on the final tree:
    • the host, journal and queue suites that touch this path (231 tests across 14 suites after the last fix, and over 600 across 48 suites the round before), plus desktop (20) and phone (15);
    • node, web and mobile typechecks, and the mobile test-typecheck ratchet;
    • oxlint, check:code-quality:changed, and orca-ci-checks.
  • orca-ci-checks: 22 of 24 checks pass. The two failures don't come from this branch: the local bun typecheck:ci step fails with "Script not found" (the regular pnpm run typecheck passes), and the mobile no-cycle warnings are in files this branch doesn't touch.
  • Live QA with real Claude and Codex: desktop rows on 8fab421118a, with both real Claude and real Codex:
Row Claude Codex
Mid-turn /clear becomes a card; at its turn, one divider in the same tab, follow-ups in order, no memory PASS PASS
Background task at its turn: card returned; stop the task, then Send clears and the follow-ups run PASS PASS
A new message while the returned card waits goes to the agent at once PASS PASS
Delete the returned card: follow-ups run without the clear PASS PASS
Stop while the card waits pauses the queue; Resume runs the clear, then the follow-ups PASS PASS
Quit and reopen: nothing runs by itself; Send clears, the follow-ups drain, and the agent has no memory PASS PASS
Idle /clear is immediate; idle /clear with a background task is refused (as on main) PASS PASS
After Stop, a new message runs, then the held /clear clears it (intended; see below) observed observed
Phone BLOCKED: the shared iOS simulator helper was held by another lane —
SSH not run —
  • Final head be9b737f368 differs from the tested head only in round 2 and 3 fixes. None of them are on these rows' paths, and unit tests cover them.

  • Real ~/.codex and ~/.claude config hashes were unchanged throughout.

  • QA findings, all P3:

    • the returned card says "Wait…" though it won't run by itself (the remedy is Stop, then Send);
    • its reason still says tasks are running after you stop them, until Send;
    • after a reopen the held cards show no paused line (not checked on main).
  • CI on be9b737f368: green.

  • I manually tested these changes locally

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

Review

  • Plan before code. An architect planned it first from the problem and current main, then sorted every mechanism in Native chat: /clear waits in line while the agent works #26730 into keep, simplify or drop.
  • Round 1: reviewers covered host correctness, design and simplicity, clients and wire compatibility, plus an architecture challenger. The challenger's own independent design matched this one choice for choice. Fixed:
    • Send on a held /clear card lost the fresh context, because two record writes were composed into one;
    • two accept plans with a guard became one plan;
    • a typed /clear, or Send on a held card, could strand the cards behind it.
  • Round 2: reviewed only the fixes. It found no P0–P2 issues. Fixed: two P3s from the round-1 fixes, and a comment.
  • CI caught one more (fixed in be9b737f368): a /clear pressed while this window's own earlier message is still being sent had become silent. It now says "Your earlier message is still being sent. Run /clear once it has gone." again, as on main.
  • Readiness review on the final diff: no P0 or P1. The P2s are the two items labelled above (image sends skip the queue; the ordering rule for queued-messages.v1).
  • Deferred P3s:
    • a rolled-back clear bumps a counter early, causing at worst one extra republish;
    • a very short window where a send from another window during a queued /clear accept is refused instead of queued (not reproduced);
    • a stale Send on a card a typed /clear just spent answers "withdrawn".

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

  • SSH and remote: the queue, the drain and the clear all run on the execution host. Clients only send delivery and render cards.
  • Folder workspaces: nothing here depends on git or on paths.
  • Mobile: one condition was removed. The mobile test-typecheck ratchet passes.

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)

…t when refused

A context clear run from a /clear card withdraws that card in the same
transaction (rolling back if the card is gone) and leaves the cards
queued after it alone. An immediate clear that finds an unsettled
/clear card is that card's clear and cuts the queue at it the same way.
A clear run from its card (the drain or its Send) writes a Resume row
before the clear's pause, so a Stop or reopen pause lifts as it does for
a turn, while the cards ahead of it stay held as the old context's; a
typed /clear lifts no pause, as on main. A card the host runs itself
can be returned with its refusal without a hand-off.
The /clear RPC stays one ledger plan: its run asks the queue's accept
rule first, as /compact does, and answers with the card; otherwise it
clears at once. When the drain reaches a /clear card, or the person
presses its Send, the host runs the same clear on the session's lane
instead of handing it to the agent, and commits the clear's record
receipt alone; a Send records its own receipt after it. Refused at its
turn, the card is returned with the refusal and the cards behind it
hold until Send or Delete.
…n line

Both clients now ask the host to hold a /clear sent while the agent
works as a card, under the same gate as /compact. Card rendering is
unchanged: a /clear card is a command card like a queued /compact.
…says it is being sent

Desktop now lets /clear wait in line like /compact, which moved a /clear
pressed while this window's own message is still on its way onto
/compact's quiet hold (nothing said, text kept). /clear keeps main's
words there: "Your earlier message is still being sent. Run /clear once
it has gone." The mid-turn gating test now expects /clear to ask the
host to wait.
@brennanb2025

Copy link
Copy Markdown
Contributor Author

Review loop status (head be9b737f368, draft; not marked ready)

Problem. In native chat, /clear sent while the agent works is refused ("The agent is still working. Run /clear when it's done."), and you have to retype it later.

User-facing change. /clear now waits in line as a card. When its turn comes, the host clears the context in the same chat, and anything queued after it runs in the fresh context, in order. If background tasks are still running at its turn, the card comes back; you stop them and press Send, or press Delete.

Fixed during review:

  • Round 1 (P1): Send on a held /clear card showed "Context cleared", but the agent kept its old memory.
  • Round 1 (P2s): two accept plans plus a ledger guard became one plan. A typed /clear, or Send on a held card, could strand the cards behind it.
  • Round 2 (P3s): a retry answer after a failed receipt write; a typed /clear no longer lifts your Stop, as on main.
  • CI: a /clear behind this window's own unsent message had become silent; main's message is back.

Deferred:

  • Temporary: image sends skip the queue, so an image sent while a /clear card waits is cleared afterwards. Follow-up: images join the queue.
  • Temporary: /clear still accepts through the operation ledger until main retires its ledger co-write.
  • Product question: after Stop, a new message runs before the held /clear. The existing "Send message?" dialog doesn't say the held item is a /clear.
  • P3s:
    • the returned card's "Wait…" wording, though the card won't run by itself;
    • that reason goes stale after you stop the tasks;
    • no paused line after a reopen (may be pre-existing);
    • a rolled-back clear bumps a counter early;
    • a short race where a send from another window during a queued /clear accept is refused (not reproduced).

Verified:

  • Unit and integration tests (new 20-case host suite, plus desktop and phone).
  • Node, web and mobile typechecks; lint; orca-ci-checks. Its two local failures don't come from this branch.
  • CI green.
  • Live desktop QA with real Claude and real Codex: every row passed.

Not verified:

  • Phone live QA: blocked because the shared iOS simulator helper was held by another lane.
  • SSH.
  • The final two fixes live; unit tests cover them.

@brennanb2025

Copy link
Copy Markdown
Contributor Author

Closing: we're keeping main's refusal for /clear while the agent works. #28101 records the reason in the code.

@brennanb2025
brennanb2025 deleted the brennanb2025/clear-waits-v2 branch October 12, 2026 23:00
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