DEV Community

Ankan Mondal
Ankan Mondal

Posted on

Metering the Agentic Coding Loop: Per-Task Token Accounting for Claude Code

You can already get usage.input_tokens back from an API call. What you can't get is the cost of JIRA-1234 — a multi-turn, tool-calling, cache-heavy agent session that spanned two days and three sittings.

That's the instrumentation gap, and Claude Meter closes it at the layer where you actually work: the CLI/IDE coding loop. This post is the engineer's cut — the cost model, the hook architecture, and the token math that makes a 452K-token session cost 34 cents.

The attribution gap, stated precisely

Token spend is a first-class line item now, but observability for it is stratified by how the model is invoked:

Invocation surface Who owns the request loop Per-work-unit attribution
Direct API integration Your code usage object + your own tags
Chat / session status UIs The vendor UI ~ Per-session, not per-work-unit
CLI / IDE agentic coding The agent (Claude Code) The blind spot

The reason the blind spot exists is structural: in an agentic coding session, you sit above the API. The tool owns the request loop, so you have no natural seam to hang instrumentation on. And the work isn't a request — it's a trajectory:

   Prompt
     │
     ▼
 ┌─────────────┐   tool call (read/edit/bash)
 │  Agent turn │ ──────────────────────────────┐
 └─────────────┘                                ▼
     ▲                                  ┌────────────────┐
     │        tool result into context  │  Tool executes │
     └──────────────────────────────────└────────────────┘
     │
     ▼
 Final answer
Enter fullscreen mode Exit fullscreen mode

Every loop iteration re-sends accumulated context, spawns tool calls, and burns tokens across several distinct price classes. That trajectory maps cleanly onto a ticket — but nothing connects the token stream to the ticket. Wiring that link is the entire product.

The cost model: how tokens become dollars

The naive mental model — cost = tokens × price — is wrong for agentic sessions, because not all tokens are billed at the same rate. There are (at least) four price classes:

four price classes

where T_in, T_out, T_cw, T_cr are input, output, cache-write (creation), and cache-read token counts. For a Sonnet-class model the price vector is roughly:

Class Symbol Price ($/M tokens) Relative to input
Input (fresh) p_in $3.00 1.0×
Output p_out $15.00 5.0×
Cache write p_cw $3.75 1.25×
Cache read p_cr $0.30 0.1×

That last row is why metering matters. In a long coding session the same context gets re-sent on every turn, so without prompt caching your input cost grows roughly quadratically in turns — turn 1 re-sends 1 chunk, turn 2 re-sends 2, ..., turn N re-sends N:

growing input cost

for N turns each adding Δ tokens of context. Prompt caching collapses the re-sent prefix to the cache-read rate — one tenth the price — so the cache hit ratio becomes the dominant lever on session cost:

cache hit ratio

And the savings from caching are exactly the delta you didn't pay at full price:

savings

A generic billing dashboard reports one blended number. A meter that separates these four classes tells you why a task was cheap or expensive — and whether your context strategy is actually hitting cache. We validate all of this against a real run below.

Architecture: a Stop hook, a state file, a command

Claude Meter is a Claude Code plugin (JavaScript, Node ≥ 18) installed from a marketplace. Naming to keep straight: claude-meter is the marketplace/repo; session-manager is the plugin you install, which exposes /session-manager:meter. Three moving parts:

 ┌──────────────────────────── Claude Code session ────────────────────────────┐
 │                                                                              │
 │   every turn ──► Stop hook (hooks.json)                                       │
 │                     │  reads local transcript                                │
 │                     │  accumulates 4 token classes + activity                │
 │                     ▼                                                         │
 │            .claude/sessions/active.json   ◄── live state                      │
 │                     │                                                         │
 │   end / clear ──────┴────► .claude/sessions/name-id.json   ◄── archive        │
 └──────────────────────────────────────────────────────────────────────────────┘

   Slash command  ── /session-manager:meter ─► reads/controls state
   Skill          ── natural-language trigger ─► same underlying logic
Enter fullscreen mode Exit fullscreen mode
  1. The Stop hook — registered via hooks/hooks.json, fires after every turn, reads the local transcript, and accumulates the four token classes plus activity counters into the active session. This is why tracking is zero-setup: the hook is the collector, and it's already wired.
  2. Session state — the live tally lives in .claude/sessions/active.json; end/clear archives it to .claude/sessions/name-id.json, so history is preserved and queryable.
  3. Command + skill — drive it with the /session-manager:meter slash command, or trigger the same logic via a natural-language skill.

Pricing resolves from the LiteLLM model-pricing list, fetched once and cached locally for 24h. If the fetch is blocked (corporate firewall), it falls back to a hardcoded price table — tracking degrades gracefully and no session data is ever transmitted off-box. Everything above is computed locally against the local transcript.

The data model: what a session actually stores

The archived session is plain JSON you own—no black box, nothing exfiltrated. Every number the report prints is a field you can inspect yourself:

