# Log Observability Events


kli emits an event for everything that happens in a session: model changes, session branches and resets, committed patches, notifications, and faults. This guide turns those events into a durable record. You set one path, kli appends each event to that file as a JSON line, and `/observability` tells you the sink is live.

The sink is off until you give it a path. Nothing is recorded by default.

## Point the sink at a file

Add an `observability` section to your settings and set `path` to where the log should be written. Set it in `settings.json` (global `~/.config/kli/settings.json`, project `<repo>/.kli/settings.json`; project wins) — see [Settings](/kli/config/settings).

```json
{
  "observability": {
    "path": "~/.local/state/kli/events.jsonl"
  }
}
```

A leading `~` expands against your home directory. A relative path resolves against the working directory kli was started in. An absolute path is used as-is. kli creates the parent directories on the first write, so the file does not need to exist beforehand.

Settings load at startup. Start kli in your project, or restart it if it is already running, for the new path to take effect.

The sink stays disabled when `path` is absent or an empty string. That is the off switch: remove the key (or set it to `""`) to stop recording without deleting the rest of your `observability` section.

## Record only some events

By default the sink records every event. Add an `events` array to record only the types you care about. Each entry is a prefix matched against the event type name, so one entry can select a whole family.

```json
{
  "observability": {
    "path": "~/.local/state/kli/events.jsonl",
    "events": ["session", "model-change", "fault"]
  }
}
```

This records every type whose name starts with `session` (`session-switch`, `session-branch`, `session-reset`, `session-rewind`, `session-cleared`, and the rest), plus `model-change` and `fault`. An event is recorded when any prefix in the array matches; an event matching none is dropped.

Matching is case-insensitive and prefix-based, so `session` catches the whole session family while `session-reset` catches just that one type. An empty array records every event, the same as omitting the key.

## Check the sink

Run `/observability` in a session to see the current state.

When the sink is enabled, it reports the resolved path, the active filter, the number of events written so far, and the last write error if one occurred:

```
observability: enabled
path: /home/you/.local/state/kli/events.jsonl
filter: session, model-change, fault
events-written: 42
```

The filter line reads `(all)` when no `events` array is set. When the sink is off, the command reports `observability: disabled`. Use this to confirm your `path` resolved to where you expected and that events are reaching the file.

## Read the log

kli appends one event per line. Each line is a JSON object with four fields:

- `timestamp` — the wall-clock time the event occurred, as a Lisp universal time integer (seconds since 1900-01-01 UTC).
- `type` — the event type name, downcased (for example `model-change`, `session-branch`, `fault`).
- `source` — the originator of the event, present only when the event carries one.
- `payload` — the event's data, present only when the event carries one. Keyword-keyed data renders as a JSON object with downcased keys; other data renders as an array or a string.

A `fault` event, for instance, looks like this:

```json
{"timestamp":3958372800,"type":"fault","source":"tool-call","payload":{"seam":"tool-call","unit":"read-file","condition":"file not found","dispatch":"contained"}}
```

Each line is independent JSON, so pipe the file through `jq`, `grep`, or any line-oriented tool. Each write is appended under a lock and flushed, so a `tail -f` of a running session never shows a half-written line.

A failed write does not interrupt the session. The sink records the error, drops that one line, and keeps running; the error then shows up on the `last-error` line of `/observability`.

## Related

- [Configure kli with settings.json](/kli/config/settings)
- [The Agent Loop](/kli/concepts/the-agent-loop)
