Detailed reference for websocket event routing and handler matching in the Python client.
Use with streaming-and-handlers.md when you need deterministic routing behavior across mixed handler registrations.
The websocket stream emits codex-manager event envelopes.
Relevant fields in handler logic:
event.typeevent.payloadevent.thread_id
StreamEvent does not expose top-level turn_id or request_id fields.
For app-server pass-through handlers, use on_app_server(...) / on_app_server_request(...) and read those fields from AppServerSignal:
signal.context["turnId"]signal.request_id
App-server passthrough events follow normalized names:
- notifications:
app_server.<normalized_method> - server requests:
app_server.request.<normalized_method>
Control and compatibility frames you may see:
- control:
ready,pong,error - raw compatibility:
notification,server_request
Control frames generally have no business payload and should be ignored by automation handlers unless you are implementing transport diagnostics.
Base matchers:
on_event(event_type)on_event_prefix(prefix)
App-server helpers:
on_app_server(normalized_method)on_app_server_request(normalized_method)on_turn_started()(returnsStreamEvent, alias ofapp_server.item.started)
Registration order is preserved inside the default router.
Default router behavior:
- evaluate routes in registration order
- invoke handler when matcher returns
True - continue to remaining handlers even when one handler fails
Implication:
- handlers are fanout, not first-match short-circuit.
Examples:
item/started->app_server.item.starteditem/tool/call->app_server.request.item.tool.callitem/fileChange/requestApproval->app_server.request.item.file_change.request_approval
Use normalized names in decorators.
When running multi-session listeners, filter early:
@cm.on_app_server("item.started")
async def on_item_started(signal, _ctx):
if signal.context.get("threadId") != target_session_id:
return
# handle session-local logicFor remote-skill handling, prefer skills.matches_signal(signal) to avoid mismatched session dispatch.
Handler exceptions are isolated by default router behavior.
Implications:
- one failing callback does not stop stream consumption
- one failing callback does not prevent other handlers from running
You should still log/monitor errors to avoid silent business-logic gaps.
- Async client handlers may be sync or async callables.
- Sync client handlers must remain sync from caller perspective.
If sync remote-skill handlers accidentally return awaitables, dispatch returns a handled failure payload and instructs using the async client path.
Advanced users can inject stream_router=... to replace matching and dispatch behavior.
Recommended invariants for custom routers:
- preserve handler isolation
- preserve deterministic ordering guarantees
- avoid blocking I/O inside dispatch loop
- ensure exceptions remain observable
- Streaming and handlers overview:
streaming-and-handlers.md - Remote-skill routing and submission semantics:
remote-skills-dispatch-and-reliability.md - Protocol event families:
../protocol/harness-runtime-events.md