Analytics
PABLO keeps a local log of task lifetimes and agent runs, including tokens and cost, and summarises it with pablo stats or the dashboard's Analytics page. Nothing leaves your machine, and prompts are never stored.
Use it#
pablo stats # last 30 days, all projects
pablo stats --days 7 --project wallet-kit
pablo stats --type open-source # work | open-source | personal
pablo stats --agent ci-analyst # filter the agents table
pablo stats --format json # for scriptsThe same numbers are charted on the dashboard's Analytics page.
What it tells you#
- Task lifetimes
- Time from start to close (average, median, 90th percentile) and merge rate, per project.
- Agent runs
- Runs per agent, average runtime, tokens with their cache hit rate, accumulated cost, and how long the result waited for you before the task moved on.
- State dwell times
- Average time spent in each state, and how many times tasks entered it. A high count for
ci-redorrequest-changesmeans rework loops.
The cache hit rate is cache_read / (input + cache_read + cache_write), per run. If you're on a subscription plan, cost can legitimately stay at zero while token counts are real.
What is recorded#
Five kinds of events, one JSON line each:
| Event | When | Main fields |
|---|---|---|
task_opened | a task is created | initial state, issue (or none), prompt length, worktree |
state_entered | a task changes state | from, to |
agent_run_started | an agent is launched | run id, agent, backend, worktree |
agent_run_finished | the run is over | duration, tokens (input, output, reasoning, cache read, cache write, total), cost, models, session |
task_closed | a task is closed (merge or task:close) | lifetime, final state, merged or not, pull request, attempts per agent |
Each finished run is counted exactly once, even if the component that normally reports it crashed: the poller catches anything left unreported, and duplicates are ignored both when writing and when reading.
Privacy#
No prompt text is ever stored: only its length and a short, non-reversible fingerprint (16 hex characters of a SHA-256), used to match a run to the right OpenCode, Claude Code or Copilot session. Everything lives in ~/.pablo/analytics/.
Where tokens and cost come from#
PABLO reads usage from the agent runtime once a run is over. With OpenCode:
- Orca and headless runs: from OpenCode's local storage. When the run's fingerprint matches a session's first message, the match is
exact; otherwise every session created in that worktree since the launch is counted (window). - OpenChamber runs: from OpenChamber's own session list for the worktree (
window), because those sessions live in OpenChamber's server. - Nothing found: usage is empty (
none), and the timing is still recorded.
With Claude Code:
- Tokens come from the run's transcript,
~/.claude/projects/<worktree>/<session id>.jsonl(underCLAUDE_CONFIG_DIRwhen set): input, output, cache read and cache write, summed over the assistant messages, each counted once. Thinking tokens are part of output, so reasoning stays at 0. The models are recorded too. - Matching works as with OpenCode: the transcript whose first message matches the fingerprint is
exact, otherwise the worktree's transcripts since the launch are counted (window). - Cost is not measured and is recorded as 0: with a Pro or Max subscription a run has no price of its own.
With GitHub Copilot CLI:
- Tokens come from
~/.copilot/session-state/<session id>/events.jsonl(underCOPILOT_HOMEwhen set): the per-modelusageof the latestsession.shutdown(orsession.usage_checkpoint) event, giving input (minus cached), output, cache read and cache write. - Matching: the session whose prompt matches the fingerprint; otherwise only sessions whose recorded working directory is the worktree.
- Cost is recorded as 0: a Copilot plan bills credits, not a price per run.
Files#
~/.pablo/analytics/
├── <project>/
│ └── YYYY-MM.jsonl # one event per line, one file per month
└── analytics-errors.log # write failures; normally emptyRecording is best-effort: a failure goes to analytics-errors.log and never breaks a task operation. Set PABLO_ANALYTICS_DIR to store the log elsewhere.