05Observe
Events and hooks
Every call is a stream of events — hook into them, query them, replay them.
Every step of a call emits an event. Events are:
- stored with the session in the database,
- streamed to Realtime and PCM clients and the dashboard,
- passed to your
on_eventhook.
An event has session_id, kind, t (seconds into the call), turn_id and
data (a dict).
Hooking in
from cuelo.runtime.events import Event
async def on_event(event: Event) -> None:
if event.kind == "turn.ended" and event.data["role"] == "user":
print(f"[{event.session_id}] user: {event.data['text']}")
if event.kind == "session.ended":
await notify_crm(event.session_id)The hook may be sync or async. Exceptions are logged and never break the call.
Outside a project, pass Harness(build, on_event=...).
Kinds
| Kind | data |
|---|---|
session.started | agent, config |
session.updated | what the client changed, e.g. voice |
session.ended | reason |
turn.started | role |
turn.ended | role, text, interrupted |
stt.partial | text |
stt.final | text, finalized, start, end, language |
controller.signal | controller, signal, audio_t, text |
policy.decision | action, audio_t, text |
llm.started | model |
llm.first_token | — |
llm.delta | text. Live only, not stored. |
llm.completed | text, stop_reason, input_tokens, output_tokens |
tool.called | id, name, arguments |
tool.result | id, content, is_error |
tts.started | text |
tts.first_audio | — |
tts.ended | interrupted |
agent.interrupted | — agent audio stopped. Clients must drop buffered audio. |
backchannel.played | text |
audio.saved | side, uri, sample_rate, start, duration |
summary.created | text, model |
error | component, message |
The list lives in cuelo.runtime.events.Kind.
Events are also stored with each call. See Storage for the database, recordings and the JSON API over them.