← back to work

case study · August 2026

Agent Observatory

A native macOS flight recorder for AI coding sessions. Teams spend real money on coding agents with no visibility into what sessions actually do; this tool replays them, tool call by tool call, with a running dollar total. From first commit to a tagged v0.1.0 release: about a week.

2 undocumented formats reverse-engineered27 tests pinning parser behavior0 network calls, enforced structurallyv0.1.0 released, MIT, CI green

The problem

Claude Code and Cursor both write complete session records to disk, and almost nobody reads them. When an agent burns $15 retrying a broken test, that fact exists in a JSONL file nobody opens. I wanted the black box recorder: every prompt, every tool call, every retry, every dollar, replayable after the fact.

Constraints I chose

Local-only, enforced rather than promised. These transcripts contain code, prompts, and occasionally secrets, so the app makes no network calls at all, and that had to be verifiable from the code, not stated in a privacy policy.

Native, not Electron. A tool developers keep open all day should feel like a macOS app: real vibrancy, system behaviors, small binary. I chose Tauri with a Rust core, having never shipped Rust before, and priced in the learning curve.

Read-only by construction. The app must be incapable of corrupting the tools it observes. Cursor's SQLite store is opened with SQLITE_OPEN_READ_ONLY at the native level, and the filesystem capability is scoped to the transcript directories. Misuse is impossible, not discouraged.

Architecture

~/.claude/projects/*.jsonl          Cursor state.vscdb (SQLite)
        │                                   │
        ▼                                   ▼
  JSONL parser (TS, tested)      read-only Rust queries (rusqlite)
        │                                   │
        └────────── normalized session model ──────────┘
                          │
        ┌─────────────────┼──────────────────┐
        ▼                 ▼                  ▼
   timeline replay   cost engine       insight passes
   (per tool call)   (per-model $)    (retries, waste)

The load-bearing decision is the normalized session model in the middle: both sources map into one shape, so the timeline, cost engine, and insights are written once and work on both. Parsing stays in TypeScript where it is easiest to test exhaustively; Rust does only what must be native.

What reverse engineering actually looked like

Neither format is documented. Claude Code's JSONL had two traps worth naming. First, a streamed assistant message spans several lines and each line repeats the same usage block; sum them naively and costs double or triple. The lines share a requestId, so usage must be deduplicated per request while every content block still renders. There is a test pinning exactly this case, because it is the kind of bug that produces confident, wrong dollar figures. Second, tool results do not live with their calls; they arrive later inside user lines, paired by tool_use_id, and pairing them back up is what makes per-call durations, and therefore the interesting insights, possible.

Cursor stores conversations in SQLite: a headers table for conversations, a key-value table with one JSON blob per message bubble, tool calls in a toolFormerData object with name, args, result, and error status. The trap: message order comes from a headers list on the conversation record, not from storage order, which I learned by getting it wrong once and writing the test that keeps it right.

What went wrong

Honesty section. I tried macOS's hudWindow vibrancy effect for the glass UI and the app launched with no visible window at all; the fix was a different material (underWindowBackground) and a note in the repo so nobody repeats it. The Tauri dev watcher silently ignores config changes, which cost me an afternoon of debugging phantom state. And my first cost numbers were triple the truth, courtesy of the streamed-usage trap above, which is precisely why the parser is the most tested code in the repo.

Outcome and limits

v0.1.0 is released under MIT with CI, an icon, a .dmg build pipeline, and a README whose architecture and limitations sections are real. Current limits, stated plainly: Claude Code and Cursor only (the adapter interface is designed for more tools), unsigned builds pending an Apple Developer membership, and pricing tables that need manual updates when providers change rates.

Code, fixtures, and the parser tests are public: github.com/tsaipraveen99/agent-observatory. The reverse-engineering field notes became a post: Reading the black box.