Skip to content

Implemented thread-level atomic elicitation counter for stopwatch pausing#12296

Merged
cconger merged 8 commits intomainfrom
cconger/stopwatch-elicitation
Mar 10, 2026
Merged

Implemented thread-level atomic elicitation counter for stopwatch pausing#12296
cconger merged 8 commits intomainfrom
cconger/stopwatch-elicitation

Conversation

@cconger
Copy link
Contributor

@cconger cconger commented Feb 20, 2026

Purpose

While trying to build out CLI-Tools for the agent to use under skills we have found that those tools sometimes need to invoke a user elicitation. These elicitations are handled out of band of the codex app-server but need to indicate to the exec manager that the command running is not going to progress on the usual timeout horizon.

Example

Model calls universal exec:
$ download-credit-card-history --start-date 2026-01-19 --end-date 2026-02-19 > credit_history.jsonl

download-cred-card-history might hit a hosted/preauthenticated service to fetch data. That service might decide that the request requires an end user approval the access to the personal data. It should be able to signal to the running thread that the command in question is blocked on user elicitation. In that case we want the exec to continue, but the timeout to not expire on the tool call, essentially freezing time until the user approves or rejects the command at which point the tool would signal the app-server to decrement the outstanding elicitation count. Now timeouts would proceed as normal.

What's Added

  • New v2 RPC methods:
    • thread/increment_elicitation
    • thread/decrement_elicitation
  • Protocol updates in:
    • codex-rs/app-server-protocol/src/protocol/common.rs
    • codex-rs/app-server-protocol/src/protocol/v2.rs
  • App-server handlers wired in:
    • codex-rs/app-server/src/codex_message_processor.rs

Behavior

  • Counter starts at 0 per thread.
  • increment atomically increases the counter.
  • decrement atomically decreases the counter; decrement at 0 returns invalid request.
  • Transition rules:
    • 0 -> 1: broadcast pause state, pausing all active stopwatches immediately.
    • >0 -> >0: remain paused.
    • 1 -> 0: broadcast unpause state, resuming stopwatches.
  • Core thread/session logic:
    • codex-rs/core/src/codex_thread.rs
    • codex-rs/core/src/codex.rs
    • codex-rs/core/src/mcp_connection_manager.rs

Exec-server stopwatch integration

  • Added centralized stopwatch tracking/controller:
    • codex-rs/exec-server/src/posix/stopwatch_controller.rs
  • Hooked pause/unpause broadcast handling + stopwatch registration:
    • codex-rs/exec-server/src/posix/mcp.rs
    • codex-rs/exec-server/src/posix/stopwatch.rs
    • codex-rs/exec-server/src/posix.rs

Copy link
Contributor

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 54d4da7f29

ℹ️ 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".

@cconger cconger requested a review from bolinfest February 20, 2026 04:47
@cconger cconger force-pushed the cconger/stopwatch-elicitation branch 4 times, most recently from b5279d1 to 675128b Compare February 26, 2026 18:51
@cconger cconger force-pushed the cconger/stopwatch-elicitation branch 2 times, most recently from e2aa79b to 5d6a86f Compare March 8, 2026 22:22
cconger added 6 commits March 9, 2026 00:11
…sing

While trying to build out CLI-Tools for the agent to use under skills we
have found that those tools sometimes need to invoke a user elicitation.
These elicitations are handled out of band of the codex app-server but
need to indicate to the exec manager that the command running is
not going to progress on the usual timeout horizon.

Model calls universal exec:
`$ download-credit-card-history --start-date 2026-01-19 --end-date 2026-02-19 > credit_history.jsonl`

download-cred-card-history might hit a hosted/preauthenticated service to fetch data.  That service might decide that the request requires an end user approval the access to the personal data.  It should be able to signal to the running thread that the command in question is blocked on user elicitation.  In that case we want the exec to continue, but the timeout to not expire on the tool call, essentially freezing time until the user approves or rejects the command at which point the tool would signal the app-server to decrement the outstanding elicitation count.  Now timeouts would proceed as normal.

- New v2 RPC methods:
    - thread/increment_elicitation
    - thread/decrement_elicitation
- Protocol updates in:
    - codex-rs/app-server-protocol/src/protocol/common.rs
    - codex-rs/app-server-protocol/src/protocol/v2.rs
- App-server handlers wired in:
    - codex-rs/app-server/src/codex_message_processor.rs

- Counter starts at 0 per thread.
- increment atomically increases the counter.
- decrement atomically decreases the counter; decrement at 0 returns invalid request.
- Transition rules:
    - 0 -> 1: broadcast pause state, pausing all active stopwatches immediately.
    - >0 -> >0: remain paused.
    - 1 -> 0: broadcast unpause state, resuming stopwatches.
- Core thread/session logic:
    - codex-rs/core/src/codex_thread.rs
    - codex-rs/core/src/codex.rs
    - codex-rs/core/src/mcp_connection_manager.rs

- Added centralized stopwatch tracking/controller:
    - codex-rs/exec-server/src/posix/stopwatch_controller.rs
- Hooked pause/unpause broadcast handling + stopwatch registration:
    - codex-rs/exec-server/src/posix/mcp.rs
    - codex-rs/exec-server/src/posix/stopwatch.rs
    - codex-rs/exec-server/src/posix.rs
  - Add a session-local out-of-band elicitation pause signal in codex-rs/core, so core runtimes can observe the same paused
    state that was previously only forwarded to MCP servers.
  - Update CodexThread to set and clear that local pause state when the out-of-band elicitation count crosses zero, with
    rollback if the MCP notification fails.
  - Make unified exec subscribe to that session pause state for both the initial exec_command call and subsequent write_stdin
    polls.
  - Pause unified exec’s yield deadline while the thread is paused, then resume with the remaining budget once the pause lifts
    instead of letting the 10s window expire in the background.
  - Add a core regression test proving a paused unified exec call does not yield early and still returns the command output
    after the pause clears.

  Why

  - The thread elicitation APIs are meant to block turn progress while an external binary is still holding the thread.
  - Before this change, unified exec’s yield timer kept advancing even during an out-of-band elicitation pause, so exec_command
    could return early and let the model continue reasoning before the underlying process finished.
@cconger cconger force-pushed the cconger/stopwatch-elicitation branch from 90ed0c8 to cf8c6be Compare March 9, 2026 00:14
@cconger
Copy link
Contributor Author

cconger commented Mar 9, 2026

@codex review

Copy link
Collaborator

@celia-oai celia-oai left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

chatted offline, this will be experimental & intended for internal only and we'll delete after proper solution here (maybe elicitation cli)

@cconger cconger force-pushed the cconger/stopwatch-elicitation branch from 5e38ee3 to cf8c6be Compare March 9, 2026 23:53
params: v2::ThreadUnsubscribeParams,
response: v2::ThreadUnsubscribeResponse,
},
#[experimental("thread/increment_elicitation")]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we add a docstring here describe what these two endpoints do?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

& maybe make it clear that this only applies to unified exec as a workaround?

Ok(())
}

async fn notify_out_of_band_elicitation_state_change(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's the next step for handling in this notification from the MCP side? Is there an MCP example that utilize this?

@cconger cconger merged commit c6343e0 into main Mar 10, 2026
31 checks passed
@cconger cconger deleted the cconger/stopwatch-elicitation branch March 10, 2026 05:29
@github-actions github-actions bot locked and limited conversation to collaborators Mar 10, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants