Skip to main content

agemetry

A ticker plant for coding agents. Local-first, append-only telemetry store for Claude Code, Codex, GitHub Copilot (CLI and VS Code), Cursor and (soon) every other coding agent — with one schema and one SQL view over all of them.

feed handlers        tickerplant log            HDB                     query
──────────────       ────────────────           ───────────────         ──────────────
Claude Code hooks ─┐
Codex rollouts    ─┤
Copilot sessions  ─┼─▶  ~/.agemetry/log/   ─▶  ~/.agemetry/hdb/  ─▶  DuckDB `events`
VS Code chat      ─┤
Cursor            ─┘    YYYY-MM-DD.jsonl         date=YYYY-MM-DD/        canned queries,
                        (today, stdlib-only)     events.parquet (zstd)   MCP, replay

Your agent logs rot in ~/.codex/sessions, ~/.claude/projects, ~/.copilot, VS Code's and Cursor's storage. Nothing compacts, indexes, or joins them. agemetry treats agent activity as what it is — a high-rate, timestamped, append-only event stream — and stores it the way market data is stored: a hot log for today, columnar partitions for history, and a single query surface over both.

Nothing leaves your machine. Credentials that show up in tool output (API keys, tokens, private keys, passwords in URLs and config) are replaced with [REDACTED:<kind>] before anything is stored. That covers recognisable formats, not every possible secret, so --redact stays the rule before sharing.

Data stored before scrubbing existed can be cleaned with agemetry scrub --rewrite. It rewrites the stored files with each credential replaced and nothing else changed: same rows, ids and timestamps. It's the one exception to append-only. The agents' own transcripts aren't agemetry's to edit, so rotate any credential that leaked.

Install

One line, with uv (or pipx install with the same argument):

uv tool install "agemetry[mcp]"
agemetry install          # Claude Code hooks for every session
agemetry doctor           # checks hooks, adapters, MCP, billing and the store

uv tool upgrade agemetry updates it. From a clone: pip install -e ".[dev,mcp]".

Quick start

agemetry install          # hooks for every session, in ~/.claude/settings.json (idempotent)
agemetry doctor           # is everything wired up and is data arriving?
agemetry scrub            # credentials stored before scrubbing existed (--rewrite removes them)
agemetry ingest           # one-off backfill of Claude Code transcripts (and Codex, which every command also reads)
agemetry q cost           # cost & tokens by day / provider / model
agemetry q tools          # failure rate and p50/p95 latency per tool
agemetry q retries        # same tool on same file 3+ times = thrash hotspot
agemetry q sessions       # roll-up per session
agemetry q context --session <id>   # context-pressure timeline
agemetry sql "SELECT ..." # anything, over the `events` view
agemetry report           # one HTML page over everything, opened in your browser
agemetry top              # live view of what your agents are doing right now (q quits)
agemetry compact          # closed days' JSONL -> Parquet (also done before every command)

No cron or timer is needed: every command, and every MCP tool call, first drains the Claude Code spool, reads new Codex rollout lines and compacts closed days.

The schema is the product

One wide, flat, nullable table. Provider-specific junk goes in raw (JSON) until two providers need a column. See agemetry/schema.py.

column notes
ts, seq µs UTC; seq breaks ties
event_id derived from the source (spool line, rollout line, API message id); re-reading a source after a crash gives the same id, and the events view and compaction keep one row per id
provider, session_id, parent_session_id subagents link to parents
event_type session_start/end · prompt · assistant · tool_call · tool_result · token_usage · diff · permission · error · subagent_start/end · notification
model, tool, tool_call_id tool_call_id joins call ↔ result ↔ duration
repo, branch, cwd, file, host, user the "symbols" you group by
tokens_in/out/cache_read/cache_write, cost_usd from transcripts / token_count events
duration_ms, lines_added/removed, ok tool outcome
text, raw truncated human text; full payload for replay