The raw archived session file — token counts per class, activity counters, estimatedCostUSD, cache savings, plus the git branch and commit captured at session start.

The schema is essentially the cost-model inputs plus provenance:

{
  "label": "xyz",
  "inputTokens": 0,            // T_in
  "outputTokens": 0,           // T_out
  "cacheCreationTokens": 0,    // T_cw
  "cacheReadTokens": 411505,   // T_cr
  "turns": 11,                 // N
  "toolCalls": 10,
  "bashCommands": 7,
  "estimatedCostUSD": 0.3379,  // C_session
  "cacheSavingsUSD": 1.1111,   // S
  "gitBranch": "...",          // provenance: pins cost to repo state
  "gitCommit": "..."
}
Enter fullscreen mode Exit fullscreen mode

Capturing gitBranch + gitCommit at start is the detail that makes a session genuinely tied to a moment in your repo's history — cost attribution with a commit anchor, not just a label.

Reading the meter: a worked example

Here's a real collated report from /session-manager:meter stat xyz:

One session: 452,265 total tokens, $0.3379 actual cost. Cache read = 411,505 tokens (91% of all tokens), saving ~$1.11 versus full input price. Also 11 turns, 10 tool calls, 7 bash commands.

Let's plug the real numbers into the model and confirm it.

numbers

i.e. a blended rate 4× below the fresh-input rate of $3/M — a single scalar that tells you your context strategy is doing its job. That's the difference between a bill and a diagnostic: the meter doesn't just say a task was cheap, it shows you the mechanism (ρ ≈ 0.91) that made it cheap.

Modes and the work-unit lifecycle

Invoke /session-manager:meter with no argument for the menu, or pass a mode directly:

The /session-manager:meter menu — eight modes in plain English; reply with a number or keyword.

Mode What it does Maps to
start [label] Begin a tracked session, labeled with the ticket task kickoff
show Live metrics mid-work in-flight monitoring
end Stop + print final report task done
clear Archive current, start fresh context reset
resume <name> Continue an archived session multi-sitting work
stats <name> Aggregate all runs for a name into one total durable roll-up
token-breakdown [name] Attribute tokens: thinking / replies / tools root-causing cost

A typical loop:

/session-manager:meter start "JIRA-1234"   # kickoff
/session-manager:meter show                # peek at live ρ and cost
/session-manager:meter end                 # final report
# later, across sittings:
/session-manager:meter resume "JIRA-1234"
/session-manager:meter stats  "JIRA-1234"  # one honest number for the ticket
Enter fullscreen mode Exit fullscreen mode

Two design choices carry the weight:

  • resume + stats make the work unit durable: a ticket spanning three sittings across two days still rolls up into a single figure — C_ticket = Σ C_session_i over all runs sharing a label.
  • token-breakdown turns the meter into a profiler. Knowing a task spent its budget on thinking tokens vs. tool churn tells you something actionable about how the work was structured — the same way a flame graph tells you where CPU went.

Why quantification compounds

Measurement changes behavior. Once per-task cost is visible, value compounds on two timescales.

Short term

  • Immediate per-task cost. The instant end runs, you have tokens, dollars, and wall-clock for the ticket — no monthly-invoice lag.
  • Live course-correction. show mid-session catches a runaway while it's running: if a "simple" fix is 5× the tokens you expected, that's a signal (bad context, thrashing, over-broad prompt) before you burn more.
  • Like-for-like comparison. Session ≙ work unit, so "this refactor cost 2× that one — why?" becomes answerable via token-breakdown.
  • Zero-friction adoption. Tracks out of the box, stores locally, adds no external dependency — the cost of starting to measure is ~0.

Long term

  • Baselines per task class. Aggregate enough sessions and a bug fix trends at X tokens, a migration at Y. Estimates become distributions, not vibes.
  • Defensible ROI. Pair C_session against elapsed time: this task cost $C and saved H hours. That's the sentence finance actually wants.
  • Real optimization targets. Attribution → attack: surface token-hungry task classes, tune prompt/context strategy (drive up ρ), and measure whether the number moved.
  • Forecasting. Per-work-unit history turns "what will AI-assisted dev cost next quarter?" into a projection grounded in your own tickets.
  • Cultural shift. When engineers can see the meter, token efficiency gets scoped in — the way visible latency dashboards made teams performance-aware.

You cannot optimize what you cannot measure. Claude Meter's contribution is narrow but foundational: it makes token cost measurable at the one place it was invisible — the CLI/IDE coding session — and pins that measurement to the unit of work the rest of the org already speaks in.

Try it

Claude Meter is open source under the MIT license. Start a session named after your next ticket, end it when you're done, and read the receipt.

Repo, install instructions, and docs → github.com/ankan4445/claude-meter

If this was useful, a ❤️ or 🦄 helps, and I'd love to hear how per-task metering changes the way you scope work.

Top comments (0)