Docs / Metrics
Metrics
How every number in Clepso is defined, where it comes from, and what it does not mean. These rules are the code's rules: each section names the files that implement it, and the three playgrounds run the same rules beside the code.
On this page
Where the numbers come from
| Source | What it gives | How fresh | Kept by Clepso |
|---|---|---|---|
| Claude Code status line | 5-hour and 7-day usage with reset times, current model, context window, session cost estimate, durations, lines changed, prompt-cache counts | Every status refresh while Claude Code runs | Yes, on this Mac only, after an allowlist removes everything else (paths, prompts, ids) |
Codex session files (~/.codex/sessions, optional) | 5-hour and weekly used % with reset times, plan type, model: only token_count events for Codex's own allowance, from the last 256 KB of the three most recently written files in the last 8 days' folders, and Codex's version from each file's first 4 KB. For Stats, tokens per day and model, from token_count and turn_context lines only | Each time Clepso checks, about every 30 seconds while on | Yes, the limits only, on this Mac. Prompts and replies in the same files are skipped without being parsed |
Claude Code statistics file (~/.claude/stats-cache.json, the data behind /stats) | Messages and sessions per day, tokens per model per day, tokens by kind per model (all time), sessions by hour | Up to yesterday: Claude Code folds each day in afterwards | No. Read when you open Stats, only if you turn it on; never stored or exported |
What the tokens are
A token is a small piece of text, roughly three or four characters, that models read and write. Claude Code counts four kinds.
| Kind | What it is | Typical share | Counts for level |
|---|---|---|---|
| Output | Text the model wrote: replies, code, tool calls. The model's own work. | Well under 1% | Yes |
| Input | New text sent to the model that was not already cached: your message, tool results, files it read. | Under 1% | No |
| Cache read | Earlier conversation the model re-reads from the prompt cache on every turn. Grows with the length of a conversation, not with the work done. | Most tokens, often 95% or more | No |
| Cache write | Conversation stored in the prompt cache so later turns can re-read it cheaply. | A few percent | No |
Totals (“15.8B tokens”, “79% of all”) add all four kinds, as /stats does. Levels use output only.
Levels
Level = 1 + the number of targets the model's all-time output has reached.
Targets: 1K, 2K, 5K, 10K, 20K, 50K, 100K … (1, 2, 5 in every power of ten).
LV 14
// Sources/ClepsoApp/Views/Details/ModelsSection.swift
static let targets: [Int64] = {
var all: [Int64] = []
var decade: Int64 = 1_000
while decade <= 1_000_000_000_000_000 {
all += [decade, decade * 2, decade * 5]
decade *= 10
}
return all
}()
init(output: Int64) {
let output = max(0, output)
let reached = Self.targets.prefix { $0 <= output }.count
self.output = output
self.level = reached + 1
self.floor = reached == 0 ? 0 : Self.targets[reached - 1]
self.next = reached < Self.targets.count ? Self.targets[reached] : Self.targets[reached - 1]Why output: it is what the model produced. Total tokens are dominated by cache reads, so a long conversation would level a model up without it doing more. Why fixed 1-2-5 targets: every next target is a round number you can read, and each level asks for 2 to 2.5 times the output of the one before, so levels keep meaning something at any scale.
Worked example: why “18.2M of 20M output” is progress to LV 15
A model has written 18.2M output tokens. The highest target reached is 10M, the 13th target, so it is LV 14. The next target is 20M, which makes LV 15. The level bar in its dossier runs from 10M to 20M: (18.2M − 10M) ÷ (20M − 10M) = 82%, with 1.8M to go.
The ladder
| Level | Output reached | Next level at | Output in this level |
|---|---|---|---|
| LV 1 | 0 | 1K | 1K |
| LV 2 | 1K | 2K | 1K |
| LV 3 | 2K | 5K | 3K |
| LV 4 | 5K | 10K | 5K |
| LV 5 | 10K | 20K | 10K |
| LV 6 | 20K | 50K | 30K |
| LV 7 | 50K | 100K | 50K |
| LV 8 | 100K | 200K | 100K |
| LV 9 | 200K | 500K | 300K |
| LV 10 | 500K | 1M | 500K |
| LV 11 | 1M | 2M | 1M |
| LV 12 | 2M | 5M | 3M |
| LV 13 | 5M | 10M | 5M |
| LV 14 | 10M | 20M | 10M |
| LV 15 | 20M | 50M | 30M |
| LV 16 | 50M | 100M | 50M |
| LV 17 | 100M | 200M | 100M |
| LV 18 | 200M | 500M | 300M |
| LV 19 | 500M | 1B | 500M |
| LV 20 | 1B | 2B | 1B |
| LV 21 | 2B | 5B | 3B |
| LV 22 | 5B | 10B | 5B |
| LV 23 | 10B | 20B | 10B |
| LV 24 | 20B | 50B | 30B |
| LV 25 | 50B | 100B | 50B |
The ladder continues the same way past LV 25. In the app, “How levels work” under Stats › Models (Arena) draws it as a pixel track: a cell per level, at least 1 to 20, each target under its cell, every model's character standing on its level and the chosen model's next level marked NEXT. On 30-day and 7-day ranges, levels still use all-time output, because Claude Code records tokens by kind for all time only.
Relevant source files
Sources/ClepsoApp/Views/Details/ModelsSection.swift
Rank and specialty
Rank: by level, then by output, then by total tokens. A higher rank never has a lower level.
Specialty ratio for a kind = (model's tokens of that kind ÷ model's total) ÷ (all models' tokens of that kind ÷ all models' total)
The specialty is the kind with the highest ratio, shown as, for example, “Reading input · 13× the party”: that model's share of input is 13 times the share across all models. If no kind reaches 1.1×, the model is an All-rounder. Every model mostly reads from cache, so the plain largest kind would say the same thing on every card; the ratio shows how each one differs.
Relevant source files
Sources/ClepsoApp/Views/Details/ModelsSection.swift (ModelSpecialty)
Agents per task
Optional, off until you turn it on in Settings. A task is one prompt: from the moment you send it until Claude stops. Clepso adds four background hooks to Claude Code (prompt submitted, subagent started, subagent stopped, Claude stopped) and counts the subagents each task starts, nested ones included.
| Figure | Meaning |
|---|---|
| Agents | Subagents started during the task. Each one counts once, matched by a hashed id, even if its events arrive out of order. |
| Types | Claude Code's built-in types by name, made readable (Explore (research), Plan, General, Claude Code guide, Status line setup). Your own and plugin agents count as Custom, unless you approve an agent's exact name in Settings › Claude Code: only then does the helper pass that name on. Approved names stay on this Mac; an account upload counts them as Custom. |
| Finished | Subagents that reported stopping. Background subagents can finish after the task ends. |
| Running | No stop seen yet, and the subagent started under six hours ago. “Running now” lists every such subagent from any task, with the task that started it; after 10 quiet minutes in its session one reads “Last seen”. |
| Used agents | Share of the last 30 days' tasks that started at least one subagent. |
| Agents per task | All subagents in 30 days ÷ all tasks in 30 days, tasks without subagents included. |
Claude Code hands every hook the whole event, your prompt included. The helper keeps only the event name, hashed session and agent ids and the type, and drops the rest before anything leaves it.
Relevant source files
Sources/ShimProtocol/HookFrame.swiftSources/Storage/Store+Agents.swift
Limits and the pace estimate
| State | When |
|---|---|
| Available | Under 80% used |
| Approaching limit | 80% or more |
| Nearly exhausted | 95% or more |
| Limit reached | 100% |
| Reset expected | The reset time has passed but no fresh reading has confirmed it. Clepso never says “ready” without one. |
| Stale | No reading recently; the last value is shown as “last seen” with its age. |
A state clears only after usage drops 3 points below its threshold, so it does not flicker.
Available
// Sources/UsageEngine/PoolReducer.swift
static func classify(_ used: Decimal, previous: LimitState, config: EngineConfig) -> LimitState {
if used >= config.exhausted { return .exhausted }
if used >= config.critical { return .approaching(.critical) }
if previous == .approaching(.critical), used > config.critical - config.hysteresis { return .approaching(.critical) }
if used >= config.warning { return .approaching(.warning) }
if case .approaching = previous, used > config.warning - config.hysteresis { return .approaching(.warning) }
return .availablePace estimate (always labelled “Estimate”): a straight line fitted by least squares through recent readings in the current window: the last 90 minutes for the 5-hour limit, the last 24 hours for the 7-day limit. It needs at least 3 readings over at least 10 minutes. At or below 0.5 points per hour usage counts as flat (“not rising”). It never looks further ahead than 8 times the history it rests on, so an hour of readings cannot predict two days. If the window resets before the line reaches 100%, it says roughly how much will be used by the reset.
// Sources/UsageEngine/Pace.swift
guard slope > flatSlope else { return .notRising }
let hoursToLimit = (100 - latest.usedPct) / slope
let hit = latest.at.addingTimeInterval(hoursToLimit * 3600)
let span = latest.at.timeIntervalSince(first.at)
let horizon = min(hit, resetsAt.flatMap { $0 > now ? $0 : nil } ?? hit).timeIntervalSince(latest.at)
guard horizon <= span * maxLookAhead else { return .notEnoughData }
if let resetsAt, resetsAt > now, hit >= resetsAt {
let projected = latest.usedPct + slope * resetsAt.timeIntervalSince(latest.at) / 3600
return .belowAtReset(projectedPct: min(projected, 100))
}
return .reachesLimit(at: hit)The Limits chart spans the limit's own window, from reset minus its length to the reset. The hatched span before the first reading is time Clepso was not yet watching.
Relevant source files
Sources/UsageEngine/PoolReducer.swift (EngineConfig, classify)Sources/UsageEngine/Pace.swift
Session figures
| Figure | Meaning |
|---|---|
| Context | Tokens in the model's context window now, out of the window's size. Not a running total for the session. |
| Cost est. | Claude Code's estimate at API list price. Not your bill and not your plan's usage. |
| Cache hits | Share of prompt tokens served from the cache in this session. |
| API time · Wall time | Time spent waiting on the model · time since the session started. |
| Lines +/− | Lines added and removed by the session's edits. |
| By model | Sessions grouped by their main model over 30 days; background models count under the main one. |
Activity calendar
Each square is a day. Its level is set by messages that day, in quarters of your active days, and drawn as pixel density rather than shade: the quietest quarter lights a few pixels, then a checkerboard, then solid with a few holes, and the busiest quarter is solid. The pattern says the level without relying on colour. A day with no messages is the plain square; a day before your first session or after today is an outline marked “no data”. All time shows one calendar year (January to December) with a year switcher that goes back to 2025; 30 days shows a month calendar, each day's date and message count on the plain square with its level as a pixel band below them; 7 days shows a column per day.
Relevant source files
Sources/ClepsoApp (StatsCalendar, StatsSummary)
Charts
Tokens per day are stacked by model on a real date axis: days with nothing stay on the axis. Past 120 days the bars become weeks. Every bar chart is drawn as pixel columns: whole-pixel widths, heights in whole pixels from the baseline, stepped top corners. The scale's top is the first round number at or above the tallest bar whose quarters are also round (for example 8M for a 6.5M peak). Models differ by colour and fill pattern, never colour alone. Point at a bar for its numbers.