Skip to content

Session-cumulative providers (hermes) lose post-finalization usage from the daily history #916

Description

@ozymandiashh

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

  1. The hermes provider emits one ParsedProviderCall per session, carrying the session's lifetime token totals, with timestamp = started_at.
  2. 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).
  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions