Two real defects, both found by testing rather than reading:
1. The client sent sessionId in the POST body while the route read it from
the query string, so the host always saw an empty id and answered
total:null -- the pill was correct to stay silent. The client now sends
the id in both places and the route reads both.
2. The ledger stored only a session total, so a restart kept the sum but
dropped the per-purpose split and the call count (9.99 total against
6.24 in parts, seen on the live route). Sessions are now stored as
objects; a bare number from an older ledger is still accepted.
Also made the client report why it cannot read a total: swallowing the
error made 'nothing spent yet' indistinguishable from 'request failed'.
Host half wraps globalThis.fetch to read usage.cost out of provider
responses (tee, never clone) and listens on the llm/stream seam for the
session; the two halves join on the generation id. Client half adds one
pill to the composer dock under its own id, so the harness's native stats
stay where they are.
Verified against the live API: three calls in one session (agent reply,
compaction, session title) attributed with per-purpose totals, an unknown
session answered with null, the ledger written and reloaded across
instances, and the pill rendering only when a cost exists.