Skip to content

feat(core,llm,memory): add per-message usage/cost tracking#6628

Merged
bug-ops merged 1 commit into
mainfrom
feat/issue-6549/per-message-usage-cost-tracking
Jul 21, 2026
Merged

feat(core,llm,memory): add per-message usage/cost tracking#6628
bug-ops merged 1 commit into
mainfrom
feat/issue-6549/per-message-usage-cost-tracking

Conversation

@bug-ops

@bug-ops bug-ops commented Jul 21, 2026

Copy link
Copy Markdown
Owner

Summary

  • Adds a durable usage_records table (migration 115, SQLite + PostgreSQL) that records a per-LLM-call usage ledger (tokens, cost, latency, TTFT/TTFB, tokens/sec), additive to CostTracker's existing provider-keyed daily aggregate.
  • Every production call site that feeds CostTracker::record_usage also writes a paired row: the turn loop (message_id-linked to the persisted assistant message), the orchestration planner and aggregator, and verifier-ensemble members (the latter three carry message_id = NULL, since no persisted conversational message exists for them). Current-day SUM(cost_cents) reconciles with CostTracker.current_spend().
  • ttft_ms is true time-to-first-token on the one production streaming path (speculative decoding, captured at SpeculativeStreamDrainer::drive's stream-consumption point) and a time-to-first-byte proxy elsewhere, measured per-attempt inside the shared retry helper so a retried call is never inflated by backoff sleep. NULL only for the in-process Candle backend and for RouterProvider (a pre-existing, unrelated usage-propagation gap, tracked as a follow-up, not fixed here).
  • No new config surface — usage-row writes follow the existing [cost] enabled gate and are inline-awaited on the same call sites that already update CostTracker; no new tokio::spawn.

Closes #6549

Test plan

  • cargo +nightly fmt --check
  • cargo clippy --profile ci --workspace --all-targets --features "desktop,ide,server,chat,pdf,scheduler,testing" -- -D warnings
  • cargo nextest run --config-file .github/nextest.toml --workspace --features "desktop,ide,server,chat,pdf,scheduler" --lib --bins — 15041 passed, 0 failed
  • RUSTFLAGS="-D warnings" RUSTDOCFLAGS="--deny rustdoc::broken_intra_doc_links" cargo doc --no-deps --workspace --features "desktop,ide,server,chat,pdf,scheduler"
  • cargo test --doc --workspace --features "desktop,ide,server,chat,pdf,scheduler"
  • gitleaks protect --staged --no-banner --redact — no leaks
  • New tests: AnyProvider/MaskedProvider TTFT delegation, retry TTFB-not-inflated-by-backoff, pending-usage no-leak-on-filtered-persist, price_of fallback parity, reconciliation invariant (turn + planner), disabled-cost-tracker gating
  • Testing playbook: .local/testing/playbooks/per-message-usage-cost-tracking.md
  • Coverage status row added (Untested — implemented, no live agent session run yet)

@github-actions github-actions Bot added enhancement New feature or request size/XL Extra large PR (500+ lines) documentation Improvements or additions to documentation llm zeph-llm crate (Ollama, Claude) memory zeph-memory crate (SQLite) rust Rust code changes core zeph-core crate labels Jul 21, 2026
@bug-ops
bug-ops enabled auto-merge (squash) July 21, 2026 00:46
@bug-ops
bug-ops force-pushed the feat/issue-6549/per-message-usage-cost-tracking branch 2 times, most recently from 2b60f79 to 87d4eb7 Compare July 21, 2026 00:57
Every LLM call that feeds CostTracker::record_usage now also emits a
durable usage_records row (migration 115, SQLite + PostgreSQL): input/
output/cache tokens, cost in cents, latency, TTFT/TTFB, and tokens/sec,
queryable independently of the running session/daily aggregate.

Conversational turns link message_id to their persisted assistant
message; planner, aggregator, and verifier-ensemble calls carry
message_id = NULL since they produce no conversational message. Every
row is priced via CostTracker::price_of, the same formula the daily
aggregate uses, so current-day SUM(cost_cents) reconciles with
CostTracker.current_spend().

ttft_ms is true time-to-first-token on the one production streaming
path (speculative decoding, captured at SpeculativeStreamDrainer's
stream-consumption point) and a time-to-first-byte proxy elsewhere,
measured per-attempt inside the shared retry helper so a retried call
is never inflated by backoff sleep. AnyProvider/MaskedProvider/
TriageRouter delegate last_ttft_ms to the active inner provider.

Usage-row writes are inline-awaited on the same call sites that
already update CostTracker, gated on the existing [cost] enabled
switch - no new tokio::spawn, no new config surface.

Closes #6549
@bug-ops
bug-ops force-pushed the feat/issue-6549/per-message-usage-cost-tracking branch from 87d4eb7 to 348c0ff Compare July 21, 2026 01:01
@bug-ops
bug-ops merged commit 371528a into main Jul 21, 2026
43 checks passed
@bug-ops
bug-ops deleted the feat/issue-6549/per-message-usage-cost-tracking branch July 21, 2026 01:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core zeph-core crate documentation Improvements or additions to documentation enhancement New feature or request llm zeph-llm crate (Ollama, Claude) memory zeph-memory crate (SQLite) rust Rust code changes size/XL Extra large PR (500+ lines)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

no per-message usage/cost tracking — only session/daily aggregate, TTFT and tok/s are ephemeral UI state

1 participant