feat: add durable app-server queued turns#21466
Conversation
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8aed86715d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
50245ae to
1a25424
Compare
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1a254249bc
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
1a25424 to
8b0004c
Compare
|
@codex review |
8b0004c to
bebdc22
Compare
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: bebdc228e9
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
bebdc22 to
cf489a3
Compare
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: cf489a3b7a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
cf489a3 to
bc3d438
Compare
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: bc3d4381ee
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
bc3d438 to
bc8a27f
Compare
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: bc8a27f5d8
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
bc8a27f to
4414e1a
Compare
|
@codex review |
4414e1a to
61d9aab
Compare
|
@codex review |
61d9aab to
f3477d8
Compare
|
@codex review |
1 similar comment
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f3477d8b76
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if self | ||
| .thread_has_live_in_progress_turn(thread_id, thread.as_ref()) | ||
| .await | ||
| { | ||
| return; |
There was a problem hiding this comment.
Mark dispatched turns active before draining again
This idle check can still return false just after start_queued_turn succeeds, because submit_with_trace only enqueues the core op and the listener has not necessarily observed turn/started yet. A second queue mutation in that window can dispatch the next queued item immediately instead of waiting for the first queued turn to reach a terminal state.
Useful? React with 👍 / 👎.
| self.turn_start_inner( | ||
| /*request_id*/ None, params, /*app_server_client_name*/ None, | ||
| /*app_server_client_version*/ None, |
There was a problem hiding this comment.
Preserve client compatibility when draining queued turns
start_queued_turn passes None for the app-server client name/version, but turn_start_inner still calls set_app_server_client_info. For Xcode 26.4 sessions this recomputes mcp_elicitations_auto_deny as false, undoing the compatibility auto-deny setting before queued turns run.
Useful? React with 👍 / 👎.
| let turn_start_params_json = | ||
| serde_json::to_string(¶ms.turn_start_params).map_err(|err| { | ||
| internal_error(format!("failed to serialize queued turn params: {err}")) | ||
| })?; | ||
| let queued_turn = state_db | ||
| .append_thread_queued_turn(thread_id, turn_start_params_json) |
There was a problem hiding this comment.
Cap queued payload size before persisting
thread/queue/add validates only text characters, then persists the full serialized turnStartParams. Non-text fields such as image URLs, paths, metadata, and outputSchema remain unbounded, so a client can store arbitrarily large queued rows and repeatedly force DB reads/notifications with those payloads.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f3477d8b76
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if let Err(err) = state_db | ||
| .delete_thread_queued_turn(thread_id, queued_turn.queued_turn_id.as_str()) | ||
| .await |
There was a problem hiding this comment.
Avoid resubmitting queued turns after a crash
After start_queued_turn succeeds, the durable row is deleted in a separate later DB operation. If app-server exits or is killed in this window, the row remains; on the next resume/drain, first_dispatchable_thread_queued_turn will submit the same queued user input again, duplicating commands despite the queue being the recoverable execution state.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f3477d8b76
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| match self | ||
| .turn_processor | ||
| .start_queued_turn(turn_start_params) |
There was a problem hiding this comment.
Serialize queued dispatch until the turn is observed running
start_queued_turn only enqueues an op to core; it does not wait for turn/started or mark app-server state active. A second thread/queue/add on an idle thread can therefore pass the idle check before the listener sees the first turn and dispatch the next queued item too, causing core to steer/replace the active turn instead of preserving one durable turn per terminal boundary.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f3477d8b76
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| match self | ||
| .turn_processor | ||
| .start_queued_turn(turn_start_params) | ||
| .await | ||
| { | ||
| Ok(_) => { | ||
| if let Err(err) = state_db | ||
| .delete_thread_queued_turn(thread_id, queued_turn.queued_turn_id.as_str()) |
There was a problem hiding this comment.
Persist dispatch state before starting queued turns
The queue row is deleted only after start_queued_turn returns. If app-server exits or is killed after core accepts the turn but before this DELETE commits, the durable row survives; on the next resume/drain the same user input is submitted again, duplicating a turn and potentially repeating tool actions. The durable queue needs an atomic in-flight/claimed state or idempotent dispatch before starting.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f3477d8b76
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if self | ||
| .thread_has_live_in_progress_turn(thread_id, thread.as_ref()) | ||
| .await |
There was a problem hiding this comment.
Treat submitted queued turns as active immediately
start_queued_turn only enqueues the core op; agent_status/active_turn_snapshot do not change until the later TurnStarted event is processed. A second queue mutation in that window passes this idle check and dispatches/deletes another queued turn before the first queued turn reaches a terminal state, breaking the durable one-at-a-time queue semantics.
Useful? React with 👍 / 👎.
Summary
Queued follow-up turns are currently owned by clients, so a renderer reload can lose the next user command before it ever reaches app-server. This change moves the durable queue primitive into app-server so clients can enqueue future turns once, recover them after reconnecting, and let app-server drain them when the active turn reaches a terminal state.
This adds persistent queued-turn storage, v2 JSON-RPC methods under
thread/queue/*, queue-change notifications, and queue draining after terminal turns. Queue and steer remain separate operations: clients still choose whether to steer the active turn or enqueue a future turn.Design decisions
add,list,delete, andreordermatch the behavior clients already expose today.lastDispatchErroron that queued turn instead of silently skipping ahead and changing user-visible order.Testing
Tests: targeted app-server queue integration tests, state-store tests, schema fixture tests, and local Codex App smoke testing with a custom CLI build proving a queued follow-up survives a renderer reload and later drains into a real second turn.