feat(orchestration): persist run-next messages server-side across providers - #7240
feat(orchestration): persist run-next messages server-side across providers#7240DavidIlie wants to merge 4 commits into
Conversation
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
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. Comment |
There was a problem hiding this comment.
One consistency finding in the new web queued-message affordance in apps/web/src/components/chat/MessagesTimeline.tsx. See the inline comment.
Posted via Macroscope — UI Consistency
| command, | ||
| threadId: command.threadId, | ||
| }); | ||
| const message = thread.messages.find((entry) => entry.id === command.messageId); |
There was a problem hiding this comment.
🟠 High orchestration/decider.ts:1085
thread.queued-turn.dispatch permanently blocks FIFO processing when the oldest queued message has been evicted from thread.messages: the invariant fails, the durable queue row remains queued, and every subsequent dispatch selects the same row. Validate and remove the turn using the durable queue record rather than the capped message snapshot; thread.queued-turn.cancel needs the same treatment for evicted rows.
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/orchestration/decider.ts around line 1085:
`thread.queued-turn.dispatch` permanently blocks FIFO processing when the oldest queued message has been evicted from `thread.messages`: the invariant fails, the durable queue row remains `queued`, and every subsequent dispatch selects the same row. Validate and remove the turn using the durable queue record rather than the capped message snapshot; `thread.queued-turn.cancel` needs the same treatment for evicted rows.
There was a problem hiding this comment.
Fixed in 64c33af. The command read model now hydrates every message whose projected delivery_state is queued while still omitting ordinary message history. Dispatch/cancel invariants therefore retain durable queue entries regardless of the UI detail window, and the query behavior has focused coverage.
There was a problem hiding this comment.
Sorry, I'm unable to act on this request because you do not have permissions within this repository.
There was a problem hiding this comment.
Reviewed the new Effect service introduced by this PR (ProjectionQueuedTurnRepository) and the touched service consumers. Two convention violations in the new service module layout; the rest of the changes (namespace imports, yield* Foo.Foo dependency acquisition in ProviderCommandReactor/ProjectionPipeline, no new runtimes or errors) look consistent with the conventions.
Posted via Macroscope — Effect Service Conventions
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 8e01fce. Configure here.
ApprovabilityVerdict: Needs human review 1 blocking correctness issue found. This PR introduces a significant new feature for durable server-side message queuing with new database schema, event types, commands, and UI components. New capabilities of this scope warrant human review. An unresolved High severity finding about potential queue blocking also requires attention. You can customize Macroscope's approvability policy. Learn more. |
| command, | ||
| threadId: command.threadId, | ||
| }); | ||
| const message = thread.messages.find((entry) => entry.id === command.messageId); |
There was a problem hiding this comment.
Sorry, I'm unable to act on this request because you do not have permissions within this repository.

What?
Adds a mobile Steer active turns setting. When it is off, follow-up messages are stored on the T3 server and run after the active turn finishes instead of waiting in the mobile app.
Queued messages are visible and cancellable from both mobile and web, and the existing immediate steering behavior remains the default.
Why?
Mobile's local outbox previously could not hand a follow-up to the server until the current turn stopped. The app had to remain open long enough to observe that stop and send the next message, so backgrounding or disconnecting the phone could leave the follow-up stranded locally.
How?
The turn-start contract now distinguishes immediate delivery from
after-currentdelivery. The server persists queued turn intent in the event log and a dedicated projection before the provider boundary, so the behavior applies uniformly to Codex, Claude Code, Cursor, Grok, and OpenCode.The provider reactor dispatches the oldest queued message when the thread becomes ready. The projection tracks
queuedandhandoffstates: pre-handoff work resumes after a server restart, while a restart during the external provider handoff is reported as interrupted instead of replayed because provider CLIs do not share a durable idempotency key with orchestration. That avoids executing the same coding instruction twice.Command reads always retain durable queued messages even when ordinary message history is windowed. Shell snapshots expose queue presence for settle and snooze decisions, and reverting a thread cancels both the queue record and its visible message.
Mobile waits for its saved preference before accepting a send, snapshots the selected delivery mode into its offline outbox, and sends online messages to the server immediately. Both mobile and web project the durable queue state and expose cancellation before provider handoff.
Verification
pnpm --filter @t3tools/contracts typecheckpnpm --filter @t3tools/client-runtime typecheckpnpm --filter t3 typecheckpnpm --filter @t3tools/mobile typecheckpnpm --filter @t3tools/web typecheckgit diff --checkThe in-app preview authenticated and navigated to the isolated fixture, but its screenshot/evaluate broker timed out repeatedly, so no visual artifact is attached.
Generated with GPT-5.6 Sol via the Codex harness.
Note
Persist queued turn messages server-side with lifecycle management and cancellation UI
deliveryMode: 'after-current'option tothread.turn.startso messages sent during an active session are queued server-side instead of starting a new turn immediately.projection_thread_turn_queuetable (migration 041) and aProjectionQueuedTurnRepositoryto persist queued turns withqueued→handoff→ deleted status transitions.deliveryState='queued'on enqueue, clearing it on dispatch, and deleting the message on cancellation.handoffis treated as ambiguous and transitions the thread session toerrorwith an activity recorded, preventing duplicate dispatch.cancelQueuedTurncommand on both web and mobile UIs; queued user messages show a 'Queued' label and a Cancel button in the message feed.steerActiveTurnspreference on mobile (default on); when off, messages sent during active work useafter-currentdelivery mode.canSettleandhasQueuedTurnStartnow block thread settling whenhasQueuedTurnsis true on the shell snapshot.handoffat server restart will be set to error state and require the user to retry.Macroscope summarized 6632d44.
Note
High Risk
Touches orchestration turn delivery, durable queue/handoff recovery, and provider dispatch—incorrect replay or dispatch could double-run agent work or strand messages.
Overview
Introduces
deliveryMode: 'after-current'onthread.turn.startso follow-up user messages are persisted on the server (event log +projection_thread_turn_queue) instead of starting the provider immediately. The decider emitsthread.turn-queued; dispatch/cancel paths addthread.queued-turn-dispatchedandthread.queued-turn-cancelled, with messages taggeddeliveryState: 'queued'and shells exposinghasQueuedTurns.ProviderCommandReactordispatches the oldest queued turn when a thread session is idle, recovers pre-handoff rows after restart, and surfaces ambiguous mid-handoff state as session error rather than replaying to providers.Mobile adds Settings → Steer active turns (off →
after-current+ outboxdeliveryMode); web and mobile show Queued / Cancel on queued bubbles viacancelQueuedTurn. Command reads always include queued messages; settle/snooze/revert logic treats queued work as blocking.Reviewed by Cursor Bugbot for commit 6632d44. Bugbot is set up for automated code reviews on this repo. Configure here.