Runtime Events
Event kinds workers receive via @worker.on_event, with payload fields and lifecycle ordering.
Workers subscribe to runtime events with @worker.on_event(kind). The runtime publishes thirteen kinds across turn lifecycle, prompts, transport, sessions, components, and capability reloads.
@worker.on_event("turn.completed")async def on_turn(event, client) -> None: print(event.kind, event.payload["duration_ms"])Each handler receives an EventEnvelope. event.kind is always set; event.session_id is set for session-scoped events and None for runtime-scope. event.payload is a dict[str, Any] with the fields listed below.
Turn lifecycle
Section titled “Turn lifecycle”A turn always emits accepted first, started once it leaves the queue, and exactly one terminal event (completed, failed, or cancelled). Subscribe to the terminal kinds when you want one event per turn — they carry the full result.
| Kind | Payload | When |
|---|---|---|
turn.accepted | agent, model, reset, message_length, queue_depth | The turn was queued for processing. |
turn.started | agent, model | The turn left the queue and the model call is about to start. |
turn.completed | turn_id, response_text, tool_calls, usage, duration_ms, agent, message_count | Terminal — successful completion. |
turn.failed | turn_id, error: {type, message}, partial_response, tool_calls_attempted, duration_ms | Terminal — error before completion. |
turn.cancelled | turn_id, reason, partial_response, duration_ms | Terminal — cancelled by the user or runtime. |
Prompts
Section titled “Prompts”| Kind | Payload |
|---|---|
prompt.required | event_type, raw_event — permission requests and human-input requests |
Respond with client.send_permission_response(...) or client.send_human_input_response(...).
Sessions
Section titled “Sessions”| Kind | Payload | Notes |
|---|---|---|
session.created | session_id | A new session opened on the runtime. |
session.deleted | session_id | A session was removed. |
session.warning | code, message, sync_status | Operational warning for a session — currently used for platform-sync degradation. |
Capabilities
Section titled “Capabilities”| Kind | Payload |
|---|---|
capabilities.reloaded | capability_count |
Fires after the runtime re-discovers capabilities on disk.
Components
Section titled “Components”| Kind | Payload | Notes |
|---|---|---|
component.state_changed | capability, kind, name, status, error, detail | Any worker, MCP server, or tool health transition (start, stop, restart, crash). |
High-volume kinds
Section titled “High-volume kinds”Two kinds fire at very high rates and exist primarily for the runtime’s own clients (the TUI, transport bridges). Subscribe sparingly.
| Kind | Payload | Notes |
|---|---|---|
turn.event | event_type, raw_event | Every granular event inside a turn — model deltas, tool starts, generation chunks. |
transport.heartbeat | event_type, raw_event | Periodic keepalive emitted by the runtime transport layer. |
If you only care about completed turns, subscribe to turn.completed instead of filtering turn.event — the terminal envelope already aggregates everything you need.
Reserved namespaces
Section titled “Reserved namespaces”turn.*, prompt.*, session.*, transport.*, capabilities.*, and component.* are reserved for the runtime. client.publish(...) rejects custom kinds in those namespaces — use your own prefix (myapp.*, bridge.*, or capability.<name>.*) for events you emit.
Publishing custom events
Section titled “Publishing custom events”await client.publish( kind="myapp.report_ready", payload={"report_id": "abc123", "url": "https://..."}, session_id=event.session_id,)Subscribed workers and external clients receive the event. Use client.notify(...) instead when the audience is the human operator — notifications surface in the TUI rather than flowing through the event bus.