Python SDK: support deferred/asynchronous server-request handling for human-in-the-loop clients
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 125k
- Forks
- 19.4k
- PR merge metrics
- PR metrics pending
Description
Summary
The Python SDK currently makes it difficult to build a long-lived interactive client that must surface Codex server-originated requests to a human UI and resolve them later without stopping the SDK reader.
I would like to propose a deferred/asynchronous server-request API for CodexClient / AsyncCodexClient, with explicit support for command/file/permission approvals and requestUserInput-style flows.
This is particularly important for gateway/server integrations where a request may be parked for seconds or minutes while a user responds.
Current behavior
As of current main (2026-09-02):
CodexClientaccepts anapproval_handler.- the single reader thread calls
_handle_server_request(msg)synchronously; _handle_server_request()invokes the handler synchronously;- the reader immediately writes the JSON-RPC response after the handler returns.
Source:
sdk/python/src/openai_codex/client.py
Conceptually:
msg = read_message()
response = self._handle_server_request(msg) # synchronous
self._write_message({"id": msg["id"], "result": response})
AsyncCodexClient does not provide a separate async transport architecture. It is documented as an async wrapper around the sync client using thread offloading, and its constructor currently accepts only config, not an approval_handler.
Source:
sdk/python/src/openai_codex/async_client.py
There is also a related existing issue about the sync client's fail-open default handler behavior:
- #27277
Why synchronous handling is limiting
For an interactive client/gateway, the desired flow is often:
Codex server request
-> client records pending request
-> UI displays approval/question
-> HTTP/UI request returns later
-> client responds to the original Codex JSON-RPC request
If the server-request handler simply blocks waiting for the human response, the SDK reader is also blocked for that entire period.
That may be acceptable for a dedicated one-client-per-session topology, but it has important limitations:
- no further reader-side control/event processing for that client while parked;
- interrupt/terminal responses cannot be consumed until the handler returns;
- a second server-originated request cannot be surfaced concurrently if the protocol permits one;
- process/transport failure is harder to fan out deterministically to the pending interaction;
- shared-client/multiplexed topologies become unsuitable;
AsyncCodexClientdoes not currently offer an async/deferred escape hatch.
For server products this can force either process-per-session topology or a private replacement of the SDK reader/router, which significantly reduces the maintenance advantage of using the official SDK.
Requested capability
A minimal useful design would separate receiving a server request from responding to it.
For example, one of the following shapes could work.
Option A — deferred request handle
async for request in client.server_requests():
# request.method
# request.params
# request.id / thread / turn metadata
decision = await wait_for_human_ui(request)
await request.respond(decision)
Option B — async handler
async def server_request_handler(request):
return await wait_for_human_ui(request)
client = AsyncCodexClient(
config=config,
server_request_handler=server_request_handler,
)
The important implementation property is that the transport reader should be able to continue routing unrelated responses/notifications while a server request is unresolved.
A bounded pending-request registry keyed by JSON-RPC request id would be enough; the API does not need to prescribe a UI model.
Async API parity
It would also be useful for AsyncCodexClient to expose the same explicit server-request / approval configuration as the sync API rather than silently inheriting the sync client's default behavior.
At minimum, callers should be able to select a fail-closed handler explicitly.
Failure semantics that would help long-running clients
For a robust gateway integration, a deferred server request should have deterministic behavior when:
- the Codex process exits;
- the transport closes;
- the turn is interrupted/completed and the request becomes invalid;
- the caller cancels the pending request;
- a late UI response arrives after the request has been resolved/invalidated.
Ideally pending handles/futures would complete with a transport/request-expired error rather than remain blocked indefinitely.
This is adjacent to the transport-terminalization and early-turn-routing issues tracked in:
- #40399
- #41078
Why this belongs in the SDK
codex app-server already exposes the rich bidirectional request/notification surface needed by interactive clients. The Python SDK is already the official typed app-server client.
A deferred server-request primitive would let applications use that official client for human-in-the-loop products without reimplementing the reader/router solely to avoid blocking on UI latency.
It would also make the SDK substantially more suitable for multi-session server/gateway use cases while keeping protocol evolution centralized upstream.
Minimal acceptance cases
A regression/integration test for the feature could cover:
- server requests approval and remains pending;
- unrelated notification/RPC response is still routed while approval is pending;
- approval is resolved later and response is written exactly once;
- interrupt while approval is pending reaches terminal state;
- process/transport death completes the pending request with an error;
- late/double resolution is rejected deterministically;
AsyncCodexClientsupports the same fail-closed handler/deferred API.
I am not proposing a specific public naming scheme; the key capability is deferred, non-reader-blocking server-request resolution.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with sdk/python/src/openai_codex/client.py, especially the reader loop and _handle_server_request(), then compare sdk/python/src/openai_codex/async_client.py. Define and document a deferred server-request API that keeps unrelated routing active, and cover pending resolution, interruption, transport failure, late or duplicate responses, and AsyncCodexClient parity in regression or integration tests.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- api
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100