How It Works

Session Watcher is a real-time instrumentation layer for Claude Code sessions. It observes the transcript as it grows, derives position and cost metrics per API call, and delivers them to a browser dashboard. This page describes the logical pipeline, the session lifecycle, and why each stage produces correct results.

Pipeline

A single API call flows through these stages:

Claude Code session
    │
    ▼  writes usage rows into a local transcript
┌──────────────────────────────────────────────┐
│  Incremental read                             │
│  Only new bytes since last poll are consumed  │
└──────────────────────────────────────────────┘
    │
    ▼  parse, build branch topology, find active path
┌──────────────────────────────────────────────┐
│  Fold                                         │
│  Deduplicate by message ID (idempotent),      │
│  detect segment boundaries                    │
└──────────────────────────────────────────────┘
    │
    ▼  one valid usage snapshot per API call
┌──────────────────────────────────────────────┐
│  Measurement                                  │
│  ├─ Baseline (B): per-path token accounting   │
│  ├─ Position (L): effective context length    │
│  ├─ Growth (g): smoothed residual rate        │
│  ├─ Settlement: absorb cache-timing noise     │
│  └─ Position fold: u, pp, mf, bp per call     │
└──────────────────────────────────────────────┘
    │
    ▼  assembled status snapshot
┌──────────────────────────────────────────────┐
│  Rate lamp                                    │
│  Integrate the stamped rent increment into    │
│  two clocks; a wallet rollover reminds        │
└──────────────────────────────────────────────┘
    │
    ▼  push to connected clients
┌──────────────────────────────────────────────┐
│  Dashboard                                    │
│  Browser fetches status on each data event,   │
│  renders gauges and history chart             │
└──────────────────────────────────────────────┘

Why this works:

Session Lifecycle

1. Hook (session-start)

Claude Code fires a hook on startup, resume, clear, or compact. The hook discovers the running server, hands it the transcript path, and injects any pending handoff tokens into the session context.

2. Server bootstrap

The MCP entrypoint starts a watcher instance, an HTTP server on a loopback port, and writes a discovery file so that hooks and the statusline can locate it. The server lifecycle is tied to the Claude Code process.

On resume (or any fresh process start with an existing transcript), the watcher reconstructs its state by re-reading the transcript from byte zero — rebuilding the full call history, measurement state, and segment boundaries from the file alone, through the same interpretation the live path uses. This is how per-call time series data survives a process restart without requiring a separate persistent store for it.

3. Polling

A timer drives the pipeline at regular intervals:

  1. Late resolution — if the transcript did not exist at startup, retry each tick.
  2. Idle gate — skip ticks when no clients are connected and nothing changed recently.
  3. Poll — run the full pipeline (read → fold → measure).
  4. Rate-lamp advance — integrate new samples into the rent ledger and raise any reminder they complete.
  5. Broadcast — notify connected browsers that fresh data is available.

4. Segmentation

A segment is a contiguous stretch of context between resets. Boundaries come from transcript topology alone — a root UUID that is not the file's first, which is what /compact and /continue produce. No token total starts a boundary, however steeply it falls: a reset replaces the whole conversation prefix, and only topology states that a prefix was replaced. A compact whose new branch keeps its parent in the file is therefore not seen as a boundary.

On boundary: finalize settlement, archive the segment for history, and reset all metrics to initial state. This ensures the cost model always reflects the current context, not a mixture of old and new.

5. Session rotation

When Claude Code restarts within a grace window, the hook sends the new session-id. The server archives the current segment, switches to the new transcript, and updates its discovery file.

6. Shutdown

On termination or idle timeout: stop timers, close connections, archive the final segment, and remove the discovery file. Segment archival persists profile summaries to SQLite — enough to reconstruct cross-segment trends but not the full per-call time series, which lives only in the current process memory.

Handoff

When a session ends, the agent can package its working context for the next session. The handoff mechanism bridges the gap between /clear (which destroys in-memory context) and the new session (which starts empty).

  1. The agent selects which file paths and symbols are essential for the next task — guided by the per-path token weights from B. It writes a structured summary (current state and intent, not history) and submits the package. The server persists this package on its own; nothing about the conversation is selected automatically.

  2. On /clear, the session-start hook fires for the new session. The hook queries persistent storage, finds the undelivered handoff for this project, and injects a reminder into the session context containing the load token, age, and task preview. The load skill reads this reminder and initiates the restore flow.

  3. The load skill extracts the token from the injected reminder, retrieves the handoff package, and reads the kept paths using the cheapest strategy available (symbol line ranges when present, full file otherwise). As these files are read, they flow through the normal pipeline — fold processes them, path attribution adds them to B — so the baseline naturally rebuilds to reflect the carried context. The response also carries a page of the history turns behind the handoff — the newest ones that fit a fixed budget, each a user request carrying the note written for it where the turn had one — plus a cursor for the next page, whose presence proves more history remains while its absence does not prove none does. Beside that page it carries the lineage itself: one headline per session behind the handoff, so a successor sees the chain it inherits and not only the session immediately before it. Three read tools go further into that same history: page deeper, search a literal that occurs verbatim, or locate the turn ranges that mention a remembered term. Each resolves the lineage from the handoff this session loaded, so none of them takes a lineage identifier.

Why this works