Skip to content

Usage ledger: single-writer idempotent events and metadata-only aggregation #1154

Description

@jonathanKingston

Summary

Agent usage is currently written from both the main-process chunk sink and the renderer IPC path. The ledger attempts to deduplicate those writes by comparing a new event only with the immediately preceding event within 250 ms.

That is not concurrency-safe. With two interleaved runs, the ordering can be:

  1. main A
  2. main B
  3. renderer A
  4. renderer B

Neither renderer copy matches the last ledger entry, so both duplicates are retained.

The main writer also derives projectId from the globally active project at record time. A background run that finishes after the user switches projects can therefore be attributed to the wrong project.

Finally, opening the Usage view calls loadAllProjectThreads(), folding every message in every configured project even though aggregation only consumes per-thread usage metadata. The event array is also cloned and rewritten in full for every event.

Relevant code

  • src/main/services/agent-chunk-sink.ts
  • src/main/services/storage/usage-ledger.ts
  • src/renderer/controller/agent.ts
  • src/shared/usage/usage-event.ts
  • src/shared/usage/aggregate-usage.ts
  • src/main/services/thread-store.ts

Plan

  1. Choose one authoritative writer

    • Record agent-stream usage in the main process only.
    • Keep renderer IPC only for genuinely renderer-originated legacy sources, or remove it if no such source remains.
  2. Add stable identity

    • Give each usage delta a run-scoped/event-scoped idempotency key.
    • Deduplicate by that key rather than timestamp adjacency.
    • Persist the owning projectId and threadId from the run context, never from activeProjectId.
  3. Make storage scale with events

    • Use an append-oriented ledger with bounded compaction, or a small indexed store.
    • Maintain daily/model/project aggregates if needed for fast views.
    • Keep the existing 90-day retention behavior explicit and testable.
  4. Aggregate from metadata

    • Read thread catalog/meta usage fields without folding messages, tool results, images, or subagents.
    • Prefer a project-level usage projection if repeatedly scanning metadata is still material.
  5. Cover concurrency

    • Add deterministic tests with interleaved usage chunks from multiple simultaneous runs and a project switch during a run.

Acceptance criteria

  • One agent usage chunk produces exactly one durable ledger event.
  • Interleaved chunks from concurrent runs cannot defeat deduplication.
  • A run remains attributed to its owning project after the active project changes.
  • Advisor/auxiliary model usage retains correct source and model attribution.
  • Building the Usage summary does not call loadAllProjectThreads or read message/blob contents.
  • Per-event work does not clone and rewrite an unbounded event array.
  • Existing ledger data remains readable or has a versioned migration.
  • Tests cover replay/idempotency, concurrency, project switching, retention, and corrupt records.

Related

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

area:performancePerformance, resource usage, and package sizearea:storagePersistence, threads, memories, and migrationbugSomething isn't workingperformancePerformance / CPU / memory issuepriority:p2Important issue to address soon

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions