Skip to content

feat(orchestration): let an agent steer its message into a busy chat instead of queueing it - #26110

Closed
brennanb2025 wants to merge 6 commits into
mainfrom
brennanb2025/agent-message-send-now
Closed

brennanb2025 wants to merge 6 commits into
mainfrom
brennanb2025/agent-message-send-now

Conversation

@brennanb2025

@brennanb2025 brennanb2025 commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 12 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​952 $\color{#cf222e}{\Huge{\mathbf{−}}}$​19 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​933
Prod 39 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​503 $\color{#cf222e}{\Huge{\mathbf{−}}}$​154 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​349

ELI5

When one agent sends another agent a message (or a task) and the receiving chat is busy, Orca holds it as a queued card until the chat's current turn ends. That is the right default, but sometimes the sender knows it is urgent ("stop, you're on the wrong branch"). This PR lets the sending agent choose: keep queueing (default), or --delivery steer, which puts it into the running turn the same way a person's message goes in when they send while the agent works with queueing off.

What Changed

Before: orca orchestration send and orca orchestration dispatch into a busy chat always became a queued card. A coordinator correcting a worker mid-task waited for the worker's whole turn to end. The worker guide even says the coordinator's follow-up "does not interrupt you".

After (what the person sees):

  • Default unchanged: an agent's message to a busy chat is still a queued card (Steer / Delete).
  • With --delivery steer, a busy chat takes it into the running turn instead: no card, it shows mid-turn as "Message from ", exactly like a person's send with queueing off.
  • While the chat is waiting on an approval or a question, a steer waits for the person's answer and then joins the turn, the same rule the card's own Steer button follows. A dispatch, which can't wait, becomes a card and says so.
  • Idle chats: no difference. Terminal agents: no difference (see Notes).

Mechanism:

  • No new send path. Both choices go through the one existing send, sendAgentTurn: queue is the composer's queued send, steer is its plain send (the composer's send with queueing off), which the chat host already steers into a running turn.
  • dispatch passes the choice straight through and its result now reports how the task was handed over (sent, still starting, queued).
  • send is asynchronous (the message is stored, then a short "you have N messages" notice is sent into the chat, batched and retried), so the sender's choice is stored on the message row (messages.busy_delivery, schema v44, default queue). Each notice works out its delivery from the messages it points at: steer if any of them asked for it. Nothing is latched; the choice goes away with the delivered rows.
  • Two small fixes the steer needs to actually arrive promptly:
    • mail that arrives while an earlier notice to the same chat is still being sent is now retried as soon as that send ends, instead of waiting for the chat to go idle;
    • chats that were not started as workers (for example a coordinator chat) now retry parked mail on the transition from waiting for a person to working, as well as when they go idle, so a steer held for an approval goes in once the approval is answered. Streaming updates do not trigger this retry.

CLI: --delivery <queue|steer> on orchestration send and orchestration dispatch --inject only. --type heartbeat with steer is refused (it would interrupt the coordinator on every beat), and --delivery on a dispatch without --inject is refused (nothing is sent to the chat then).

Side effects on the default (queue) path, named so they're not a surprise:

  • because of the two retry fixes above, queued mail can become its card a little sooner (when an earlier notice finishes sending, or when an approval is answered) instead of waiting for the chat to go idle. The end result is the same card.
  • a dispatch to a chat now prints how the task was handed over: "(task sent)", "(task handed to the chat; not taken yet)" or "(task queued in the chat)".
  • a steered dispatch returns as soon as the chat has it, without waiting for the agent to take it. If that turn then ends without taking it (for example the person presses Stop), the task is dropped and the coordinator isn't told; the guide tells coordinators to check on a worker that never reports.

Why

The common pattern gives the sender a delivery choice on each message. This change uses that choice and reuses Orca's existing composer send, while keeping queue as Orca's default. Differences in prompt handling and durable mail are listed below. Alternatives considered:

  • Reusing --priority urgent to mean "steer": rejected, it mixes how important a message is with how it is delivered and would silently change what existing urgent senders get.
  • Keeping the choice in memory instead of on the message row: rejected, it is lost on any parked retry, restart or batch re-selection, and the chat host rejects a retried send whose delivery changed.
  • A new host-side "steer unless a prompt is open" mode: rejected, it changes the chat send wire format; the orchestration side already reads the same fact and can wait.

Differences from the common pattern:

  • Default is queue, not steer (intended): keeps the person in control of what lands mid-turn.
  • The chat gets a notice, not the message text (intended, unchanged): Orca's mail is durable and shared with terminal agents; that is why the choice lives on the message.
  • send doesn't report "steered" vs "queued" (intended): it returns once the message is stored; the notice is sent afterwards. dispatch does report it.
  • Open prompts are checked by orchestration before the shared send (intended): mail waits in the existing retry path and tries again after the person answers; a synchronous dispatch becomes a queued card. This reuses the existing message and queue contracts. The separate status read can race a newly opened prompt.
  • No "restart" mode (intended): Orca has no composer equivalent to reuse; the stored value takes more modes later without a table rebuild.
  • A queued notice card already waiting when a steer arrives still sends later (intended): it costs one extra turn that says there is mail; removing it would mean special handling for agent cards, which the queue deliberately doesn't have.
  • In a chat whose agent runs one prompt at a time, a steer ends the current turn and the message runs next (intended, same as a person's own Send now there; these chats are behind the "updated structured native chat" setting).
  • Steer isn't carried to a Dispatch on another Orca server yet (temporary): the message still arrives, queued, and send returns a delivery_not_relayed warning. Follow-up: carry the choice in the cross-server mail payload.

Linked Issue

Follow-up to #25078 (agent messages use the chat's queue) and #22972.

Visual Proof

QA at pushed c61186876c1 used a hidden local Electron renderer, normal orchestration CLI calls and a stand-in Codex adapter. The frames below are full, uncropped renderer screenshots. Stub records and copied raw session journals corroborate delivery timing.

Default queue: the agent notice stays in a card with Steer/Delete while the recipient works, then starts a separate turn after completion.

Default queue during the running turn

Send now: the notice appears in the running turn without a queued card. This test used a placeholder sender, so its attribution is the generic fallback, “Message from an agent”.

Send now during the running turn

Waiting for approval: no notice is injected while the approval is open. After Allow, it joins the same turn; the stub recorded the join 8 ms after the answer. This sender also used the generic fallback.

Steer held while approval is open

Steer joins the continuing turn after approval

Send now for a task from a native chat: a genuine sender named “Codex Chat” dispatches into the busy recipient. Its caption is visible with the running status and Stop button, without a queued card. Accepted journal records and the stand-in adapter show the task joined the same turn, with no extra turn afterward.

Named task sender visible during the running turn

Testing

  • Unit and rig tests: the notice lane (steer batch, queue batch, held while an approval is open then steered, retried after an in-flight send, an earlier queued card plus a later steer), dispatch (steer, prompt fallback to a card, result), send params and stored rows (single and group, heartbeat refused, cross-server warning), schema v44 migration, CLI flags, SSH bridge.

  • Both CI failures were reproduced on the PR with their fixes removed, and both original tests passed on main (80d45095d2f). The migration fixture now runs in the required Node project; the runtime-wiring test verifies the prior approval status reaches mail redrive.

  • Before the main merge: 85 selected files / 837 tests passed, including the real database downgrade/write/reopen test. At final head c61186876c1: the narrow merge batch passed 14 files / 119 tests.

  • CI passed on c61186876c1: all five full unit-test shards, relay integration, static analysis and typecheck, packaging and verification.

  • Node, CLI and web typechecks passed sequentially through the machine-wide gate after clearing old build-info. Required final static checks: 21/22 passed. The full type-aware audit did not run because the machine queue timed out; changed-file type-aware quality passed. CI static analysis and typecheck passed, covering the full audit.

  • Hidden Electron QA on local macOS verified default queue, send now, holding mail through an approval, idle delivery, invalid delivery/heartbeat/non-injected dispatch refusals, and busy task dispatch with a visibly named sender. The adapter was a stand-in; real providers, executing the dispatched task, mobile, Windows/WSL, live SSH and cross-server delivery were not exercised.

  • The QA CLI used an esbuild emission of clean c61186876c1, invoked by absolute path only. The product build uses TypeScript emission; types were checked separately locally and in CI. This was not a test of the shipped CLI artifact.

  • The rig used isolated homes/profile, mock keychain and guarded stand-in providers. Shared live-app configuration changed during the restart; all real-configuration observations after the last clean boundary at 21:35:56 UTC are excluded as evidence of isolation.

  • I manually tested these changes locally (hidden renderer through CDP, with a stand-in adapter)

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

Review

Two independent review rounds converged; the final repair and main integration were checked separately. The readiness review found no P0/P1 issues; the remaining limitations are listed above and in the completion comment. The one-prompt-at-a-time Send now behavior is an accepted product choice.

Agent skill upstream boundary

  • Not applicable, or this change follows docs/reference/agent-skill-sharing-upstream-boundary.md and copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.

Notes

  • Terminals: mail to a terminal agent is still typed only when it goes idle, or taken at once by check --wait; typing into a busy agent terminal would race whatever the person is typing there. A dispatch to a terminal is already typed at once, so the choice changes nothing there.
  • Mixed versions: delivery is a new optional parameter. An older Orca ignores it, so the message still arrives, queued as today. Nothing the host publishes changes shape; no protocol bump.
  • SSH: the SSH bridge passes --delivery through on send.
  • Mobile: nothing needed; a steered message is an ordinary message the phone already renders.

X / Twitter: @BrennanKB5

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)

…l only on an answered prompt

- redrive mail parked on a chat on the prompt-answered edge only, never per streamed status frame
- a dispatch steered into a running turn reports its hand-off instead of waiting out a start
- stamp an accepted send before holding a steer for an open prompt; merge rerun reserved types
- refuse dispatch --delivery without --inject; unknown dispatch delivery arms print nothing
- separate, accurate send and dispatch notes and guide text
… say a steered task can still be dropped

- the answered-prompt edge also re-derives the chat's mailboxes, so an attempt in flight on a
  read from before the answer is rerun instead of waiting for the turn to end
- dispatch note: --delivery (either value) needs --inject; a task handed to the chat but not
  taken yet can still be dropped if that turn ends first, so check on a worker that never reports
@brennanb2025

Copy link
Copy Markdown
Contributor Author

A message or task sent by an agent to a busy native chat previously waited until the running turn ended. This PR adds --delivery steer to orchestration send and orchestration dispatch --inject, using the same send function and busy-chat behavior as the person's composer. Queue remains the default. Mail held for an approval or question retries after the person answers; a dispatch in that state becomes a queued card and reports that result.

The two CI regressions were caused by this PR: the new real-SQLite migration test was missing from the Node-runtime test list, and the production-wiring test still expected the old one-argument callback. The fixes put the migration fixture in its required runtime and check that the retry receives the prior status needed to detect an answered prompt. Both original tests were reproduced on the PR and passed on main (80d45095d2f). At pushed head c61186876c1, the final focused batch passed 14 files / 119 tests; the earlier selected batches passed 85 files / 837 tests. Node, CLI and web typechecks passed. Final local static checks passed 21/22: the full type-aware audit was not run, the machine queue timed out; changed-file type-aware quality passed. CI passed all five full unit-test shards, relay integration, static analysis/typecheck, packaging and verification, covering the missing local audit.

The reviewed limitations remain explicit: a steered dispatch can be dropped before the chat takes it, leaving the coordinator to check on a worker that never reports; a notice queued before a steer can later cause an extra turn; an older runtime ignores the optional delivery choice and queues it; cross-server mail does not yet carry steer and returns a warning. The approval/question state is read before sending, so a newly opened prompt can race that read; this remains deferred. Queue-mode notices can now appear sooner after an in-flight send or an answered prompt. For agents that run one prompt at a time, send-now stops the current turn and runs the message next, matching the person's Send now; that product choice is accepted.

Hidden Electron QA on local macOS verified default queue, send now during a turn, mail held through an approval and delivered after the answer, idle delivery, invalid delivery/heartbeat/non-injected dispatch refusals, and busy task dispatch from a genuine native-chat sender. Full screenshots are embedded in the PR body, including the named sender caption beside the running status and Stop button. Copied raw journals and adapter records confirm same-turn delivery with no extra turn. The adapter was a stand-in and the QA CLI was an esbuild emission of the clean head, rather than the shipped TypeScript-emitted artifact. Real providers, executing the dispatched task, mobile, Windows/WSL, live SSH and cross-server delivery were not tested. Shared live-app configuration changed during the restart; real-configuration observations after 21:35:56 UTC are excluded as isolation evidence. No merge or auto-merge was performed.

@brennanb2025
brennanb2025 marked this pull request as ready for review October 7, 2026 22:24
@coderabbitai

coderabbitai Bot commented Oct 7, 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: 6c919b40-eec8-4e0a-bb4b-8d3f6d6d044c
📥 Commits

Reviewing files that changed from the base of the PR and between 80d4509 and c611868.

📒 Files selected for processing (51)
  • config/scripts/vitest-sqlite-database-runtime-files.mjs
  • skill-guides/orchestration/references/messaging-and-gates.md
  • skill-guides/orchestration/references/worker-contract.md
  • src/cli/bundled-skill-guides.ts
  • src/cli/handlers/orchestration/busy-delivery-flag.ts
  • src/cli/handlers/orchestration/dispatch-handlers.ts
  • src/cli/handlers/orchestration/message-send-handler.ts
  • src/cli/index-orchestration.test.ts
  • src/cli/specs/orchestration-message-specs.ts
  • src/cli/specs/orchestration.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-status-feed.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-status-observation.ts
  • src/main/runtime/orca-runtime-get-pty-record-for-pane-key.ts
  • src/main/runtime/orca-runtime-get-worktree-ps.ts
  • src/main/runtime/orchestration/agent-facing-parity.test.ts
  • src/main/runtime/orchestration/db/contract-constants.ts
  • src/main/runtime/orchestration/db/messages/message-insert.ts
  • src/main/runtime/orchestration/db/schema/busy-delivery-migration.test.ts
  • src/main/runtime/orchestration/db/schema/create-core-tables-sql.ts
  • src/main/runtime/orchestration/db/schema/migrate-v44.ts
  • src/main/runtime/orchestration/db/schema/migrate.ts
  • src/main/runtime/orchestration/orchestration-schema-version-skew.ts
  • src/main/runtime/orchestration/send-agent-turn.test.ts
  • src/main/runtime/orchestration/send-agent-turn.ts
  • src/main/runtime/orchestration/structured-mailbox-pointer-delivery.test.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/orchestration/structured-session-mail-target.ts
  • src/main/runtime/orchestration/structured-session-pointer-delivery.ts
  • src/main/runtime/orchestration/types.ts
  • src/main/runtime/rpc/methods/orchestration/chat-task-delivery.ts
  • src/main/runtime/rpc/methods/orchestration/federation/federation-control-mail.test.ts
  • src/main/runtime/rpc/methods/orchestration/messaging/federated-steer-warning.ts
  • src/main/runtime/rpc/methods/orchestration/messaging/mailbox-message-receipt.ts
  • src/main/runtime/rpc/methods/orchestration/messaging/recipient-routing.ts
  • src/main/runtime/rpc/methods/orchestration/messaging/send-busy-delivery.test.ts
  • src/main/runtime/rpc/methods/orchestration/messaging/send-group.ts
  • src/main/runtime/rpc/methods/orchestration/messaging/send-methods.ts
  • src/main/runtime/rpc/methods/orchestration/messaging/send-point-to-point.ts
  • src/main/runtime/rpc/methods/orchestration/messaging/send-remote.ts
  • src/main/runtime/rpc/methods/orchestration/runs/dispatch-methods.ts
  • src/main/runtime/rpc/methods/orchestration/schemas.ts
  • src/main/runtime/structured-chat-coordinator-mail-rig.test-fixture.ts
  • src/main/runtime/structured-chat-steer-delivery.test.ts
  • src/main/runtime/structured-session-mail-redrive-wiring.test.ts
  • src/main/runtime/structured-session-mail-status-edge.test.ts
  • src/main/ssh/ssh-remote-orca-cli.test.ts
  • src/main/ssh/ssh-remote-orca-cli.ts
  • src/shared/orchestration-busy-delivery.ts
  • src/shared/rpc-contract/orchestration-params.ts

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


📝 Walkthrough

Walkthrough

The change adds queue and steer delivery options to orchestration messages and injected chat-task dispatches. The runtime stores the selected mode and applies it to structured-session delivery, including human-gate and in-flight-send handling. Status transitions now trigger mail redrive on specified edges. The CLI, SSH fallback, command guidance, and tests are updated to cover the new options and behavior.

Priority: ⬇️ Low

Merge Risk: ⚪ Minimal · up to c6118

No issue established in this review needs resolution before merging; normal checks still apply.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 52.78% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 36 functions across 48 files. (2 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: allowing agents to steer messages into busy chats instead of queueing them.
Description check ✅ Passed The description is comprehensive and follows the template. It explains the user-visible behavior, implementation, rationale, linked issues, visual proof, testing, limitations, compatibility considerat…
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 52.78% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 36 functions across 48 files. (2 skipped: 2 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.

@brennanb2025

Copy link
Copy Markdown
Contributor Author

Holding this PR and moving it back to draft.

This PR lets an agent send a message to a busy chat with "send now", so it joins the running turn instead of waiting in the queue. We can't guarantee that behavior across all the agents Orca runs. Native chat can steer a running turn, but sending to a terminal (CLI) agent doesn't interrupt it. Depending on the CLI, the message can just sit queued in that agent's own input until its turn ends; Grok, for example, does this.

So --delivery now would promise something Orca can't enforce for many targets, and the sender would think its message landed in the running turn when it didn't. Parking it until we have a way to make "send now" mean the same thing everywhere, or a clear rule for what happens when the target can't do it.

@brennanb2025

Copy link
Copy Markdown
Contributor Author

Closing: superseded by #28540, which gives native-chat agents their mail notice right away the same way for every agent.

@brennanb2025
brennanb2025 deleted the brennanb2025/agent-message-send-now branch October 19, 2026 20:59
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