2026-08-17 · engineering notes
Reading the black box
Every AI coding session leaves a complete record on your disk, and almost nobody looks at it. I spent a day turning those records into Agent Observatory, a native macOS app that replays Claude Code and Cursor sessions as timelines: every tool call, every retry, and a running total of what each session cost. This post is about the archaeology, because neither format is documented and both had surprises.
Claude Code: JSONL with traps
Claude Code writes one JSONL file per session. Each line has a type, and most types are noise for replay purposes (mode changes, file snapshots, attachments). The signal lives in three of them: user lines, assistant lines, and a small ai-title line that carries the session name.
The first trap: a single assistant message that streams in can span several JSONL lines, and each line repeats the same usage block. Sum usage naively and your cost numbers double or triple. The lines share a requestId, so the fix is to deduplicate usage per request while still rendering every content block. My parser has a test pinning exactly this case, because it is the kind of bug that produces confident, wrong dollar figures.
The second trap: tool results do not live with the tool calls. The assistant line carries the tool invocation; the result arrives later inside a user line, paired by tool_use_id. Pairing them back up is what makes per-call durations possible, which is where the interesting insights come from: the session that retried one failing command five times, or read the same file fourteen times.
Cursor: SQLite, and friendlier than expected
Cursor keeps conversations in a SQLite database. A headers table lists every conversation with timestamps and a workspace id, and a key-value table holds the messages, one JSON blob per bubble. Type 1 is the user, type 2 is the assistant, and tool calls sit in a toolFormerData object complete with name, arguments, result, and an error status. Message order comes from a headers list on the conversation record, not from storage order, which is the sort of detail you only learn by getting it wrong once.
The adapter runs two read-only queries behind a normalized session model, so the entire UI works identically on both sources. The database connection is opened with SQLITE_OPEN_READ_ONLY at the native level: writing is impossible by construction, not by promise.
Why local-only is the whole point
These files contain your code, your prompts, and occasionally your secrets, so the app makes one commitment: nothing leaves your machine. No network calls, system fonts only, a filesystem scope limited to the transcript directory, and read-only access everywhere. The privacy section of the README describes enforcement mechanisms rather than policies, and I think that is the right way around.
Agent Observatory is MIT-licensed on GitHub, with a v0.1.0 build available in Releases. The parser and its fixtures are the best starting point if you want to read your own black box.