Skip to content

feat(desktop): add harness to NIP-AM agent-usage breakdown#2790

Merged
wpfleger96 merged 3 commits into
duncan/agent-usage-archivefrom
duncan/agent-usage-harness
Jul 25, 2026
Merged

feat(desktop): add harness to NIP-AM agent-usage breakdown#2790
wpfleger96 merged 3 commits into
duncan/agent-usage-archivefrom
duncan/agent-usage-harness

Conversation

@wpfleger96

Copy link
Copy Markdown
Member

Stack: #2035 → this PR

What

Group the per-agent model breakdown by (harness, model) instead of model alone, so the same model running under two different harnesses (e.g. claude-sonnet via goose vs claude-code) produces two distinct rows rather than collapsing into one.

Changes

Rust / SQLite

  • agent_metric_index: add nullable harness TEXT column. Schema migration M1: ALTER TABLE ... ADD COLUMN + delete-then-backfill rebuild so all existing rows get harness populated from retained archived_events.raw_json — no data loss; ingest and backfill share the same from_payload parser (frozen-plan requirement preserved).
  • AgentMetricIndexRow: parse harness from AgentTurnMetricPayload.harness (REQUIRED field per NIP-AM), write/read through all store paths (ROW_COLUMNS, INSERT, row_from_sql).
  • AgentScope.models grouping key widened from Option<String> to (Option<String>, Option<String>) i.e. (harness, model). Sort tiebreak: harness ascending → model ascending, None last in each.
  • ModelUsage wire type: add harness: Option<String> field.

Frontend

  • tauriArchive.ts / bridge.ts: add harness to AgentUsageModel type.
  • AgentUsageFocusedView: render harness as a dimmed sub-label next to each model name on breakdown rows. null harness (pre-migration data or unknown) renders no label — single-harness data is visually unchanged.
  • agentUsage.ts sortModelsByKnownTotal: tiebreak updated for compound (harness, model) key.

Tests

  • Rust: collapse-fix test (same model / two harnesses → two rows), migration test (old-shape rows get harness populated after rebuild), harness sort coverage.
  • TS: sortModelsByKnownTotal harness tiebreak tests, same-model two-harness sort test.
  • E2E: bridge.ts fixture type updated, agent-usage.spec.ts harness assertion, agent-usage-screenshots.spec.ts shot 02 harness-label visibility check.

Gates

All green locally:

  • just desktop-check
  • just desktop-typecheck
  • just desktop-build
  • just desktop-test (3487 pass, 0 fail) ✓
  • cargo test in desktop/src-tauri (1575 pass, 0 fail) ✓

Group the per-agent model breakdown by (harness, model) instead of
model alone, so the same model running under two different harnesses
(e.g. claude-sonnet via goose vs claude-code) produces two distinct
rows rather than collapsing into one.

Changes:
- agent_metric_index: add nullable TEXT harness column; schema
  migration (M1) does ADD COLUMN + index rebuild via the shared
  backfill parser so all existing rows get harness populated from
  retained raw_json — no data loss, ingest/backfill remain in sync
- AgentMetricIndexRow: parse harness from AgentTurnMetricPayload
  (REQUIRED field), write/read through all store paths
- AgentScope.models: widen key from Option<String> to
  (Option<String>, Option<String>) i.e. (harness, model); sort
  tiebreak is harness-ascending then model-ascending, None last in
  each
- ModelUsage wire type: add harness: Option<String> field
- AgentUsageFocusedView: render harness as a dimmed sub-label on
  each model row; null harness (pre-migration or unknown) renders
  no label so single-harness data is visually unchanged except for
  the label
- tauriArchive.ts / bridge.ts: add harness to AgentUsageModel type
- agentUsage.ts: update sortModelsByKnownTotal tiebreak for
  (harness, model) compound key
- Tests: collapse-fix integration test (same model / two harnesses
  → two rows), migration test (old-shape rows get harness after
  rebuild), harness sort tests, E2E fixture + assertion updates

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
@wpfleger96
wpfleger96 force-pushed the duncan/agent-usage-harness branch from 0945825 to 7d11593 Compare July 25, 2026 00:13
npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 and others added 2 commits July 24, 2026 21:52
- M1 atomicity: wrap ALTER TABLE, full DELETE, per-scope rebuild, and
  archive_migrations marker in one unchecked_transaction so a crash
  before commit leaves no orphaned marker and the next open re-runs
  the whole migration from scratch.
- M1 worklist: derive (identity, relay) pairs from archived_events
  (kind 44200) instead of surviving index rows so an empty index
  cannot cause M1 to silently declare success with scopes missing.
- ALTER error handling: replace blanket let _ = ... with a PRAGMA
  table_info check; ALTER runs only when the harness column is absent
  and every other error propagates.
- New Rust tests via open_archive_db: legacy-file reopen populates
  harness + records marker, second open is idempotent, rolled-back
  mid-migration state re-runs to completion on next open.
- TS sort: replace localeCompare with ordinal < / > comparators so
  frontend ordering is locale-independent and matches Rust String::cmp.
- e2eBridge.ts: add harness: string | null to RawAgentUsageModel
  so the tauriArchive.ts mirror contract is accurate.
- Rust comparator: extract cmp_known_total + cmp_option_str_none_last
  helpers and ModelSortKey struct to eliminate the duplicated None-last
  ladder; also fixes the clippy type_complexity lint on the 4-tuple.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Split the M1 harness-migration tests from store_tests.rs into a new
store_migration_tests.rs so store_tests.rs falls back under the 1000-
line limit without a ratchet override; store_tests.rs is now ~858 lines
(checker: 858). The store_tests.rs override is removed; store.rs gains
one line for the new #[path] include (checker: 1044).

Add a one-line comment at sortModelsByKnownTotal noting the identifier
ordering domain is ASCII in practice so UTF-16 vs UTF-8 scalar
divergence for astral code points is accepted.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
@wpfleger96

Copy link
Copy Markdown
Member Author

Overview usage card

Agent list with daily-bar sparkline (known, partial, gap, and zero columns) and ranked token row — unchanged context for the PR.
01-overview-usage-section

Focused usage view — harness labels (the change in this PR)

Same agent open in the profile panel: claude-opus-4-5 under claude-code and claude-sonnet-4-5 under goose appear as separate rows with a dimmed harness sub-label each — proves the (harness, model) grouping and the collapse fix.
02-focused-usage-view

Multi-day bars

Overview card with 8 days of varied token data including a zero day — unchanged context for the PR.
03-daily-bars-multi-day

Empty state

Usage card when collection is enabled but no events have been archived yet — unchanged context for the PR.
04-empty-state

wpfleger96 pushed a commit that referenced this pull request Jul 25, 2026
@wpfleger96
wpfleger96 marked this pull request as ready for review July 25, 2026 16:04
@wpfleger96
wpfleger96 requested a review from a team as a code owner July 25, 2026 16:04
@wpfleger96
wpfleger96 merged commit 194ceb6 into duncan/agent-usage-archive Jul 25, 2026
25 checks passed
@wpfleger96
wpfleger96 deleted the duncan/agent-usage-harness branch July 25, 2026 16:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant