Log Observability Events
On this page
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.
{
"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.
{
"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: 42The 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 examplemodel-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:
{"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.