Normalize costs and find dead seats
Once two or more harnesses are emitting through Trove, their signals all
arrive in the same normalized Tier A schema. That’s what makes them
comparable: trove.harness.cost.usd from Claude Code sits on the same
axis as trove.harness.cost.usd from GitHub Copilot CLI, with no
vendor-specific exporter to build or maintain. Two questions get easy to
answer:
- Where is the money going? Compare spend per tool, per user, per week — apples to apples.
- Which paid seats are dead? Find licenses with zero activity so procurement can act before renewal.
The metrics you’ll use
Section titled “The metrics you’ll use”| Metric | Use it for | Key attributes |
|---|---|---|
trove.harness.cost.usd | Spend, normalized across vendors | model, cost.method (exact / estimated) |
trove.harness.events | Activity — turns, tool calls, edits | event.kind |
trove.harness.tokens | Volume behind the spend | direction, model |
Every data point also carries resource attributes set per harness:
harness.id, harness.name, and — once you’ve
added identity tags —
user.name and user.email. Those are the dimensions you group by.
Normalize cost across harnesses
Section titled “Normalize cost across harnesses”The goal is a single chart where each AI tool is a comparable series.
-
Enable the harnesses you want to compare and confirm each is flowing (its
harness.idshows up in the Logs tab and at your backend). Mixed coverage is fine — native-OTel and watcher-based harnesses both emit Tier A. -
Make sure cost is being emitted. On the Mappings tab, check that each harness’s
cost.usdrow is enabled. Where Trove can’t get a real cost from the harness it estimates from tokens and the rate table, marking those pointscost.method = estimated. Set your own model prices in the rate table if you run a custom or self-hosted model. -
Build a “spend by tool” view in your backend: sum
trove.harness.cost.usdgrouped byharness.idover a time window. Because the schema is shared, each harness becomes one comparable series in the same panel. -
Split exact vs. estimated by also grouping on
cost.methodso you know how much of the number is metered versus modeled. Estimated rows are directional; exact rows are billable-grade. -
Derive cost per turn by dividing
trove.harness.cost.usdby thechat.turnslice oftrove.harness.events(filterevent.kind = "chat.turn"), grouped byharness.id. Now “cost per interaction” is directly comparable between, say, Claude Code and Cursor.
Find dead seats
Section titled “Find dead seats”A “dead seat” is a paid license that produced no activity in a
window. The same harness.id-keyed (and, with identity tags,
user.email-keyed) stream that powers your cost dashboard surfaces them.
-
Tag signals with identity. In Settings, set
user.nameanduser.emailso each person’s activity is attributable. Roll this out across the team so the per-user view is complete. See Add identity tags. -
Define “active.” Pick a metric and window that mean “this seat is being used” — for example, at least one
trove.harness.eventspoint withevent.kind = "chat.turn"in the last 30 days. -
List who fired. In your backend, query the active set: distinct
user.email(optionally perharness.id) with at least one qualifying event in the window. -
Subtract from your roster. Compare that active set against your license roster for each tool. Anyone holding a paid seat who isn’t in the active set is a dead-seat candidate — by user, by tool, by week.
-
Act before renewal. Hand the list to procurement. Reclaiming a handful of unused Cursor or Copilot seats across a large org is often the fastest payback Trove delivers.
Why this works at all
Section titled “Why this works at all”Trove does the normalization at the collector, so your backend never sees five vendor dialects — it sees one Tier A schema with consistent attribute names. That’s the difference between “four dashboards nobody reconciles” and “one query that compares everything,” and it’s what lets finance and engineering look at the same numbers.