caveman learn shows where your coding agent’s tokens go, and what to fix first. It reads the setup files and session history already on your computer. Nothing is sent anywhere.
You get:
- a Setup Score from 0 to 100 (higher is leaner)
- a list of findings: places your tokens go, biggest first, each with a suggested fix
- a local HTML report and a JSON file
caveman-learn skill, one edit at a time, and only after you say yes.
Every number is an estimate from your own history (inferred in JSON). No local number is promoted to verified; see Accounting and evidence.
Quick start
Privacy: what it reads and writes
Analysis runs locally.export writes a file for you to inspect and sends nothing.
Reads
The memory and rules doctor also reads
.claude/CLAUDE.md, CLAUDE.local.md, .claude/rules/*.md and ~/.claude/rules/*.md (path-scoped rules with a paths: frontmatter are skipped because they load on demand), and Claude Code auto memory at <claude root>/projects/<repo-key>/memory/MEMORY.md plus its topic files, honoring CLAUDE_CODE_PROJECT_DIR_NAME.
Two other inputs: the session_outcomes finding reads commit timestamps with git in up to 12 repositories (3 seconds per call, budgeted overall), and the wrap_measured block reads rows the Caveman proxy already recorded in caveman.db.
Writes
All paths are under$CAVEMAN_HOME (default ~/.caveman) unless noted. CAVEMAN_DB overrides the database path.
Report files are created with mode
0600.
Commands and flags
The default window is--since 30d. The scan times out after 120 seconds (CAVE_LEARN_TIMEOUT, seconds).
Examples:
applyon abehavioralorload_bearingsink returnsapplied: falsewith a reason; there is no mechanical fix. Arecurring_contextcandidate carries locators (file, JSONL line, block index, SHA-256), never the block text.appliedneeds a fix kind. It is taken from the sink’s evidence or a default (claude_md_weight,claude_md_sections,config_tax,dead_load,dumbzone_advice); otherwise pass--fix-kind. The “before” value comes from the newest report snapshot older than the current scan, so a scan run after your edit is not used as the baseline. The edited file is fingerprinted at record time.simulateaccepts onlyclaude_md_weight:*,claude_md_sections:*,dead_load:skillsandrecurring_context:repaste:*. Config sinks are tokens per turn times scanned provider-counted turns; recurring blocks are occurrence sums minus the largest occurrence and one pointer per occurrence. Overlapping selections on the same file are counted once. No dollars, no forward projection.experiment reportdefaults its window to the experiment’s start.exportcarries sink ids, classes, magnitudes, the score, spend totals and per-model rows. It carries no paths, locators, evidence maps, repository names or session text.reconcilematches CSV columns by header name only and fails on any ambiguous header. Coverage is a token comparison, not a saving.
Setup Score
The Setup Score (cave_score in JSON) grades how lean your local setup is. It starts at 100 and subtracts four capped penalties. The caps sum to 100.
Config tokens per turn = user
CLAUDE.md + every CLAUDE.md from cwd to / + Claude skill descriptions. AGENTS.md has its own finding and is not in this sum. A component that cannot be measured has penalty 0, measured: false, and a detail string saying why.
What it is not:
- Not money. It carries no currency and is not derived from spend.
- Not billed usage. It is transcript and file arithmetic.
- Not the Caveman Cloud Cave Score, which scores organization traffic on a different scale. The two will not match.
recurring_context sink exists). Before that, learn prints the session count and top findings without a score. --json always includes it.
Sink classes
Sinks are ranked: forward-rate sinks first by tokens per day, then historical sinks by observed tokens. Classes (the report’s label in brackets):reducible(“safe fix”): a mechanical fix exists and must pass the net-token-negative gate.recurring_context(“repeated text”): content re-established across sessions; fix is a cavemem offload.behavioral(“habit”): a measured habit. Numbers are stated; suggestions are soft.load_bearing(“needed”): measured config you need. Listed so the score stays honest; never edited.
Fixing
The consent loop
caveman learn implement (or the TUI’s “Implement with agent”) opens Claude Code or Codex with the caveman-learn skill, installing it if missing. The skill:
- Reads
caveman learn report --jsonand shows the score, components and ranked sinks. - For each sink you choose, proposes one concrete diff with before and after tokens per turn, and asks yes or no.
- Applies only approved edits, with the agent’s own file tools.
- Re-measures. Net-token-negative gate: if tokens per turn did not drop, the edit is reverted.
- For a cavemem offload, also checks that
caveman mem recallreturns the content and a pointer is in place; otherwise it reverts (caveman mem forget <id>and restores the source). - After an edit passes its gate, runs
caveman learn applied <sink_id>.
load_bearing sinks are never edited. procedure_repeat candidates skip the gate and go to an experiment instead, because a skill adds prefix tokens every session and pays back only on some.
You can install the skill directly: caveman tools skills install caveman-learn (add --user for all repos, --agent codex for Codex).
Applied ledger and verdicts
Each later scan compares sessions afterapplied_at with the recorded before-value and reports a row in confirmed:
Provenance compares the file’s SHA-256 with the fingerprint taken at
applied time: intact, changed_since (someone edited past the fix), target_missing, or not_fingerprinted. changed_since and target_missing force confidence to low.
Savings
caveman learn savings lists every recorded fix, including regressions, grouped by measurement method (rung), strongest first:
Rungs are never summed together, and there is no blended headline. Each row carries standing confounders for its method. Only positive savings are priced, as a per-day rate over the scanned window at your measured effective input rate, when the plan has a
spend block. total_saved_usd_by_rung sums within one rung only. Nothing is projected to a month or labeled verified.
Experiments
A holdout compares arms over your own sessions:experiment start <label> [--sink <id>] [--fix-kind <kind>] [--note <t>]opens theonarm.experiment arm <label> off(lateronagain) switches arms. Switching to the running arm is a no-op.experiment report <label>assigns each session to the arm active at its start and compares median tokens per session.experiment stop <label>closes it.
When the on-arm has more than 1.25x the off-arm’s tool errors per turn, the report says so. Arms run at different times on different work, so the result is still
inferred.
Reverting
Every edit is made by the skill and reported exactly, so it can be undone with your own tools or version control. An offload reverts withcaveman mem forget <id> plus restoring the trimmed text. A regressed verdict is shown with no dollar figure; the skill offers the revert path.
Autopilot
Autopilot rescans in the background and tells you about new findings without you running learn. It keeps its own report underruntime/learn-autopilot/reports/: its scans run with whatever working directory and environment the last session-end hook had, so they never overwrite the canonical report, dated snapshots or trend history your own caveman learn writes.
Session-start nudge
After a background scan, autopilot may show one line at the next session start, for example:- Sinks of class
reducibleorrecurring_contextwithtokens_per_turn>= 2000 qualify. This includesmemory_health:duplicate_rulesandmemory_health:memory_orphanswhen they arereducibleand above the threshold. memory_health:broken_importsandmemory_health:memory_truncationqualify with no token threshold: they break what the agent loads. When they are all that is new they lead the line (caveman learn: memory files (<repo>) — <title>. Run \caveman learn —all` to review.`); otherwise they are appended as ”, plus N memory-file findings”.- Only sinks not seen by an earlier autopilot scan. The first scan records a baseline and announces nothing.
- While a nudge is still unclaimed, newer sinks stay unseen and are announced after it.
- Shown only on Claude Code, Codex and Gemini CLI
SessionStartwith sourcestartuporclear; never onresume,compactorfork. It is delivered as a user-visiblesystemMessage, not model context. - Claimed with one atomic rename, so concurrent sessions announce it once. The claim parks it in
runtime/learn-autopilot-nudge.inflight.jsonwith the claimer’s token; it counts as announced only when that claimer confirms, after the line was written to the host (the fast hook owns the token and confirms after relaying the delegated output). At most one nudge is in flight: a newer nudge waits behind an unconfirmed one. An unconfirmed nudge, for example after the delegate hit its 3 s timeout, is shown once more after 10 minutes, never twice. - Suppressed when autopilot is disabled.
Trends
Thetrends block compares your own sessions week over week.
Metrics:
Only the session-derived score components are recomputed per week;
config_tax and dead_load depend on today’s config and are not replayed. Full score history comes from the dated report snapshots (score.history). movers lists up to three sinks that grew and three that shrank by at least 10 tokens/turn versus the newest snapshot at least 7 days old (else the newest one).
A trend is not a saving and does not show what caused the change.
Output files and JSON
caveman learn --json and caveman learn report --json print one document with schema: "caveman.learn.v1" and basis: "inferred". The same plan, plus moves, generated_at and generation, is saved as reports/caveman-learn.json. The Go types are in proxy/internal/store/learn_types.go.
Other subcommands print their own schemas:
caveman.learn.savings.v1, caveman.learn.simulate.v1, caveman.learn.experiment.v1, caveman.learn.digest.v1, caveman.learn.reconcile.v1.
spend is what the scanned window cost at published rates. It is a floor when unpriced is non-empty, it is not an invoice, and on a subscription plan the marginal cost is zero. The compact plain view folds several unpriced models into one line; --all, --md, JSON and the HTML report list each model.
Limits and known gaps
- Headless sessions can consume the nudge. A scripted
claude -porcodex execfiresSessionStartwith sourcestartup, so it can claim and confirm the nudge where no one sees it. - Unsupported agents. Only the five sources above are scanned. Cursor, Windsurf, Hermes, Pi and other agents are not read.
- aider is off unless
CAVEMAN_AIDER_ROOTpoints at a directory to walk. aider history has no provider usage, so it adds nothing to depth, dumbzone, spend or trends. - Context windows. A window comes from the shared model catalog. For un-cataloged models learn assumes 1M if the model id contains
1m, 400k for OpenAI, 1,048,576 for Gemini, and 200k otherwise. If a session’s context grows past the assumed window, that whole session is re-measured against 1M (then 2M). A 1M-window session that never exceeds 200k is still measured against 200k. Assumed windows add a caveat and are excluded from the excess token floor and fromcross_provider:depth. - Turn-1 prefix includes the first user prompt, so the measured prefix is an upper bound.
- Plugins are counted but their tokens are not measured.
claude_md_sectionssees only visible transcript text. Claude Code injectsCLAUDE.mdinto the system prompt, so the finding is emitted only when at least one section echoes (a positive control).--repodrops Gemini sessions (their transcripts have no working directory) and omitsretro.- Auto memory location ignores a custom
autoMemoryDirectorysetting. - Time budget. With
--retro, a scan that hits its budget covers partial history; repository, section-echo, trend and applied-fix blocks are omitted rather than zero-filled. - Ledger coverage.
memory_healthfixes can be recorded withappliedbut have no after-value measurement yet, so they stayinsufficient_data.simulatedoes not acceptmemory_healthsinks. - Savings rungs. Confirmed ledger rows are currently produced only at the
deterministic_remeasureandinterrupted_time_seriesrungs.controlled_holdoutresults appear inexperiment report, not insavings, and no command yet producescounterfactual_replayrows.