# Events and hooks (/observe/events)

Every step of a call emits an event. Events are:

1. stored with the session in the database,
2. streamed to Realtime and PCM clients and the dashboard,
3. passed to your `on_event` hook.

An event has `session_id`, `kind`, `t` (seconds into the call), `turn_id` and
`data` (a dict).

## Hooking in [#hooking-in]

```python title="src/events.py"
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 [#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](/observe/storage) for the
database, recordings and the JSON API over them.