Summary
Providers that account usage cumulatively at session level and anchor the whole session to started_at (hermes today; any future session-cumulative provider) systematically lose usage in the durable daily history once the session outlives its start day.
Split off from the cost-accuracy report in #913. Related to the cost-provenance design in #890.
Mechanism
- The hermes provider emits one
ParsedProviderCall per session, carrying the session's lifetime token totals, with timestamp = started_at.
ensureCacheHydrated finalizes days up to yesterday; gapStart = lastComputedDate + 1 means a finalized day is never re-derived (by design, carry-forward is what keeps history alive after session files expire).
buildDurablePeriod takes historical days exclusively from the daily cache; the live parse only feeds today's bucket and detail views.
So for a session that starts on day D and keeps running:
- Day D is finalized on the first run of D+1 with the session's totals as of that moment.
- All usage the session adds after that finalization is attributed to D by timestamp, but D is sealed, and the today-parse drops the call because
started_at is outside today's range. The growth lands nowhere. It is permanently absent from report/overview/menubar headline totals.
- Consequence a user can see directly: drill-down session lists (fresh parse) show larger totals than the daily rows they supposedly reconcile to, and "today" views show nothing for a session that is actively burning tokens today but was started last week.
For an agent whose sessions are long-lived conversations resumed across days (exactly how hermes is used), this is not an edge case; over weeks the durable history can drift far below true usage. It is the likely remaining contributor to the 3-4x cost gap reported in #913 for multi-day sessions, on top of the WAL fingerprint bug fixed in #915.
Why the easy fixes do not work
- Anchoring the session call to last-activity instead of
started_at makes each day finalize while the still-running session's cumulative total sits in that day; the next day it moves. Every finalized day that contained it keeps a full copy: double counting proportional to session lifetime.
- Re-opening finalized days whenever a session's totals change breaks the carry-forward invariant that lets history survive session-file expiry, and turns the daily cache into a full re-derive on every run.
Direction that does work
A durable per-session snapshot ledger, in the spirit of what codex-credits does for pruned spans: persist (sessionId, lastSeenTotals, lastSeenAt) per hermes session; on each parse emit only the delta since the stored snapshot, timestamped now (the day the delta was observed), then advance the snapshot. Day D gets what actually happened on day D within one polling granularity, finalized days never need reopening, and lifetime figures still reconcile. Needs care on: db resets (totals shrink -> treat as new baseline, do not emit negative deltas), multiple profiles, and the recorded-cost precedence (delta the recorded cost the same way as tokens, flagging per #890 provenance).
Happy to take this once the approach is agreed.
Summary
Providers that account usage cumulatively at session level and anchor the whole session to
started_at(hermes today; any future session-cumulative provider) systematically lose usage in the durable daily history once the session outlives its start day.Split off from the cost-accuracy report in #913. Related to the cost-provenance design in #890.
Mechanism
ParsedProviderCallper session, carrying the session's lifetime token totals, withtimestamp = started_at.ensureCacheHydratedfinalizes days up to yesterday;gapStart = lastComputedDate + 1means a finalized day is never re-derived (by design, carry-forward is what keeps history alive after session files expire).buildDurablePeriodtakes historical days exclusively from the daily cache; the live parse only feeds today's bucket and detail views.So for a session that starts on day D and keeps running:
started_atis outside today's range. The growth lands nowhere. It is permanently absent from report/overview/menubar headline totals.For an agent whose sessions are long-lived conversations resumed across days (exactly how hermes is used), this is not an edge case; over weeks the durable history can drift far below true usage. It is the likely remaining contributor to the 3-4x cost gap reported in #913 for multi-day sessions, on top of the WAL fingerprint bug fixed in #915.
Why the easy fixes do not work
started_atmakes each day finalize while the still-running session's cumulative total sits in that day; the next day it moves. Every finalized day that contained it keeps a full copy: double counting proportional to session lifetime.Direction that does work
A durable per-session snapshot ledger, in the spirit of what codex-credits does for pruned spans: persist
(sessionId, lastSeenTotals, lastSeenAt)per hermes session; on each parse emit only the delta since the stored snapshot, timestamped now (the day the delta was observed), then advance the snapshot. Day D gets what actually happened on day D within one polling granularity, finalized days never need reopening, and lifetime figures still reconcile. Needs care on: db resets (totals shrink -> treat as new baseline, do not emit negative deltas), multiple profiles, and the recorded-cost precedence (delta the recorded cost the same way as tokens, flagging per #890 provenance).Happy to take this once the approach is agreed.