Skip to content
CLEPSOdocs

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

SourceWhat it givesHow freshKept by Clepso
Claude Code status line5-hour and 7-day usage with reset times, current model, context window, session cost estimate, durations, lines changed, prompt-cache countsEvery status refresh while Claude Code runsYes, 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 onlyEach time Clepso checks, about every 30 seconds while onYes, 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 hourUp to yesterday: Claude Code folds each day in afterwardsNo. 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.

KindWhat it isTypical shareCounts for level
OutputText the model wrote: replies, code, tool calls. The model's own work.Well under 1%Yes
InputNew text sent to the model that was not already cached: your message, tool results, files it read.Under 1%No
Cache readEarlier 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 moreNo
Cache writeConversation stored in the prompt cache so later turns can re-read it cheaply.A few percentNo

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).

Try it: output to level

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

LevelOutput reachedNext level atOutput in this level
LV 101K1K
LV 21K2K1K
LV 32K5K3K
LV 45K10K5K
LV 510K20K10K
LV 620K50K30K
LV 750K100K50K
LV 8100K200K100K
LV 9200K500K300K
LV 10500K1M500K
LV 111M2M1M
LV 122M5M3M
LV 135M10M5M
LV 1410M20M10M
LV 1520M50M30M
LV 1650M100M50M
LV 17100M200M100M
LV 18200M500M300M
LV 19500M1B500M
LV 201B2B1B
LV 212B5B3B
LV 225B10B5B
LV 2310B20B10B
LV 2420B50B30B
LV 2550B100B50B

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.

FigureMeaning
AgentsSubagents started during the task. Each one counts once, matched by a hashed id, even if its events arrive out of order.
TypesClaude 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.
FinishedSubagents that reported stopping. Background subagents can finish after the task ends.
RunningNo 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 agentsShare of the last 30 days' tasks that started at least one subagent.
Agents per taskAll 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.swift
  • Sources/Storage/Store+Agents.swift

Limits and the pace estimate

StateWhen
AvailableUnder 80% used
Approaching limit80% or more
Nearly exhausted95% or more
Limit reached100%
Reset expectedThe reset time has passed but no fresh reading has confirmed it. Clepso never says “ready” without one.
StaleNo 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.

Try it: drag the usage up and back down

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 .available

Pace 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.

Try it: a steady rise through the readings

// 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

FigureMeaning
ContextTokens 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 hitsShare of prompt tokens served from the cache in this session.
API time · Wall timeTime spent waiting on the model · time since the session started.
Lines +/−Lines added and removed by the session's edits.
By modelSessions 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.