Install
The runner
AgentRunner holds a WebSocket connection to the Unpod orchestrator. When a
call is dispatched to your agent, the runner invokes your entrypoint with a
CallContext.
agent_id is the runner agent ID - a short string you choose, matching the
agent_id in your Speech Pipe config. Not the pipe’s UUID. See
IDs You’ll Meet.Constructor
Runner lifecycle hooks
React to runner-level events (distinct from per-session hooks):This is the runner-level
call_end (signature (ctx, final_state)), fired
once per call on the runner. The session-level call_end
(@ctx.session.on("call_end"), signature (final_state)) fires inside a single
call and is where you read the telephony end_reason via
ctx.session.data.get("end_reason"). See
Call Lifecycle.CallContext
Every call to yourentrypoint receives a
CallContext:
ctx.agent_id is the agent the call was dispatched for (carried on
call.started); ctx.runner_id is the agent_id this runner process was
constructed with. They match for a single-agent runner and differ for a
multi-tenant one — route on ctx.agent_id, not ctx.runner_id.The Session
ctx.session is your interface to the live call: speak, interrupt, transfer,
record, end - all from inside your entrypoint.
Speaking
Interrupting
Transferring
Ending
"completed", "no_response", "error", "transferred",
"max_duration".
Recording control
Per-call data
session.data is a plain dict scoped to the current call:
The main loop - run()
session.run() keeps the call alive. It reads bridge events, fires your hooks,
routes each transcribed user turn to your dialog adapter’s stream(), and
pipes the reply tokens to TTS.
Live metrics
Latency/cost/token fields populate only if you feed the tracker via
metrics.record_turn(...) (e.g. from the turn_complete hook). Out of the box
only duration_s is meaningful — for real per-turn numbers use the llm_call /
turn_complete hooks. See Metrics, Cost & Observability.Session API reference
Out-of-band session control
Act on a live session from outside the call - your backend, an ops tool - via the Management SDK, targeting it by session ID:Running in production
Monitoring
Graceful shutdown
SendSIGTERM (standard for containers and systemd). The runner stops
accepting dispatches, waits up to drain_timeout_s for active calls, then
exits. Or call await runner.shutdown() yourself.
Multiple runners
Run multipleAgentRunner processes with the same agent_id across machines.
The orchestrator load-balances on reported capacity - no shared state needed.
Next steps
Bring Your Agent
Plug your existing brain into session.dialog_machine.
Hooks & Events
React to every turn, interruption, silence, and lifecycle event.