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:
- Incremental read ensures no data is missed or double-counted regardless of how fast the transcript grows.
- Idempotent folding means streaming revisions of the same call never create duplicate entries — only the final snapshot survives.
- Segment scoping resets metrics on each context boundary so stale history from a previous context never contaminates current measurements.
- Settlement absorbs the timing gap between cache-creation and cache-read: B is credited ahead of L during the lag, and the deferred ledger holds that credit until L confirms it. Any ledger balance un-retired at segment end is treated as estimation error and corrected out of the buckets.
- Growth smoothing (g) is a single-exponential level filter with a per-call step cap on the residual (total-stock growth net of the growth known file operations account for), floored so the cold-start diagnostic never divides by zero. It drives the growth readout and the projection slope. The position fold does not read it: each interval is priced at the realized mean growth so far of the context outside the scenario's baseline, which carries no smoothing constant and cannot lag behind what the segment has actually accumulated.
- Cost chain maps the session to an EOQ inventory cycle: the fixed restart cost is cRatio × baseline (analogous to the order cost per batch), the per-token cache-read price is the holding cost, growth rate g is the demand rate, and session length in calls is the order quantity. The sweet spot is where amortized restart cost equals accumulated holding cost. This mapping produces a single dimensionless premium (bp) that is comparable across models and session sizes.
- Causal position is what bp is read at, and it is accumulated rather than recomputed. Each call adds the fraction of a restart interval that the baseline in force and the realized mean growth outside that baseline imply for that call, so the position is a property of the path the session actually travelled. Re-pricing the whole history against today's baseline — which is what a ratio of current values would do — would let a session that loads a large file appear to have travelled backwards, and would move a reminder that was already earned. Because each interval is priced once, at the state that held while it was being traversed, later growth changes only how fast the position advances from then on. Landmarks on the position axis are placed by fitting a straight line across the travelled path, so they sit on the same evidence rather than on a formula applied to the latest reading.
- Rate lamp integration accumulates the same per-call rent increment into two counters: a bill cycle for display, and a reminder interval whose rollover raises the reminder, each increment converted at the exchange fraction in force for its own interval. Reading accumulated rent rather than the momentary premium means reminders follow sustained cost, and nothing about a later position revision can retract one.
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:
- Late resolution — if the transcript did not exist at startup, retry each tick.
- Idle gate — skip ticks when no clients are connected and nothing changed recently.
- Poll — run the full pipeline (read → fold → measure).
- Rate-lamp advance — integrate new samples into the rent ledger and raise any reminder they complete.
- 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).
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.
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.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
- B recovers from disk state, not snapshots. The handoff carries path references, not cached token counts. If a file was deleted or changed since prepare, B reflects reality — stale entries do not inflate the baseline.
- Hook injection eliminates manual token passing. The user does not need to remember or copy a token. The hook discovers and injects it; the agent picks it up on the first interaction.
- Turn notes preserve trajectory without bulk. Rather than predicting which turns will matter later, the system records every history turn at the moment the handoff is prepared — each with the note written for it, and a turn with no assistant work of its own recorded without one — then delivers the newest of them as a page under a fixed token budget. What does not fit stays reachable through the read tools instead of being guessed at up front, so the handoff package stays small without discarding the trail that led to it.