Adapters

  • Claude Code — hooks push events live, and agemetry ingest claude backfills token usage from existing transcripts. Details below.

  • Codex — polls ~/.codex/sessions/**/rollout-*.jsonl from saved offsets (checked against Codex CLI 0.166.0). exec tool calls carry the shell command they ran; each command is also a shell tool_result with its exit code. Model comes from turn_context, branch from .git. Injected context (<environment_context>, developer instructions) is a notification, not a prompt. Token counts are normalised to Claude Code's meaning: tokens_in excludes cached tokens. Codex logs no cost; cost_usd is priced from prices.json. Unknown lines are kept as notification with raw intact.

  • GitHub Copilot (provider = 'copilot'), two sources; session_start.text says which client:

    • Copilot CLI and VS Code's agent host (its client_name, e.g. vscode-agent-host) — tails ~/.copilot/session-state/<id>/events.jsonl; repo and branch from its workspace.yaml. Tool calls with success and duration, permission prompts, apply_patch line counts. Copilot reports usage in AI units; one is a $0.01 AI Credit, so cost_usd is Copilot's own figure, not a price-table guess. Token totals arrive at session end.
    • VS Code chat sessions (vscode-chat) — the files VS Code wrote before its agent host, under workspaceStorage/*/chatSessions/. Each turn is read once it finishes. Model is the one Copilot actually routed to. Terminal commands carry exit codes and durations; other tools have no success flag in these files. Tokens have no cache split, cost_usd comes from copilotCredits where recorded, and branch is unknown.
  • Cursor — reads agent and chat sessions from Cursor's own database (globalStorage/state.vscdb), from a copy so the running app is never touched, and only when it has changed. Repo from the workspace, branch from Cursor's tracked git repos, tool calls with their targets and outcomes (completed, error, rejected, cancelled), lines added and removed per session. Cursor keeps no token counts or costs locally (it bills on its servers), no tool end times and no exit codes, so those stay empty.

Every adapter keeps the original payload in raw, so a renamed upstream field costs a null column, not lost data.

Claude Code hooks

A hook runs on every tool call, so it has to be close to free. The hook does no work beyond recording:

  1. Per tool call (PostToolUse, UserPromptSubmit, ...): a ~60-line C program, agemetry/hook/spool.c, appends the raw payload with a timestamp to ~/.agemetry/spool/YYYY-MM-DD.jsonl in a single write() and exits 0. It never parses its input. About 2 ms, against 1.3 ms for spawning /usr/bin/true and ~32 ms for a Python hook.
  2. Once per turn (Stop, SubagentStop, SessionEnd): the Python hook drains the spool into events and tails the transcript for token usage.
  3. Before any query, the CLI drains whatever is left.

agemetry install compiles the C program with the system cc into ~/.agemetry/bin/. Without a compiler (or with --python-only) every hook runs the Python version instead: same results, ~32 ms per call.

option choices
--scope user (default, every session on the machine), local (this repo, .claude/settings.local.json, not committed), project (this repo, .claude/settings.json, committed)
--events minimal (default: one hook per tool call) or full (adds PreToolUse, PermissionRequest, Notification, PreCompact)

agemetry uninstall --scope <scope> removes only agemetry's hooks. Hooks you wrote yourself are left alone. Windows currently gets the Python hook.

Billing: what you actually pay

cost_usd on every event is the usage at list prices: what those tokens would cost on the API. Many people don't pay that. Subscriptions cost the same however much you use them, and enterprises negotiate rates. Tell agemetry your plan and it prices everything accordingly when you read. Stored events never change, so you can edit a plan at any time.

agemetry billing set claude_code --subscription 200 --name Max --start 2026-09-01
agemetry billing set claude_code --subscription 90 --currency GBP --fx 1.34   # priced in pounds
agemetry billing set copilot --subscription 19 --name Business
agemetry billing set codex --discount 20        # or --rate 0.8: 80% of list price
agemetry billing                                # last 30 days: list value vs paid, per provider
agemetry billing unset codex

A subscription is prorated over whatever range you look at: 30 days of $200 a month is $197.13. Plans live in ~/.agemetry/billing.json.

To see every amount in your own currency, in the CLI and the report, set a display currency. Stored data stays in dollars, and USD turns it back off:

agemetry billing currency GBP --fx 1.34

List prices are in US dollars, so a plan priced in another currency is converted at the rate you give with --fx (US dollars per unit). The plan keeps the original amount, the rate and the date it was set. agemetry doesn't fetch rates; set the plan again to update one.

Usage limits (recorded, not shown yet)

agemetry records subscription usage windows as usage_limit events:

  • Claude's 5-hour and 7-day windows, from the figures Claude Code caches in ~/.claude.json
  • Codex's limits, from its rollouts agemetry q limits lists the latest readings.

They are kept out of the report, agemetry top, the MCP tools and alerts. Claude Code refreshes its cache only now and then, and a reading can be days old (one on this machine was 8 days old, and both windows had reset since). A stale "29% used" shown as current, or an alert on it, would mislead. The history is still recorded, so this can come back once there's a live source.

Outcomes and cost per commit

agemetry q outcomes  # sessions by outcome: PR opened, committed, edited not committed, explored
agemetry q commits   # recent commits with the agent cost behind each

agemetry reads git log (read-only) for every repo its agents worked in, found from the sessions' working directories. A linked worktree counts as its main repo. Commits go in their own commits view, not in events.

  • A commit's cost is the agent work in its repo since the previous commit.
  • A session's outcome is the strongest of these:
    • PR opened: a successful gh pr create in a shell tool
    • committed: its repo got a commit while it ran or within 30 minutes after
    • edited, not committed
    • explored
  • Subagent work counts towards its parent session.

The report's Outcomes card shows sessions by outcome, and cost per commit at list price and under your plans. Commit messages are stored, scrubbed, for the CLI, and never put in the report.

Live view

agemetry top          # refreshes every 3 s; q quits, r refreshes now
agemetry top --once   # one snapshot, for scripts
  • Header: today's spend at list prices, in your display currency, and today's active time.
  • One row per session active in the last 30 minutes: repo and branch, time since its last event, model calls, tool calls and failures, cost, current context size and last tool. Subagents sit under their parent.

Alerts

agemetry alerts set --session-cost 20    # a session passes $20 at list prices
agemetry alerts set --daily-cost 50      # a day passes $50 at list prices
agemetry alerts set --failures 15        # a session has 15 failed tool calls
agemetry alerts                          # rules and recent alerts (0 turns a rule off)
agemetry alerts test                     # see what a notification looks like

Rules are checked at the end of every Claude Code turn, inside the hook that already runs then. The check is stdlib only and uses no database. Each alert fires once per session or day, as a local desktop notification (macOS, or notify-send on Linux). Nothing is sent over the network.

Time in use

agemetry q time      # per day and provider: active hours, agent hours, tokens, cost per active hour
  • Active time is wall-clock: the stretches where events arrive no more than 5 minutes apart. Parallel sessions and subagents are merged, so it can never exceed the clock.
  • Agent time sums each session's active time, so three agents working for an hour count three hours. The gap between the two is how much your agents ran in parallel.

Report

agemetry report                 # writes ~/.agemetry/report.html and opens it
agemetry report --redact        # repo, branch and file names become repo-1, branch-1, file-1
agemetry report --out x.html --no-open

One self-contained HTML file covering:

  • what you paid under your plans, next to the list-price value (see Billing below)
  • active time and cost per active hour, sessions, tool calls, failure rate and tokens
  • API value, tokens or active time over time, by provider
  • spend by model, across providers
  • spend by repo, each opening to show its branches
  • tool reliability (failure rate, p50/p95 latency)
  • a session timeline: context, tool calls (failures marked) and prompts on active time
  • retry hotspots

A date-range filter (7/30/90 days, all time) re-scopes every number, and #range=30&m=time in the URL opens on that range and measure. Every chart has a table view. Light and dark themes are both supported. The page embeds aggregates only, never prompt or response text, and loads nothing from the network. Use --redact before sharing a screenshot.

Team mode

agemetry team init ~/Dropbox/agemetry-team   # once per machine: the shared folder and your member name
agemetry team push                           # share your last 90 days of aggregates
agemetry team report                         # everyone in the folder, in one page
agemetry team status                         # who has pushed, and when
agemetry team demo --members 3               # synthetic teammates, to try it on your own
agemetry team demo --remove

The team folder is any directory everyone can reach: a synced folder or a network share. Each member owns members/<member>/, replaced whole on every push, with three small Parquet files and a meta.json:

  • daily sessions, prompts, tool calls and failures, tokens, list-price cost and lines changed, by provider, model, repo and branch
  • daily active and agent time, and what you paid under your own plans
  • your commits per repo per day, and the agent cost behind them

Only aggregates leave the machine: no prompts, responses, code, tool output, file paths, commit messages or session ids. Repo and branch names are shared, because that's what a team report groups by; team push --redact replaces them with placeholders. Only commits by your git email (git config user.email, or team init --email) are shared, so a repo two people work in isn't counted twice.

The team report shows paid and list-price spend, active time, sessions, commits and failure rate per member, spend over time stacked by member, and spend by repo and by model across the team. team demo builds teammates from your own aggregates: different amounts, days, model mix and plans, renamed repos, marked as demo on the page.

Replay: would another model have done the same?

agemetry replay --list                            # recent Claude Code sessions
agemetry replay <session> --steps 10              # dry run: the plan and an estimated cost, nothing sent
agemetry replay <session> --model claude-sonnet-5-5 --run   # calls the Anthropic API

Replay rebuilds a recorded Claude Code session from its transcript and samples its decision points (each API call the agent made). At each one it gives another model the conversation as it stood, with the session's own system prompt and tools, and compares that model's next action with what was recorded:

  • same: the same tool on the same file, command or pattern, or both stopped
  • same tool: the same tool on a different target
  • different: anything else

The recorded tool results stand in for running the tools, so nothing is executed and a replay can be repeated.

How faithful a replay is:

  • The system prompt, tool definitions, effort and max_tokens come from the transcript's own request records (Claude Code 2.1.29x and later). Decisions without them are skipped and counted.
  • After a compaction, the replayed model sees the summary onward, as the original did.
  • Messages are rebuilt from the main conversation. Per-turn context Claude Code injects outside it is not.
  • The original model's thinking isn't replayed.

A dry run reads the transcript locally and sends nothing. --run needs pip install -e ".[replay]" and an Anthropic API key or ant auth login. It sends that session's content (code, file contents, command output) to Anthropic and costs about the dry run's estimate. Prompt caching keeps it low, because each step's request extends the previous one. Results go to ~/.agemetry/replays/.

MCP server

Lets the agent ask about itself: "what has this branch cost me?"

pip install -e ".[mcp]"
claude mcp add agemetry -- agemetry mcp      # Claude Code

For Codex, add to ~/.codex/config.toml:

[mcp_servers.agemetry]
command = "agemetry"
args = ["mcp"]

Tools: branch_summary (pass the working directory), list_queries / run_query (the canned queries above), describe_events, and sql. Every tool is read-only. sql accepts one SELECT, on a DuckDB connection that can read only log/ and hdb/ in the store: no other files, no network, no extensions. Results are capped at 200 rows and 2000 characters per value. branch_summary reports both the list-price value and what the branch cost under your billing plans, shown in your display currency. A subscription is shared out by use: the branch's list value × (paid ÷ list value) for that provider over the same days.

Roadmap (in order)

  1. OpenTelemetry receiver: a local OTLP endpoint, so any agent that emits OpenTelemetry is captured without its own adapter.
  2. Report polish: the screenshot that gets it shared. Storage is invisible; the picture isn't.
  3. Adapters: Claude Code, Codex, Copilot (CLI, VS Code) and Cursor are checked against real sessions. Next: Gemini CLI, OpenCode.
  4. Replay: decision replay against another model is in (above). Next: a same-model baseline, replay results in the report, and multi-step replays that let the new model's own choices play out.
  5. Team mode: a shared team folder and team report are in (above). Next: push to S3/R2 directly.

Development

CI runs the tests on Linux with Python 3.10 and 3.13, and with the oldest supported duckdb (1.2.0), in one job. macOS runs weekly and on demand.

pip install -e ".[dev]"
pytest                      # end-to-end, plus the C hook under address/undefined-behaviour sanitizers
AGEMETRY_HOME=/tmp/x agemetry stats   # point at a scratch store

Licensed under the Apache License 2.0. See LICENSE.

Metadata

Release files for agemetry 0.0.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for agemetry 0.0.1
File Size Uploaded Provenance Actions
agemetry-0.0.1.tar.gz 136.2 kB Without provenance Details for agemetry-0.0.1.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for agemetry 0.0.1
File Interpreter ABI Platform Actions
agemetry-0.0.1-py3-none-any.whl Python 3 none any Details for agemetry-0.0.1-py3-none-any.whl

Total release size: 249.0 kB

Release files / agemetry-0.0.1.tar.gz

Download URL agemetry-0.0.1.tar.gz
Size 136.2 kB
Tags Source
SHA-256 checksum
How to use checksums
bdb7e31cfcc8e27ad4349cec78bcd0e679ea11d6a6c8ba5235449087b4a124cb
BLAKE2b-256 checksum
How to use checksums
f7e2042fc242103aafff47ccd54e1f94ea86ed9369696dd9a34887b6a09b8adb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.2

Release files / agemetry-0.0.1-py3-none-any.whl

Download URL agemetry-0.0.1-py3-none-any.whl
Size 112.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
f2a73016d53782efb67dcf5e928bfee79658157812930d441cd8b78c9d82eb81
BLAKE2b-256 checksum
How to use checksums
0ceff1f453fb08eb70a65de3adb9ebcb1aaaaf88ce87fffcfdfa9cb35bf471b7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.2

Security for agemetry 0.0.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.0.1 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page