# Caveman documentation > Documentation for the open Caveman stack: the agent skill, the local proxy and its compression engine, cavemem, and the Agent SDK. What each part does, what it costs, and how every number is counted. Every page of https://docs.caveman.so in one file, in reading order. Each page is also available on its own at its canonical URL with `.md` appended, or by sending `Accept: text/markdown` to that URL. A machine index with headings, anchors and last-changed dates is at https://docs.caveman.so/llms-index.json Measurement vocabulary, which the pages below assume: - inferred: A local, per-run estimate computed on your own machine from an offline token counter. Every public Caveman tool emits this and nothing else. Not a saving, not a dollar figure, and never valid to multiply into a monthly or annual total. - measured: Traffic that was actually observed, rather than estimated from bytes. Not proof that a dollar was saved. Observing a request is not comparing it to its alternative. - verified: A saving confirmed against a real bill by a hosted rollout system that ran both arms of the comparison. No tool documented on this site can emit it. There is no configuration, flag or account tier that upgrades a local number to this. ## Contents - Overview (https://docs.caveman.so/docs): What Caveman is, which parts are open, which repository each one ships from. - Quickstart (https://docs.caveman.so/docs/quickstart): Install the skill and compress a real payload in about five minutes. - Architecture (https://docs.caveman.so/docs/architecture): The path a request takes through the stack, and where the open-core line falls. - How numbers are counted (https://docs.caveman.so/docs/counting): Inferred against verified, per run against per month, and the four rules the code follows. - Skill (https://docs.caveman.so/docs/skill): A set of instructions that changes how your agent spends tokens. No runtime, no proxy. - caveman learn (https://docs.caveman.so/docs/skill/learn): Profile a local agent session and rank what is actually eating the budget. - caveman explore (https://docs.caveman.so/docs/skill/explore): Read a repository without dragging every file into context. - Agent profiles (https://docs.caveman.so/docs/skill/profiles): The registry of coding agents the skill knows how to configure. - Proxy (https://docs.caveman.so/docs/proxy): A loopback reverse proxy: point an agent at it, keep your provider key, and it shrinks what leaves. - Wrap an agent (https://docs.caveman.so/docs/proxy/wrap): Launch Claude Code, Codex, Gemini CLI, and seven other agents through the local listener. - Providers (https://docs.caveman.so/docs/proxy/providers): The HTTP routes, credentials, and compatibility mounts the proxy forwards. - Modes (https://docs.caveman.so/docs/proxy/modes): Record, compress, and pixel, plus the evaluation modes the runtime accepts. - Configure (https://docs.caveman.so/docs/proxy/configure): Feature flags, caveman.yaml, local storage, and how mode is chosen. - Security (https://docs.caveman.so/docs/proxy/security): Loopback only, BYOK, SSRF checks, and what stays on disk. - The engine (https://docs.caveman.so/docs/proxy/engine): The compressor the proxy calls, and the standalone binary behind it. - Compressors (https://docs.caveman.so/docs/proxy/compressors): One router per content shape: JSON, logs, code, diffs, search results, prose. - Recoverable compression (https://docs.caveman.so/docs/proxy/recoverable): Why lossy stays honest: every removed byte can be fetched back. - Token counting (https://docs.caveman.so/docs/proxy/tokens): The offline counter behind every ratio the engine reports. - Memory (https://docs.caveman.so/docs/memory): cavemem holds the context you would otherwise paste into every session. - Recall and offload (https://docs.caveman.so/docs/memory/offload): Moving a heavy instruction file out of the prompt and into recall. - CLI (https://docs.caveman.so/docs/cli): The command surface: compress, detect, learn, and agent setup. - TypeScript SDK (https://docs.caveman.so/docs/sdk/typescript): Compress payloads and read spend from Node. - Python SDK (https://docs.caveman.so/docs/sdk/python): The same surface for Python agents. - MCP server (https://docs.caveman.so/docs/mcp): Expose compression and recall as tools any MCP client can call. - caveman-shrink (https://docs.caveman.so/docs/shrink): Shrink command output before it reaches the model. - caveman-browse (https://docs.caveman.so/docs/browse): Read web pages as compressed accessibility trees instead of raw HTML. - Overview (https://docs.caveman.so/docs/agent-sdk): Build an agent that is efficient by construction. - Install (https://docs.caveman.so/docs/agent-sdk/install): Build 0.2 from source, run doctor, and the gap with the npm 0.1 package. - Create an agent (https://docs.caveman.so/docs/agent-sdk/create): The initializer that writes a typechecking project with a starter eval graph. - Define an agent (https://docs.caveman.so/docs/agent-sdk/define): agent(), context, output, filesystem, and subagents as one frozen definition. - Tools (https://docs.caveman.so/docs/agent-sdk/tools): Input schemas, effect classes, timeouts, and what happens to a tool result. - Run a turn (https://docs.caveman.so/docs/agent-sdk/run): run, stream, receipts, budgets, and durable resume. - Sandbox (https://docs.caveman.so/docs/agent-sdk/sandbox): required, fixture, and host: where tool closures run, and what lock eligibility requires. - Memory (https://docs.caveman.so/docs/agent-sdk/memory): Local, opt-in, one-turn-behind recall. Never verified, never in the frozen prefix. - Compaction (https://docs.caveman.so/docs/agent-sdk/compaction): A fail-closed ladder: recover, summarise, clamp, then stop. Only inside a declared budget. - Programmatic tools (https://docs.caveman.so/docs/agent-sdk/code): One caveman_code cell instead of a large JSON tool catalog. Nested calls still go through the runtime. - Connect (https://docs.caveman.so/docs/agent-sdk/connect): Paginated provider data through one stable tool. Fail closed. The daemon is not in this package. - Evals and builds (https://docs.caveman.so/docs/agent-sdk/build): Profile, search, freeze, holdout. Failed evals never produce a lock. - Adapters (https://docs.caveman.so/docs/agent-sdk/adapters): Exact-pinned framework lanes. Presence is not a grant of execution. - Cloud (https://docs.caveman.so/docs/cloud): The managed plane: fleet visibility, spend attribution, and verified numbers. - Model router (https://docs.caveman.so/docs/router): One model id: classify the task, apply a cost tier, route to measured evidence, fall back to your baseline. - Licensing (https://docs.caveman.so/docs/licensing): Which surfaces are MIT, which are BSL 1.1, and what that means for you. - Telemetry (https://docs.caveman.so/docs/telemetry): What the CLI sends, what it never sends, and the three ways to turn it off. - Eval graders (https://docs.caveman.so/docs/evals): The grader set used to check that compression did not change an answer. - Provider catalog (https://docs.caveman.so/docs/provider-catalog): The model price table every cost figure is read from. === --- title: Overview summary: What Caveman is, which parts are open, which repository each one ships from. canonical: https://docs.caveman.so/docs updated: 2026-08-30T15:24:20+02:00 basis: inferred --- # Overview > What Caveman is, which parts are open, which repository each one ships from. Caveman makes agents cheaper to run. It is not one program and not a framework you commit to. It is a short list of separate things, each of which you can adopt on its own and remove on its own. There are two repositories and one hosted plane. Knowing which is which is most of what you need to navigate this site. - caveman: Makes an agent you already use cheaper. The skill, the proxy and the compression engine inside it, memory, and the adoption surfaces: CLI, SDKs, MCP server. - agent-sdk: Builds a new agent that is efficient by construction. A separate repository and a separate install. It does not need the proxy. - Caveman Cloud: The managed plane, across a network boundary. The only paid part, and the only place a number can be verified against a bill. ## caveman: make an existing agent cheaper Everything in this repository runs on your machine and needs no account. The four rows below are the operating stack, bottom to top. Each one answers a different question, and each fails on its own. [Diagram: the four layers of the stack, bottom to top: Skill, Engine, Memory, Cloud.] ### 00 Skill A [skill](/docs/skill) is a persistent instruction set your coding agent reads before it answers. It cuts filler, narration and hedging out of the agent's own output, and it is explicit about what it must never touch: code blocks, function names, CLI commands and exact error strings stay byte for byte. It costs nothing to run because it is not software. It is text your agent already reads. ### 01 Proxy The [proxy](/docs/proxy) is a reverse proxy on loopback. You point an agent at `127.0.0.1:8787` instead of the provider, it keeps your key, and it shrinks the context on the way out. The compressor doing that shrinking is the engine. It detects what a payload is, routes it to a compressor built for that shape, and stores the original bytes under a content-addressed handle before it removes anything. If the agent needs what was dropped, it asks for the handle and gets the original back exactly. The engine is not a separate product and not a separate install; it is [documented inside the proxy](/docs/proxy/engine), because the proxy is how you actually run it. ### 02 Memory [cavemem](/docs/memory) holds the context you would otherwise paste into every session. It stores text once, recalls the parts a question actually needs, and compresses each hit on the way out. ### 03 Cloud [Caveman Cloud](/docs/cloud) is the managed plane. It is where a fleet's numbers stop being local estimates and become something a finance team can read. It is in private development and the waitlist is open. ### Build with it The same capabilities as a [CLI](/docs/cli), a [TypeScript](/docs/sdk/typescript) or [Python](/docs/sdk/python) SDK, an [MCP server](/docs/mcp), and two standalone commands: [caveman-shrink](/docs/shrink) for command output and [caveman-browse](/docs/browse) for web pages. These are the MIT adoption surfaces. Reach for them when you want the behaviour without a proxy hop. ## agent-sdk: build a new agent The [Agent SDK](/docs/agent-sdk) is the other direction. Rather than making an existing agent's traffic smaller, it is how you write an agent whose context, tools and budget are declared up front, so a turn cannot quietly exceed them. An agent is one frozen [definition](/docs/agent-sdk/define): context, output schema, filesystem, [tools](/docs/agent-sdk/tools), and subagents. You [run a turn](/docs/agent-sdk/run) against it and get a receipt rather than a vibe. [Evals](/docs/agent-sdk/build) gate the build, so a failed eval never produces a lock. [Compaction](/docs/agent-sdk/compaction) is a fail-closed ladder inside a declared budget: recover, summarise, clamp, then stop. It lives in its own repository, ships as its own package, and shares no runtime with the proxy. You can use either without the other. If you already have Claude Code, Codex or a provider SDK and want it to cost less, you want the skill and the [proxy](/docs/proxy). If you are writing an agent from scratch and want its budget to be a declared property rather than something you discover on the bill, you want the [Agent SDK](/docs/agent-sdk). ## What the numbers mean Every local tool on this site reports `inferred`. That word is load bearing. It means a per-run estimate computed on your machine from an offline token counter, and it is never turned into a monthly figure or a dollar amount. No tool in either repository can emit `verified`. That label is reserved for hosted rollout systems that can compare against a real bill. If a number here has no basis label next to it, treat it as an example rather than a promise. [How numbers are counted](/docs/counting) explains the four rules the code follows. ## Where to go next Install the skill, then compress something real and read the report. The path one request takes, and where the open-core line falls. One command, thirty agents, no runtime. Point an agent at loopback. The engine shrinks what leaves. Build an agent that returns a receipt, not a vibe. Which surfaces are open, and what the additional use grant permits. --- --- title: Quickstart summary: Install the skill and compress a real payload in about five minutes. canonical: https://docs.caveman.so/docs/quickstart updated: 2026-08-30T15:03:57+02:00 basis: inferred --- # Quickstart > Install the skill and compress a real payload in about five minutes. There are two products, and they are independent. The skill changes how your agent writes and takes about thirty seconds. The proxy, launched through the CLI, shrinks what the agent reads and gives you a local report on where those tokens actually go. ## Install the skill
This works for most agents: ```bash npx skills add JuliusBrussee/caveman ``` Without `-g` this writes into `./.agents/skills` under the directory you are standing in, not into a global config. That is usually what you want for a project, and surprising if you expected otherwise.
Some agents have a native path that wires up more than the skill file: ```bash # Claude Code claude plugin marketplace add JuliusBrussee/caveman claude plugin install caveman@caveman # Gemini CLI gemini extensions install https://github.com/JuliusBrussee/caveman # Codex CLI npx skills add JuliusBrussee/caveman -a codex # Cursor, which needs the global flag npx skills add JuliusBrussee/caveman -a cursor -g ```
Check it took. In your agent, run: ```text /caveman ``` You should get a confirmation that the mode is active. `/caveman off` turns it back off, and so does saying "normal mode".
The skill drops articles, filler and narration from your agent's replies. It is explicit about what stays byte for byte: code blocks, function and API names, CLI commands, and exact error strings. It also steps aside on its own for security warnings, irreversible actions, and any moment where being terse would make an instruction ambiguous. ## Install the local tools The CLI is published on npm under a scoped name. ```bash npm i -g @caveman-ai/cli caveman setup --install ``` The bare `caveman` package on npm is an unrelated JavaScript templating library. Install `@caveman-ai/cli`. On PyPI the same applies: our SDK is `caveman-sdk`, imported as `caveman_cloud`. `caveman setup --install` downloads the companion binaries into `~/.caveman/bin`. It checks a key-signed manifest first, then verifies each artefact against its own SHA-256 before installing it. Command names, version, platform, duration, exit class, and aggregate token counts from local sessions. Never prompts, code, file paths, arguments, model names or dollar figures. It is how we see which commands people use and which ones break, and it is a large part of why the local tools can stay free. Turn it off with `caveman telemetry off`, `CAVEMAN_TELEMETRY=0`, or `DO_NOT_TRACK=1`. The full payload is listed on [Telemetry](/docs/telemetry). Then start your agent through the CLI so traffic goes through the [local proxy](/docs/proxy): ```bash caveman claude ``` The same works for `codex`, `gemini`, `aider`, `hermes`, `kilo`, `openclaw`, `opencode`, `pi`, and `qwen`. Ten wrap profiles ship today, and each is one JSON file in the registry. That set is smaller than the thirty-plus agents the skill supports, because wrap has to know the host's provider endpoint. ## Find out where your tokens go ```bash caveman learn ``` This reads your local agent sessions and ranks what is actually consuming the budget. It measures and does nothing else. Applying a fix is a separate, consent-gated step: ```bash caveman learn report --json caveman learn apply --dry-run caveman learn apply ``` Every applied fix is checked for being net token negative. If an edit does not make the thing smaller, it is reverted. ## What you should expect to see On the committed benchmark suite, the skill cuts output tokens by about 65 percent on average, with a range from 22 to 87 percent across ten prompts. That is a per-run inferred estimate measured on that suite, not a promise about your bill. Nothing you run locally will ever report a dollar figure or a monthly total. That is deliberate and [the reasoning is here](/docs/counting). --- --- title: Architecture summary: "The path a request takes through the stack, and where the open-core line falls." canonical: https://docs.caveman.so/docs/architecture updated: 2026-08-30T15:24:20+02:00 basis: inferred --- # Architecture > The path a request takes through the stack, and where the open-core line falls. Caveman is not one program. Most people install the skill, the proxy, or both, and never touch the rest. This page shows how a single request moves through the pieces and why they are separable. It describes the `caveman` repository. The [Agent SDK](/docs/agent-sdk) is a different repository with a different job: it is how you build an agent, not how you make an existing one cheaper, and nothing on this page is required to use it. ## One request [Diagram: one request, from your agent through the skill and the engine to the provider, with a recovery path back.] The agent decides what to say. The skill has already shaped how it says it. When the request is wrapped, it goes to the [local proxy](/docs/proxy) on loopback; the [engine](/docs/proxy/engine) reduces whatever payload is about to leave; the provider bills for what arrives. Without the proxy, the engine is a binary you call yourself. The dashed line is the part that makes the rest defensible. Compression here is not a one-way door. Before the engine emits a smaller payload it writes the original bytes to a local store keyed by a hash of those bytes, and the compressed output carries that handle. An agent that needs the part that was removed asks for the handle and gets the original back, byte for byte, in the same turn. The rule the engine follows has three possible outcomes and no fourth: the payload is compressed and recoverable, or it passes through untouched, or it is recovered. If the recovery store is unavailable, the engine does not compress. It passes the original through. ## Why the pieces are separable Each layer answers a different question, and they fail independently. - Skill: Changes what the agent writes. No process, no config, no network. - Proxy: Changes the base URL and what a payload weighs. A loopback process that routes and records, plus the engine binary it calls to compress. - Memory: Changes what you have to say twice. A local SQLite store. - Cloud: Changes what you can prove to someone else. An account, across a network. Nothing above a layer is required by the layer below it. The skill does not know the proxy exists. The proxy does not need an account. This is why the licence split follows the same lines rather than cutting across them. ## Where the open-core line falls [Diagram: the licence split across MIT, BSL 1.1 and Commercial surfaces.] The rule that governs new code is one sentence: a module that imports, links, embeds or ships as part of the engine-linked runtime is BSL 1.1 unless a decision record explicitly classifies it as an adoption surface. The practical effect for you is in the additional use grant. BSL 1.1 here permits internal evaluation, local development, CI, integration, and self-hosted use for your own first-party traffic, production included. What it does not permit is offering Caveman's functionality to third parties as a hosted or embedded service. That boundary is the commercial line, and it is the only one. Each BSL release converts to Apache 2.0 on the earlier of 21 June 2030 or four years after that version was first distributed. ## Content detection The engine does not ask you what a payload is. It reads the bytes and decides, then routes to a compressor built for that shape. The types it recognises: `json` · `log` · `code` · `diff` · `search-result` · `text` · `toon` · `html` · `a11y` · `terminal` · `tabular` · `config` Two compressors are never chosen automatically and have to be asked for by name: the tool-schema compressor and the TOON encoder. Automatic selection of either would surprise a caller who did not opt in. When detection is not confident, it answers `text`, which is the compressor that removes the least. Every unknown case in this system resolves toward doing less, not more. ## Failure behaviour This is worth stating on its own, because it is the part that decides whether you can leave the thing switched on. ```text unknown mode -> record, which never transforms transform error -> forward the original bytes result not smaller -> keep the original low-confidence type -> text unknown grader -> passed: false unknown model price -> zero, tagged unpriced ``` Every one of those resolves to the conservative answer. A compression system that is willing to guess is a compression system you have to babysit. --- --- title: How numbers are counted summary: Inferred against verified, per run against per month, and the four rules the code follows. canonical: https://docs.caveman.so/docs/counting updated: 2026-08-26T03:57:26+02:00 basis: inferred --- # How numbers are counted > Inferred against verified, per run against per month, and the four rules the code follows. Every number Caveman shows you carries a word that says how it was obtained. The words are not interchangeable and the code does not let them blur. ## Three words, kept apart - inferred: A local estimate from bytes, token counters and local records. Every public offline tool emits this. - measured: Observed traffic. Real, but not proof that a dollar was saved. - verified: Reserved for hosted rollout systems that can compare against a bill. No tool on this site emits it. If you install anything documented here and it shows you a percentage, that percentage is `inferred`. There is no configuration that upgrades it. ## The four rules ### 1. No fake savings Headline figures in these docs are local examples or target ranges. They stay labelled `inferred`, and they are never multiplied out into a monthly saving. Where the code only supports a range, the docs show the range rather than picking a number from inside it. ### 2. Byte safe `record` mode is pass-through. On a parse problem, an unsupported input, a missing recovery store, or an output that is not actually smaller, the engine keeps the original bytes. ```text record mode -> never transforms transform error -> forward the original bytes result not smaller -> keep the original ``` ### 3. No placeholders, fail closed No stub responses on shippable paths, and unknown cases resolve toward the conservative answer. ```text unknown engine mode -> record, which is pass-through low-confidence content -> text unknown grader -> passed: false unknown route -> 404 unknown model price -> zero, plus an "unpriced:" tag ``` An unpriced model contributes zero to a cost figure and says so. It does not get a guessed price. ### 4. Recoverable, so lossy stays honest Compressors drop bytes from the model-visible payload. A lossy result is emitted only after the original has been stored under a content-addressed handle, and `retrieve(handle)` returns the original byte for byte. If a tool lies about savings, every report and every rollout decision downstream inherits that lie. Public Caveman tools report what they can support locally, and stop there. ## Per run, never per month An inferred ratio is a property of one payload on one run. Projecting it forward assumes your next month looks like that payload, which nobody can know. So the engine reports a ratio and does not offer to annualise it. There is no setting for this. ## Tokens are not money On the local path, savings are counted in tokens and have no dollar field anywhere in the data structure. There is no token-to-dollar multiplication at any layer. This matters because token prices differ per model, per provider, per cache state and per contract. A local tool that has not seen your invoice cannot convert one into the other, so it does not try. ## What the counter actually is The default token counter is a real BPE tokenizer using the `o200k_base` encoding, with the vocabulary compiled into the binary. It is deterministic, so the same bytes always produce the same count, and it is offline, so it works air-gapped and never sends your payload anywhere. If the embedded codec fails to load, it degrades to a characters-divided-by-four approximation. That approximation errs low. It never guesses high. Provider-reported usage from your actual API response stays authoritative for spend. The local counter is an estimate of that, and the two are kept in separate fields. ## Numbers you will see quoted Both of these are real measurements with real limits, and the limits travel with the number. **The skill, output tokens.** About 65 percent fewer on average across ten prompts, ranging from 22 to 87 percent. Measured on the committed benchmark suite. Output tokens only. A per-run inferred estimate, not a bill-savings promise. **The wrap path, input tokens.** 33.2 percent fewer provider-reported input tokens across 18 paired runs of six cases, with a case-clustered 95 percent interval of 14.6 to 48.5 percent. All 18 exact-answer checks passed. One of the six cases regressed by 9.9 percent and stays in the aggregate, because dropping a negative case would make the average a fiction. That second result is controlled benchmark evidence on a pinned agent build. It is not production traffic, not a provider invoice, and not `verified`. --- --- title: "Skill: Overview" summary: A set of instructions that changes how your agent spends tokens. No runtime, no proxy. canonical: https://docs.caveman.so/docs/skill layer: skill license: MIT capability: skill updated: 2026-08-26T03:57:26+02:00 basis: inferred --- # Skill: Overview > A set of instructions that changes how your agent spends tokens. No runtime, no proxy. The skill is the cheapest layer in the stack because it is not software. It is a file your agent reads, and it changes how the agent writes for the rest of the session. - What it is: One Markdown instruction file, plus optional commands and subagent presets. - Runtime: None. No process, no port, no config file. - Account: Not required. - Licence: MIT. - Install: `npx skills add JuliusBrussee/caveman` ## Install ```bash npx skills add JuliusBrussee/caveman ``` Without `-g` this writes into `./.agents/skills` under your current directory rather than a global location. Several agents have their own native path: ```bash # Claude Code claude plugin marketplace add JuliusBrussee/caveman claude plugin install caveman@caveman # Gemini CLI gemini extensions install https://github.com/JuliusBrussee/caveman # Codex CLI npx skills add JuliusBrussee/caveman -a codex # Cursor, which needs -g npx skills add JuliusBrussee/caveman -a cursor -g # opencode npx -y github:JuliusBrussee/caveman -- --only opencode ``` There is also a full installer that wires up hooks and a status line for Claude Code. It is pinned to a tag, it is safe to re-run, and it supports `--dry-run`. ```bash curl -fsSL https://raw.githubusercontent.com/JuliusBrussee/caveman/v2.3.1/install.sh | bash ``` ```powershell irm https://raw.githubusercontent.com/JuliusBrussee/caveman/v2.3.1/install.ps1 | iex ``` ## What it removes Articles, filler, pleasantries, hedging and tool-call narration. Sentence fragments are allowed. Decorative tables and emoji are not. ## What it will not touch This list is the reason the skill is safe to leave on. - Code blocks, verbatim - Function, method and API names - CLI commands and flags - Commit type keywords - Exact error strings - The language you are writing in It also refuses to invent abbreviations. `cfg`, `impl`, `req`, `res` and `fn` are banned, and so are arrow characters standing in for words. The reason is measurable rather than stylistic: the tokenizer splits those the same way it splits the full word, so the abbreviation saves nothing and costs clarity. ## When it steps aside The skill drops out of terse mode on its own for: - Security warnings - Confirmations of anything irreversible - Ambiguous multi-step sequences - Any case where compressing the answer would create the ambiguity ## Levels ```text /caveman lite /caveman full # default /caveman ultra /caveman wenyan-lite /caveman wenyan-full /caveman wenyan-ultra /caveman off ``` `off`, "stop caveman" and "normal mode" all do the same thing. ## What else comes with it One install also brings a set of commands and work-pattern skills. - Commands: `/caveman-commit · /caveman-review · /caveman-compress · /caveman-stats · /caveman-help` - Subagent presets: cavecrew-investigator, cavecrew-builder, cavecrew-reviewer - Work patterns: investigate-first, lean-build, surgical-patch, safe-refactor, migration, verify-and-stop ## What it saves On the committed benchmark suite of ten prompts, output tokens drop by about 65 percent on average, with a range of 22 to 87 percent. That is a per-run inferred estimate measured on that suite. It is not a promise about your bill, and it covers output tokens only. The range matters more than the average here. A tightly scoped refactor prompt saved 22 percent. A long explanatory answer saved 87 percent. Which end you land on depends on how much of your traffic is prose. --- --- title: caveman learn summary: Profile a local agent session and rank what is actually eating the budget. canonical: https://docs.caveman.so/docs/skill/learn layer: skill license: MIT capability: skill-caveman-learn updated: 2026-08-26T04:05:35+02:00 basis: inferred --- # caveman learn > Profile a local agent session and rank what is actually eating the budget. `caveman learn` reads local agent sessions and shows where tokens are being spent. The command measures and prepares candidates. The `caveman-learn` skill is the separate, consent-gated part that can edit a file. - Input: Local Claude Code, Codex, and Caveman session records. - Default window: The last 30 days. - Report basis: `inferred` - Edits: One approved change at a time. - Protected class: `load_bearing` ## Run the profiler ```bash caveman learn ``` In an interactive terminal this shows a local Setup Score, grouped top moves, and a menu for the full report or the editing flow. Stable output modes are available for scripts and pipes: ```bash caveman learn --plain caveman learn --all caveman learn --json caveman learn --md caveman learn --since 7d --sources codex,claude,caveman ``` `--plain` has no animation or keyboard menu. `--all` includes every finding, its internal id, its basis, and its suggestion. JSON uses the `caveman.learn.v1` schema. If no matching sessions exist, the command returns no score. Repeated-context findings need the same block in three or more sessions, so a new installation may need several sessions before that class appears. ## Read the sink classes | Class | Meaning | Automatic edit | | --- | --- | --- | | `reducible` | A heavy instruction file or an installed skill that local evidence says can be smaller. | Candidate only. The skill still asks first. | | `recurring_context` | A block re-established across sessions that may fit local memory better. | Candidate only. Recall must work before source text is removed. | | `behavioral` | An observation about how the agent or user works. | None. Repetition does not prove the behaviour is wrong. | | `load_bearing` | Context that must remain available on every turn. | Never. It stays in the score so the score remains honest. | Rates such as `tokens_per_turn` and `tokens_per_day_rate` describe the observed local pattern. The daily field is a rate, not a historical total and not a monthly projection. ## Review fixes with an agent ```bash caveman learn implement caveman learn implement claude caveman learn implement codex --prompt "focus on project instructions" ``` This installs the safety guide when it is missing, opens Claude Code or Codex, and asks the agent to read the current JSON report. It does not grant permission to apply every finding. You can also install the guide directly: ```bash caveman tools skills install caveman-learn --agent claude caveman tools skills install caveman-learn --agent codex ``` ## Preview one candidate ```bash caveman learn apply --dry-run caveman learn apply ``` Both commands prepare information for review. The second writes a candidate under `~/.caveman/candidates`; it does not edit your repository. Only the installed skill performs an edit, after showing the proposed change and asking for consent. For a reducible item, the skill compares inferred tokens per turn before and after. If the result is not smaller, it restores the original. For recurring context, the skill re-reads the source block, verifies its SHA-256 locator, stores the raw block in cavemem, and confirms that recall returns it. The source is trimmed only after the pointer and recall path both work. A failed recall removes the new memory and leaves the source in place. `caveman learn apply` materializes a candidate. It does not edit a file, move a block, or prove a reduction. The proof happens after an approved edit is measured again. ## What it will not do - Apply all fixes behind one confirmation - Edit a `load_bearing` finding - Treat a behavioral finding as an instruction - Remove recurring context without a working pointer and recall path - Keep an edit whose measured result is not net token negative - Attach currency, monthly savings, or `verified` to a local result Reports are also written under `~/.caveman/reports`, including HTML and JSON forms. They contain local analysis, so review them before sharing them outside your machine. --- --- title: caveman explore summary: Read a repository without dragging every file into context. canonical: https://docs.caveman.so/docs/skill/explore layer: skill license: MIT capability: skill-caveman-explore updated: 2026-08-26T04:05:35+02:00 basis: inferred --- # caveman explore > Read a repository without dragging every file into context. `caveman-explore` is a read-only repository scout for Claude Code. It answers one localization question with small `path:line` citations, so the main agent can inspect the right code without carrying every exploratory read in its conversation. - Host: Claude Code only. - Tools: `Read, Glob, Grep` - Model: `haiku` - Output: `path/to/file.ext:START-END reason` - Licence: MIT. ## Install ```bash caveman explore install --agent claude ``` The command is a compatibility alias for installing the `caveman-explore` skill. It writes the canonical `SKILL.md` unchanged and disables pixel conversion for this file. You can choose a destination explicitly: ```bash caveman explore install --agent claude --dir ./path/to/caveman-explore ``` `caveman explore install --agent codex` exits non-zero and writes no skill. Transcript isolation for this pattern has not been verified on Codex, so the installer does not claim support. ## When to use it Use the explorer when you need to locate code before you can solve the task: - A cold start in an unfamiliar repository - A question that spans several files or packages - A direct search that did not reveal the owning code - A request that names behaviour but no file or symbol Skip it when the task already names the exact file or symbol, or when a previous turn already returned usable line citations. The explorer is a localization step, not a second opinion on a known location. ## What it returns The response contains citations only: ```text src/router/pick.go:42-71 route selection lives here src/router/pick_test.go:18-40 table test covering selection ``` Every range must have been read. The range may not extend beyond the file. If no relevant code exists, the only valid answer is: ```text no relevant locations found ``` This narrow output contract matters. The solving agent gets evidence it can open directly, without inheriting a summary, a proposed fix, or a large search transcript. ## How it searches The explorer starts with several complementary reads in parallel: likely path patterns, symbol or string matches, and the most promising file contents. It follows the evidence for one or two short rounds, then stops as soon as it can name the locations. The fixed tool list enforces the boundary. It cannot run a command, edit a file, write a patch, or test a proposed solution. ## What it cannot prove A citation proves that code exists at a location the explorer read. It does not prove that the code is correct, that a test passes, or that the cited path is the only implementation. The solving agent still owns diagnosis, changes, and verification. --- --- title: Agent profiles summary: The registry of coding agents the skill knows how to configure. canonical: https://docs.caveman.so/docs/skill/profiles layer: skill license: MIT capability: agent-profiles updated: 2026-08-30T15:03:57+02:00 basis: inferred --- # Agent profiles > The registry of coding agents the skill knows how to configure. Agent profiles are the registry entries that tell the CLI how a coding agent is installed, launched, and given local Caveman tools. Ten profiles ship in the registry. The JSON files are compiled into the CLI, so source and distributed behaviour stay tied together. - Schema: `schema_version: "1"` - Profiles: Aider, Claude Code, Codex CLI, Gemini CLI, Hermes, Kilo, OpenClaw, opencode, Pi, and Qwen Code. - Fallback: `generic-env` - Validation: Unknown hook or injection methods fail compilation. - Licence: MIT. ## Registered agents | Profile id | Wire protocol | Configuration shape | Command-output hook | | --- | --- | --- | --- | | `aider` | OpenAI Chat | Environment | Manual | | `claude` | Anthropic Messages | Environment | Claude Code hook | | `codex` | OpenAI Responses | Host-specific code path | Codex hook | | `gemini` | Gemini generateContent | Environment | Gemini hook | | `hermes` | OpenAI Chat | Environment plus builder | Hermes plugin | | `kilo` | OpenAI Chat | Inline config via environment | Manual | | `openclaw` | OpenAI Chat | Config file overlay | OpenClaw plugin | | `opencode` | OpenAI Chat | Config content overlay | opencode plugin | | `pi` | OpenAI Chat | Native extension | Pi extension | | `qwen` | OpenAI Chat | Temporary system-settings overlay | Manual | This table describes declared integration shape. It does not say that every host version or provider credential has been tested. Each profile carries a tested version, and profiles that need code beyond their JSON declaration say so with `builder-assisted` or `code-only`. ## What one profile contains Every profile has an id, display name, binary names, install hint, wire protocol, injection description, attribution header, tested agent version, completeness label, and fallback. Optional fields describe command hooks, memory hooks, on-disk skill directories, and config overlays. The completeness labels have exact meanings: | Label | Meaning | | --- | --- | | `declarative` | Profile data alone describes routing setup. | | `builder-assisted` | Profile data is the base, and CLI code adds host-specific setup. | | `code-only` | Declared injection is inert; host setup lives in reviewed CLI code. | The registry compiler cross-checks these labels against the real builders. A profile cannot call itself declarative when it depends on code. ## Inspect what is installed ```bash caveman status caveman doctor claude caveman doctor codex caveman setup ``` `status` shows local state. `doctor ` checks the selected host integration. `setup` reports companion binary availability and repair commands. Launch shortcuts use profile ids: ```bash caveman claude caveman codex caveman gemini caveman aider caveman hermes caveman kilo caveman openclaw caveman opencode caveman pi caveman qwen ``` Each shortcut is equivalent to `caveman wrap `. [Wrap an agent](/docs/proxy/wrap) is the page for the hop itself. ## Hooks are capabilities, not assumptions Command hooks rewrite noisy shell output through `caveman shrink` before the model reads it. A profile without a verified hard hook surface stays manual and receives command guidance instead. Memory hooks are stricter. They are off by default, and a profile may declare one only when the host exposes a verified live user-prompt hook. Today only Claude Code declares that capability. Skill directories are also explicit. Claude Code declares user and project skill roots. Codex declares its user skill root. A profile with no verified convention omits the field, and conversion skips it. ## Failure behaviour Registry compilation rejects duplicate ids, unknown methods, false completeness labels, invalid tested-version metadata, and schema drift. Runtime detection falls back to `generic-env` when a declared setup path cannot be used. A profile means the CLI knows the host's integration shape. Live support still depends on installed host version, credential path, protocol, and available recovery surface. Use `doctor` on the machine that will run it. ## Adding a profile New profiles begin as JSON that passes the shared schema and compiler. A profile also needs runtime tests for its launch path, configuration injection, fallback, and any hook it claims. Adding a name to the registry without those paths does not make an agent supported. --- --- title: "Proxy: Overview" summary: "A loopback reverse proxy: point an agent at it, keep your provider key, and it shrinks what leaves." canonical: https://docs.caveman.so/docs/proxy layer: proxy license: BSL-1.1 capability: engine updated: 2026-08-30T15:24:20+02:00 basis: inferred --- # Proxy: Overview > A loopback reverse proxy: point an agent at it, keep your provider key, and it shrinks what leaves. The local proxy is a reverse proxy on loopback. You point an existing coding agent or provider SDK at `http://127.0.0.1:8787`, it forwards each request to your chosen model API with the credential that came in, and it compresses eligible context before those bytes leave. It is one of two things you install. The [skill](/docs/skill) changes how an agent writes. The proxy changes how much the agent has to read. They run independently. - Repository: `JuliusBrussee/caveman` - Binary: `caveman-proxy` - Listen: `127.0.0.1:8787` - Account: Not required. - Licence: BSL 1.1, converting to Apache 2.0 after four years. - Reports: Always `inferred`. It cannot emit `verified`. Two binaries, one thing you install and one set of pages. `caveman-proxy` routes, authenticates, streams and records. `caveman-engine` decides what a payload is and makes it smaller. You almost never run the second one directly, so it is documented here rather than as a product of its own: start at [the engine](/docs/proxy/engine), then [Compressors](/docs/proxy/compressors) for what happens to a specific content type. [Diagram: one request through the local proxy, from your agent to the engine to the provider, with recovery back from the CCR store.] The agent and the provider request format stay as they are. The proxy is a base-URL swap, not a new agent loop. Recovery copies live in `~/.caveman/ccr.db` on this machine. Usage rows live in `~/.caveman/caveman.db`. Neither file is a bill. ## Two ways in **Wrap an agent.** The CLI starts the proxy and points a known coding agent at it: ```bash npm install -g @caveman-ai/cli caveman setup --install caveman claude ``` The same shortcut exists for Codex, Gemini CLI, Aider, Hermes, Kilo, OpenClaw, opencode, Pi, and Qwen Code. See [Wrap an agent](/docs/proxy/wrap). **Start the listener yourself.** Use this when an application already has a provider SDK and you only want to change its base URL: ```bash caveman start ``` Default mode for a bare `caveman start` is `record`: every model-visible byte is forwarded unchanged, and the proxy writes local usage rows. Wrap defaults the other way, to `compress`. [Modes](/docs/proxy/modes) explains the difference. ## What happens to a request 1. The agent sends a provider-shaped HTTP request to loopback. 2. The proxy matches a [route](/docs/proxy/providers), keeps or resolves the credential, and inspects the body. 3. If the mode allows it and every gate passes, the engine stores the original bytes and emits a smaller view plus a recovery handle. 4. The proxy forwards the (possibly smaller) request upstream and streams the provider response back. 5. A local usage row is written. Token reductions on that row are `inferred`. If any transform gate fails, step 3 is skipped and the original body is forwarded. A compression problem never becomes a fake success or a client-side parse error. Provider errors still arrive as provider errors. ## Build from source Most people never do this. The CLI installs a signed `caveman-proxy` into `~/.caveman/bin`. If you want the binary yourself: ```bash git clone https://github.com/JuliusBrussee/caveman cd caveman go build -o ./bin/caveman-proxy ./proxy/cmd/caveman-proxy ANTHROPIC_API_KEY=… ./bin/caveman-proxy ``` There is no npm, pip, Docker, or Homebrew package for the proxy itself. `@caveman-ai/cli` is the MIT launcher. ## What the numbers mean In a pinned Claude Code benchmark, the wrap path used 33.2 percent fewer provider-reported input tokens across 18 paired runs of six cases. The case-clustered 95 percent interval is 14.6 to 48.5 percent. All 18 exact-answer checks passed. One case regressed by 9.9 percent and stays in the aggregate. That is controlled benchmark evidence on a pinned agent build, not production traffic and not `verified`. Local rows the proxy writes after your own sessions stay `inferred`. They are never multiplied into a monthly figure and never converted to money. ## What it will not do It will not bind to a LAN, container bridge, or public interface. A non-loopback listen address is rejected, because the listener has no inbound authentication. It will not log authorization headers, put API keys in YAML, or emit `verified`. It will not replace your agent. Claude Code still owns its UI, permissions, and model calls. The proxy owns the hop between that agent and the provider. It will not compress when the recovery store cannot accept the original. No store, no lossy transform. ## Running it Launch a coding agent through the local listener. Routes, credentials, and compatibility mounts. Record, compress, pixel, and the evaluation modes. Feature flags, caveman.yaml, and local storage. Loopback, BYOK, and what stays on disk. ## What it does to your bytes The compressor the proxy calls, and the standalone binary behind it. One router per content shape: JSON, logs, code, diffs, search results, prose. Why lossy stays honest: every removed byte can be fetched back. The offline counter behind every ratio the proxy reports. --- --- title: Wrap an agent summary: Launch Claude Code, Codex, Gemini CLI, and seven other agents through the local listener. canonical: https://docs.caveman.so/docs/proxy/wrap layer: proxy license: BSL-1.1 updated: 2026-08-30T15:24:20+02:00 basis: inferred --- # Wrap an agent > Launch Claude Code, Codex, Gemini CLI, and seven other agents through the local listener. Wrapping starts an existing coding agent with the local proxy as its provider endpoint. Caveman does not replace the agent. The agent still owns its model calls, user interface, permissions, and project workflow. Ten profiles ship today. The skill works with thirty-plus agents; wrap is a different, smaller set. A profile is a JSON file the CLI compiles, not a promise that every host version has been tested on your machine. Run `caveman doctor ` where you will actually launch it. - Command: `caveman wrap claude` - Shortcuts: One verb per profile id, same as wrap. - Default mode: `compress` - Pass-through: `caveman wrap --off claude` ## Launch ```bash caveman claude caveman codex caveman gemini caveman aider caveman hermes caveman kilo caveman openclaw caveman opencode caveman pi caveman qwen ``` `caveman kilocode` is an alias for the same `kilo` profile. Arguments after the shortcut are passed through: ```bash caveman wrap claude caveman wrap --off codex caveman wrap --pixel gemini caveman wrap --workflow review opencode caveman codex --full-auto ``` `--off` and `--pixel` cannot be used together. For an agent that has no profile: ```bash caveman run -- my-agent --flag value ``` Generic wrapping supplies proxy environment. It cannot infer every host's native hook or plugin format. ## Registered agents | Profile | Wire protocol | How it is pointed at the proxy | | --- | --- | --- | | `aider` | OpenAI Chat Completions | Environment | | `claude` | Anthropic Messages | Environment | | `codex` | OpenAI Responses | Environment | | `gemini` | Gemini GenerateContent | Environment | | `hermes` | OpenAI Chat Completions | Environment | | `kilo` | OpenAI Chat Completions | Inline config via environment | | `openclaw` | OpenAI Chat Completions | Configuration file | | `opencode` | OpenAI Chat Completions | Configuration plus environment | | `pi` | OpenAI Chat Completions | Native extension | | `qwen` | OpenAI Chat Completions | Temporary system-settings overlay | This table is the integration shape. It does not say every credential path or host version works. [Agent profiles](/docs/skill/profiles) is the registry page, including completeness labels and hook rules. Kilo and Qwen need their own CLI installed first. The profiles pin the versions the wrap path was checked against: ```bash npm install -g @kilocode/cli@7.5.6 caveman kilo npm i -g @qwen-code/qwen-code@0.22.3 caveman qwen ``` Kilo organization or enterprise-managed config can override the injected provider. When that happens, or when account state cannot be verified safely, the wrapper launches Kilo directly instead of claiming a confinement it cannot keep. Qwen's overlay is a temporary file. Enterprise policy and unrelated provider settings survive the merge. `~/.qwen/settings.json` and the source system settings are not rewritten. ## First run On the first interactive local wrap, the CLI can install the signed runtime bundle and continue the same command. Non-interactive runs do not change installation state without an explicit: ```bash caveman setup --install ``` Wrap starts `caveman-proxy` for that session when it can. If something the CLI does not own is already listening on the port, wrap will not restart or trust it. An interactive run can offer to launch the agent directly when the proxy binary is missing. A script must make sure the listener is up. ## Native setup Claude Code and Codex can install the files those hosts actually read: ```bash caveman setup --agent-native claude caveman setup --agent-native codex caveman setup --agent-native claude --remove ``` Review the resulting hooks and plugins before you commit dotfiles. Native setup is per agent, not a global rewrite of every tool on the machine. ## Recovery during a run Compressed context carries a `ccr_…` handle, or a typed `ccr://…` pointer. Agents with the MCP integration can retrieve the original through a tool call. You can do the same from a terminal: ```bash caveman tools retrieve caveman tools mcp install qwen --server caveman ``` The Qwen installer preserves sibling MCP entries and refuses to overwrite one it does not own. Streaming and subscription-auth sessions need that agent-side recovery path. Non-streaming API-key requests can use proxy-side handling where the adapter supports it. The CLI checks that `caveman-mcp` is present for the selected agent before it advertises the MCP path. If recovery is missing on a path that needs it, that path stays uncompressed. See [Recoverable compression](/docs/proxy/recoverable) for handle format and store limits. ## Skill and wrap are separate switches An installed skill does not prove that request compression is on. Proxy traffic does not prove that the response skill is on. Hooks can add reminders or shrink command output without either of those being true. Turn each one on because you want that effect, not because you installed the other. ## When wrap goes wrong 1. `caveman status` for the selected mode and whether the listener is ours. 2. `caveman setup` for missing binaries. 3. `caveman wrap --off ` to see whether the failure survives without transforms. 4. Check provider credential variables without printing the secret values. 5. Confirm the agent is using the local endpoint the profile emitted. If a transform cannot parse the body, cannot store a recovery record, or cannot produce a smaller safe result, the original bytes go upstream. That is success for the request, and a zero-ratio row in the local store. --- --- title: Providers summary: The HTTP routes, credentials, and compatibility mounts the proxy forwards. canonical: https://docs.caveman.so/docs/proxy/providers layer: proxy license: BSL-1.1 updated: 2026-08-30T15:03:57+02:00 basis: inferred --- # Providers > The HTTP routes, credentials, and compatibility mounts the proxy forwards. The proxy speaks the HTTP shapes your agent already uses. Change the base URL to `http://127.0.0.1:8787` (plus the provider prefix when the profile needs one) and keep the same request bodies. Compatibility here means the route and the credential mapping, not every vendor extension. Credentials stay on the request or in the environment. They never belong in YAML. - Default listen: `http://127.0.0.1:8787` - Unknown route: 404, then nothing is forwarded. - Inbound key: Preserved. Environment is a fallback only. - Price gaps: Zero, tagged unpriced. No guessed list price. ## Anthropic ```text /anthropic/v1/messages /anthropic/v1/messages/count_tokens /v1/messages /v1/messages/count_tokens ``` Claude Pro and Max OAuth tokens pass through as `Authorization: Bearer`. Remapping those to `x-api-key` would 401, so the proxy does not. Environment fallback: `ANTHROPIC_API_KEY`. ## OpenAI ```text /openai/v1/chat/completions /openai/v1/responses /openai/v1/embeddings /v1/chat/completions /v1/responses /v1/embeddings ``` Environment fallback: `OPENAI_API_KEY`. ## Google Gemini ```text /gemini/v1beta/models/{model}:generateContent /gemini/v1beta/models/{model}:streamGenerateContent /gemini/v1beta/models/{model}:countTokens ``` Bare `/v1beta/models/{model}:…` paths are also accepted when a profile uses them. Environment fallback: `GEMINI_API_KEY`. ## Amazon Bedrock ```text /bedrock/model/{model}/invoke /bedrock/model/{model}/invoke-with-response-stream /bedrock/model/{model}/converse /bedrock/model/{model}/converse-stream ``` No raw endpoint URL is required in standalone mode. Use a bearer key or a complete IAM pair: ```bash AWS_REGION=us-east-1 AWS_BEARER_TOKEN_BEDROCK=… caveman-proxy AWS_REGION=us-east-1 AWS_ACCESS_KEY_ID=… AWS_SECRET_ACCESS_KEY=… caveman-proxy ``` `AWS_SESSION_TOKEN` is honoured for temporary credentials. A partial IAM pair fails closed. Credential precedence is the inbound credential, then the Bedrock bearer token, then a complete IAM pair. Region precedence is `providers.bedrock.region` in `caveman.yaml`, then `CAVE_BEDROCK_REGION`, `AWS_REGION`, `AWS_DEFAULT_REGION`, then `us-east-1`. The Mantle-style Anthropic Messages route at `/bedrock/anthropic` stays off unless `CAVE_BEDROCK_MANTLE_ENABLED` is set. A Claude Code user agent cannot relabel paid Bedrock traffic as subscription traffic. Inbound `x-api-key` and bearer credentials are stamped as Bedrock API keys before that classification. ## Azure OpenAI and Vertex AI Azure mounts under `/azure/…` after you set its base URL. Vertex mounts under `/vertex/v1/projects/…` and accepts the public Google and Anthropic publisher route forms the adapter implements. Both are opt-in: the endpoint and identity are installation-specific. ```yaml providers: azure: base_url: https://example-resource.openai.azure.com ``` Environment fallback for Azure: `AZURE_OPENAI_API_KEY`. ## OpenAI-compatible mounts Named compatibility mounts live at `/compat/{name}/…`. Each mount declares a `base_url` and the environment variable that holds its credential: ```yaml compat: local-model: base_url: http://127.0.0.1:11434/v1 api_key_env: LOCAL_MODEL_API_KEY ``` Compatibility means HTTP shape. It does not mean every provider extension works. Private, loopback, and link-local upstreams are blocked until you add an exact host to `CAVE_SSRF_ALLOWLIST`. See [Security](/docs/proxy/security). ## Applications and SDKs A wrap profile is one way to set the base URL. An application can do the same thing itself. The CLI prints copy-ready recipes: ```bash caveman tools sdk ``` Recipes cover Anthropic, OpenAI, Google Gen AI, Vercel AI SDK, LangChain, LiteLLM, CrewAI, Pydantic AI, the OpenAI Agents SDK, and raw HTTP. The TypeScript and Python SDKs also take a `baseURL` of `http://127.0.0.1:8787`; see [TypeScript SDK](/docs/sdk/typescript) and [Python SDK](/docs/sdk/python). ## Streaming Transforms finish before the request is dispatched. The response stays a stream. Provider streaming protocols and status codes are preserved. A transform never converts a streaming call into a buffered fake. ## Pricing on the local row The [provider catalog](/docs/provider-catalog) supplies dated public list prices. An unknown provider or model resolves to zero with an `unpriced` marker. Provider-reported token counts stay in a different field from engine estimates. A displayed list-price subtotal is not an invoice. ## When a route misses A 404 usually means the agent used the wrong provider prefix, or a bare path the selected adapter does not own. Authentication failures should be checked at the inbound header and at the environment fallback, without printing the secret. A blocked custom base URL almost always needs a precise `CAVE_SSRF_ALLOWLIST` entry, not a wider listen address. --- --- title: Modes summary: Record, compress, and pixel, plus the evaluation modes the runtime accepts. canonical: https://docs.caveman.so/docs/proxy/modes layer: proxy license: BSL-1.1 updated: 2026-08-30T15:24:20+02:00 basis: inferred --- # Modes > Record, compress, and pixel, plus the evaluation modes the runtime accepts. Mode decides whether the proxy is allowed to change model-visible bytes. Unknown values become `record`. That fail-closed default is why you can leave the listener running when you are unsure. The CLI exposes three operator modes. The binary accepts four more for controlled evaluation. - Wrap default: `compress` - Start default: `record` - Unknown mode: `record` - Pass-through flag: `caveman wrap --off` ## Record ```bash caveman wrap --off claude caveman start ``` Forward the model-visible request unchanged. Usage is still written locally. Use record to take a baseline, to prove an integration works, or for any workflow where every input byte must stay visible. `CAVEMAN_OBSERVE_ESTIMATE=1` can run the compressor on copies of live-zone segments while mode is `record`. The forwarded request is not mutated, no recovery record is stored, and no saving is booked. It answers "what would compress have done" without doing it. ## Compress ```bash caveman claude caveman tools config set think.mode compress ``` Eligible context is sent through the engine. The original is stored first. The smaller view is emitted only after that write commits, and it carries a recovery handle. If the result is not smaller, if parse fails, if the store is full, or if the content type is unsupported, the original bytes go upstream. TOON, the compact encoding for uniform tabular JSON, is allowed in this mode when the feature gate is on and the encoded form is smaller. It is never selected by automatic detection, and it is never applied to tool-call arguments. Changing those can change program behaviour even when the data looks similar. ```bash caveman tools toon encode < data.json caveman tools toon decode < data.toon ``` The decoder rejects malformed input rather than inventing structure. See [Compressors](/docs/proxy/compressors) for the content types compress actually routes. ## Pixel ```bash caveman wrap --pixel gemini ``` Pixel renders text as a PNG for a vision-capable model. It is lossy, model-dependent, and CCR-recoverable. The model must appear in `think.pixel.models`. Caveman does not infer vision support from a name. Density is `conservative`, `balanced`, or `max`. Higher density packs more text into the image and can make small characters harder to read. A smaller local representation does not prove a lower provider bill. Providers count image tokens differently from text. Check `pixel simulate` against the model you will actually call before leaving this on: ```bash caveman-engine pixel simulate --model anthropic ``` `--pixel` is not supported for Codex subscription sessions. ### Skills as images `caveman convert` renders installed `SKILL.md` bodies to PNG pages in place. Front matter stays text, so discovery and triggering still work. The model reads the body as an image. ```bash caveman convert --dry-run caveman convert --agent claude caveman convert --revert ``` Conversion runs only when the pages beat the text on the local token counter. Any failure leaves the skill byte-identical and names the gate that said no. `--revert` restores `SKILL.orig.md`. Skills installed through `caveman skills install` auto-pixel by default; pass `--no-pixel` to skip. Measured on the caveman skill itself: 1,069 to 415 estimated tokens, a 61 percent reduction. That figure is `inferred`, one skill, and not a bill. ## Evaluation modes The binary also accepts `recommend`, `shadow`, `canary`, and `active`. Standard local CLI workflows do not put you in these. They exist so a controlled evaluation can score or apply experimental behaviour without changing the three operator modes. If you have not been told to set one, you want `record`, `compress`, or `pixel`. ## How a mode is chosen Wrap reads feature configuration (`think.mode`), then environment (`CAVEMAN_WRAP_MODE` or `CAVEMAN_MODE`), then the `--off` / `--pixel` flags. Start reads the same feature configuration, but a bare `caveman start` with no override resolves to `record` so an unattended listener does not transform traffic. `caveman.yaml` `mode` is what the binary itself loads. `CAVEMAN_MODE` overrides that file. Command flags win for one session. Full precedence is on [Configure](/docs/proxy/configure). ## Failure behaviour ```text unknown mode -> record malformed transform input -> original bytes result not smaller -> original bytes CCR unavailable or full -> original bytes, no handle unsupported type or model -> original bytes unknown safety class -> do not transform missing recovery MCP -> leave that path uncompressed foreign process on the port -> do not restart or trust it ``` Every row resolves toward doing less. A mode you did not ask for cannot turn itself on. --- --- title: Configure summary: Feature flags, caveman.yaml, local storage, and how mode is chosen. canonical: https://docs.caveman.so/docs/proxy/configure layer: proxy license: BSL-1.1 updated: 2026-08-30T15:24:20+02:00 basis: inferred --- # Configure > Feature flags, caveman.yaml, local storage, and how mode is chosen. Two files, two jobs. Feature configuration tells `caveman wrap` which switches are on. Proxy configuration tells `caveman start` how the listener binds and which provider endpoints it may call. API keys belong in neither file. - Feature file: Printed by `caveman tools config path` - Proxy file: `~/.caveman/caveman.yaml` - Usage store: `~/.caveman/caveman.db` - Recovery store: `~/.caveman/ccr.db` ## Feature configuration Inspect the resolved path and a single key: ```bash caveman tools config path caveman tools config get think.mode caveman tools config set think.mode compress ``` A project may add a restricted overlay at `./.caveman/config.json`. The overlay can set `think.toon`, `think.shrink`, `remember.*`, and `execute.*`. It cannot change `think.mode`, `think.core`, or pixel settings. A checked-in project file is not allowed to silently turn on a more invasive transform. | Key | Default | Meaning | | --- | --- | --- | | `think.mode` | `compress` | `compress`, `record`, or `pixel` | | `think.core` | `true` | Core context compression | | `think.toon` | `true` | Allow TOON when it is smaller | | `think.shrink` | `true` | Shrink command output where supported | | `think.pixel.models` | `[]` | Models allowed to receive pixel context | | `think.pixel.density` | `balanced` | `conservative`, `balanced`, or `max` | | `remember.mem` | `true` | Local memory integration | | `remember.offload` | `auto` | `auto`, `on`, or `off` | | `remember.recall` | `false` | Automatic memory recall | | `execute.mcp` | `auto` | MCP recovery wiring | | `execute.browse_tool` | `true` | Browser tool integration | | `execute.browse_cli` | `false` | Browser command integration | | `execute.delegate` | `false` | Delegation integration | | `execute.proxy` | `true` | Route supported agents through the local proxy | Environment overrides beat the stored file. Use them for one session; use `config set` for a choice you want to keep. | Variable | Key | | --- | --- | | `CAVEMAN_WRAP_MODE` | `think.mode` | | `CAVEMAN_CORE` | `think.core` | | `CAVEMAN_TOON` | `think.toon` | | `CAVEMAN_SHRINK` | `think.shrink` | | `CAVEMAN_MCP` | `execute.mcp` | | `CAVE_PIXEL_MODELS` | `think.pixel.models` | | `CAVE_PIXEL_DENSITY` | `think.pixel.density` | `CAVEMAN_MODE` also overrides wrap and start mode when it is set. ## Proxy configuration Default path: `~/.caveman/caveman.yaml`. `CAVEMAN_CONFIG` loads a different file. A missing file is valid: the listener starts in `record` on `127.0.0.1:8787`. ```yaml label: local mode: record listen: 127.0.0.1:8787 optimizers: {} subscription_compress: live_zone toolschema_strip: off breakpoint_plan: frontier providers: {} compat: {} ``` | Field | Meaning | | --- | --- | | `label` | Tag on local usage rows. Trials set this through `CAVEMAN_LABEL`. | | `mode` | Runtime mode. Unknown values become `record`. | | `listen` | Loopback `host:port` only. | | `optimizers` | Per-id overrides. Three provider-cache planners default on. | | `subscription_compress` | `live_zone` (or empty) allows eligible subscription compression. `off` and any unknown value disable it. | | `toolschema_strip` | Only `annotations` turns this on. Everything else, including empty, is off. | | `breakpoint_plan` | Cache-breakpoint planner. Default `frontier`. Explicit `off` disables it. Unknown values become `off`. | | `providers` | Endpoint, billing tier, and region overrides. No secrets. | | `compat` | Named OpenAI-compatible mounts. | The three planner ids that default on are `anthropic-cache-breakpoints`, `openai-prompt-cache-key`, and `bedrock-cache-points`. Set any one to `false` to disable it. Planner metadata does not change model-visible bytes. Record mode stays an unconditional pass-through even when they are on. ```yaml providers: bedrock: region: eu-west-1 azure: base_url: https://example-resource.openai.azure.com compat: local-model: base_url: http://127.0.0.1:11434/v1 api_key_env: LOCAL_MODEL_API_KEY ``` `caveman start` flags for one run: ```bash caveman start caveman start --port 8787 --host 127.0.0.1 caveman start --config /path/to/caveman.yaml ``` `--host` still has to be loopback. The binary refuses anything else. ## Environment the binary reads | Variable | Role | | --- | --- | | `CAVEMAN_CONFIG` | Path to `caveman.yaml` | | `CAVEMAN_MODE` | Override YAML mode | | `CAVEMAN_LISTEN` | Override listen address | | `CAVEMAN_LABEL` | Override row label | | `CAVEMAN_HOME` | Override the `~/.caveman` directory | | `CAVEMAN_PROXY_BIN` | Explicit path to the proxy binary | | `CAVEMAN_SUBSCRIPTION_COMPRESS` | Override `subscription_compress` | | `CAVEMAN_TOOLSCHEMA_STRIP` | Override `toolschema_strip` | | `CAVEMAN_BREAKPOINT_PLAN` | Override `breakpoint_plan` | | `CAVEMAN_OBSERVE_ESTIMATE` | Record-mode observe-only estimation | | `CAVE_SSRF_ALLOWLIST` | Exact hosts allowed as private or loopback upstreams | | `CAVE_BEDROCK_REGION` | Bedrock region, before `AWS_REGION` | | `CAVE_BEDROCK_MANTLE_ENABLED` | Enable `/bedrock/anthropic` | Companion binaries resolve in this order: the matching `CAVEMAN_*_BIN` override, then `PATH`, then `~/.caveman/bin`. ## Local storage Default state lives under `~/.caveman`: | Path | Contents | | --- | --- | | `bin/` | Verified companion binaries | | `caveman.db` | Request usage, prefix-replacement cache, trials, learn data | | `ccr.db` | Exact recovery payloads and typed working-memory objects | | `caveman.yaml` | Proxy configuration | | `receipts/` | Local native-agent run receipts when produced | CCR defaults to 512 MiB of payloads, tunable with `CAVEMAN_CCR_MAX_BYTES`. At the cap, new lossy transforms pass through. Existing handles are never evicted. See [Recoverable compression](/docs/proxy/recoverable). Protect these files the way you protect agent transcripts. They can contain recovered prompts. ## Stats ```bash caveman stats caveman stats --json caveman-proxy stats caveman-proxy stats --recent 20 ``` Rows are local and `inferred`. They do not become a monthly total or a dollar field. ## Precedence Feature configuration resolves from defaults, then the global file, then the allowed project overlay, then environment. Proxy mode resolves from default, then YAML, then `CAVEMAN_MODE`. Command flags (`--off`, `--pixel`) select one session. When resolution fails or a mode is unknown, the runtime uses record or original-byte behaviour. It does not guess. --- --- title: Security summary: Loopback only, BYOK, SSRF checks, and what stays on disk. canonical: https://docs.caveman.so/docs/proxy/security layer: proxy license: BSL-1.1 updated: 2026-08-30T15:24:20+02:00 basis: inferred --- # Security > Loopback only, BYOK, SSRF checks, and what stays on disk. The local proxy is a single-operator developer tool. It binds to loopback and has no multi-user authentication layer. Treat it with the same care you already give a coding agent that holds a provider key. - Listen: Loopback only. Non-loopback addresses are rejected. - Auth on the listener: None. That is why the bind check exists. - Keys: Inbound header, then a named environment variable. Never YAML. - Savings label: `inferred` ## Trust model One trusted operating-system user. Do not expose the listener on a LAN, a container bridge, a public interface, or a shared host. Signing in to Caveman Cloud does not turn this process into a shared service. Those are different products and different data flows. Local wrap sends request content to the model provider the agent selected, and keeps recovery copies on disk. "Local" describes the Caveman hop, not the whole model request. The provider still sees whatever the proxy forwards. ## Credentials Keep API keys in the environment or in the provider's own credential store. Do not write them into `caveman.yaml`, a project overlay, a prompt, a benchmark fixture, or shell history. The proxy preserves an inbound authorization header and does not log it. Environment fallbacks apply only when the integration sent no credential. Distinct development keys are better than sharing a production key with a local listener, where the provider lets you do that. If a terminal, a trace, or an issue report ever printed a key, rotate it. ## SSRF Outbound checks cover configured endpoints and redirects. Private, loopback, link-local, and other unsafe address classes are blocked by default. A self-hosted model on this machine needs an exact `CAVE_SSRF_ALLOWLIST` entry. Allow only the hosts you need. A broad private-network range can let a prompt-driven request reach an unrelated local service. A listen-address change will not fix a blocked upstream. The bind check and the outbound check are different doors. ## What sits on disk | Store | Typical contents | | --- | --- | | `~/.caveman/caveman.db` | Request metadata, usage rows, learn data | | `~/.caveman/ccr.db` | Exact originals behind recovery handles | | Feature configuration | Mode and switch values, not keys | | Agent-native hooks | Host-specific files the profile installed | A recovery handle is an identifier, not encryption and not an authorization token. Anyone who can read `ccr.db` can retrieve the payload. Do not share handles across a trust boundary. See [Recoverable compression](/docs/proxy/recoverable). Restrict permissions on these files to the owner. Hook writes use restrictive modes and symlink-safe replacement; do not loosen that to make a backup script easier. ## Lossy transforms Record mode is the byte pass-through. Compress and pixel change what the model sees. The safety rails around that change are parse validation, a size comparison, explicit capability gates, exact-source recovery, and original-byte fallback when the store fails. Recovery means the source is still available. It does not mean the model will ask for the missing detail, and it does not mean the compressed view had equal quality. For work where every input byte must remain visible, stay in record. ## Local against connected `caveman help tools` is the local surface and needs no account. `caveman help cloud` is the connected surface and requires login. Command discovery keeps them apart on purpose. A connected installation can persist a hosted endpoint. That path necessarily receives request and response content while it proxies. This page documents the loopback listener. The Cloud product page covers the managed plane at the level a public site can. ## Checklist 1. Confirm the proxy listens on `127.0.0.1`. 2. Keep secrets out of both configuration files. 3. Review enabled transforms and the pixel model allowlist. 4. Set a precise SSRF allowlist only when a self-hosted upstream needs it. 5. Restrict permissions on the local databases and hook state. 6. Test recovery before a long lossy session. 7. Use record mode when the input bytes are the point. 8. Review agent, browser, hook, and plugin permissions separately. ## Reporting a vulnerability Do not publish an exploitable report in a public issue before maintainers can assess it. Use the repository security policy. Include the affected version, a minimal reproduction, the impact, and a suggested mitigation, without real credentials or customer data. --- --- title: The engine summary: The compressor the proxy calls, and the standalone binary behind it. canonical: https://docs.caveman.so/docs/proxy/engine layer: proxy license: BSL-1.1 capability: engine updated: 2026-08-30T15:24:20+02:00 basis: inferred --- # The engine > The compressor the proxy calls, and the standalone binary behind it. The engine is the compressor inside the [proxy](/docs/proxy). It reduces a payload before it reaches a model: it is written in Go, it links into other tools as a library, and it also runs as a standalone binary that reads stdin and writes stdout. Most people never invoke it directly. If you are wrapping an agent, the proxy already calls this on every eligible request and there is nothing on this page you have to do. Read it when you want to know what compression is actually doing to your bytes, or when you want the binary in a pipeline of your own. - Part of: The proxy. Not a separate product and not a separate install. - Licence: BSL 1.1, converting to Apache 2.0 after four years. - Distribution: Source only. There is no npm, pip, Docker or Homebrew package. - Binary: `caveman-engine` - Input limit: 64 MiB on stdin, refused with cave_input_too_large. - Reports: Always `inferred`. It cannot emit `verified`. ## Build it ```bash git clone https://github.com/JuliusBrussee/caveman go build -o ./bin/caveman-engine ./public/engine/cmd/caveman-engine ``` ## Use it The engine reads stdin and writes the result to stdout. The accounting report goes to stderr, so you can pipe the output without stripping the numbers out of it. ```bash cat large-payload.json | caveman-engine compress > compressed.txt ``` ```bash caveman-engine detect < payload.txt caveman-engine retrieve > original.txt caveman-engine retrieve "connection pool" > narrowed.txt caveman-engine stats caveman-engine registry ``` `retrieve` with no query returns the original bytes exactly. With a query it returns the sections of the original that match, ranked by BM25, which is usually what an agent actually wanted. ## How it picks a compressor Detection reads the bytes and answers with a content type. The type selects the compressor. There is no model call and no configuration step in that path. `json` · `log` · `code` · `diff` · `search-result` · `text` · `html` · `terminal` · `tabular` · `config` Five routes require an explicit type: `toolschema`, `toolschema-annotations`, `toon`, `a11y`, and `repetition`. None is selected automatically because each needs caller intent or an input contract that byte detection cannot prove. See [Compressors](/docs/proxy/compressors) for route-specific behaviour. Built with cgo, the code compressor uses tree-sitter and handles Go, Python and JavaScript or TypeScript. Built without cgo, or as WASM, it falls back to the Go standard library parser and handles Go only. Other languages pass through untouched rather than being mangled. ## Recovery Before any lossy result is emitted, the original bytes are written to a local store. Its public handle contains the first 16 bytes of the payload's SHA-256 digest as 32 hexadecimal characters after `ccr_`. Compressing the same payload twice produces the same handle and stores it once. - Store: `~/.caveman/ccr.db` - Backend: SQLite on host platforms, an in-memory map under WASM. Same contract. - Budget: 512 MiB of payloads by default, tunable with CAVEMAN_CCR_MAX_BYTES. - At the cap: New lossy transforms pass through instead. Existing handles are never evicted. The last row is the one to read twice. When the store fills, the engine stops compressing rather than dropping old originals to make room. Losing a recovery handle would turn an earlier honest compression into an unrecoverable one after the fact, so it does not happen. ## Token counting Ratios come from a real BPE tokenizer using the `o200k_base` encoding, with the vocabulary compiled into the binary. It is deterministic and offline. Every ratio the engine produces is `inferred`. Provider-reported usage from your actual response stays authoritative for spend, and the two live in different fields. ## Targets by content type These are honest reduction targets, not measured guarantees. What you get depends on your payload. | Content type | Target reduction | | --- | --- | | `search-result` | 80 to 95 percent | | `log` | 85 to 95 percent | | `json` | 70 to 90 percent | | `diff` | 60 to 80 percent | | `text` and HTML | 50 to 80 percent | | `code` | 40 to 70 percent | For one concrete measurement: on a committed tool-output fixture, 1,366 tokens became 246, a reduction of 82 percent. That figure is `inferred` and the original is recoverable. It is one fixture, and it is not a `verified` saving. ## Other subcommands ```bash caveman-engine toon encode | decode caveman-engine pixel render --density balanced caveman-engine pixel simulate --model anthropic caveman-engine evals run --fixtures ./fixtures ``` Pixel mode renders text as an image, which is cheaper than text for some models and much more expensive for others. `pixel simulate` exists so you can check which case you are in before switching anything on. --- --- title: Compressors summary: "One router per content shape: JSON, logs, code, diffs, search results, prose." canonical: https://docs.caveman.so/docs/proxy/compressors layer: proxy license: BSL-1.1 capability: engine updated: 2026-08-30T15:24:20+02:00 basis: inferred --- # Compressors > One router per content shape: JSON, logs, code, diffs, search results, prose. The engine routes each payload to a compressor built for its shape. Detection and compression are deterministic local code. Neither step calls a model. ## Automatic routes Detection checks stronger structural signals first and falls back to `text` when confidence is low. | Content type | What the compressor keeps | | --- | --- | | `json` | Object keys, error subtrees, array edges, error items, anomalies, change points, and query-relevant items. | | `terminal` | Final progress state, head and tail context, warnings, errors, and failure lines. ANSI control bytes are removed. | | `diff` | File and hunk headers, every changed line, and nearby unchanged context. | | `html` | Main readable article content rather than scripts, styles, navigation, and repeated chrome. | | `tabular` | Table structure and signal-bearing rows from CSV, TSV, or Markdown tables. | | `code` | Imports, declarations, signatures, and type structure. Function bodies may be elided. | | `log` | Head and tail context, warnings, errors, stack frames, and query-relevant lines. | | `search-result` | Top and bottom hits, diagnostic lines, and query-relevant results. | | `config` | Structure and selected values from YAML, TOML, and INI. | | `text` | Headings, opening and closing sections, marked important sections, and query-relevant prose. | The order matters. Raw terminal escape sequences are conclusive, for example, so terminal output is recognized before code or logs. Plain prose containing one code keyword does not become code. Unknown input takes the conservative `text` path. ## Forced routes Some transforms are available only when a caller names them: | Type | Why it is explicit | | --- | --- | | `toolschema` | Changes model-visible tool descriptions and annotations. The caller must identify a tool catalog. | | `toolschema-annotations` | Strips a reviewed allowlist of schema annotations without treating arbitrary JSON as schema. | | `toon` | Re-encodes JSON into another wire format. Automatic selection would surprise a JSON caller. | | `a11y` | Expects a Chrome accessibility tree and emits a compact UID view. Ordinary JSON must not enter this path. | | `repetition` | Collapses consecutive identical lines. Repetition alone does not identify the payload's meaning. | Do not depend on a published compressor count. The registry changes as content shapes and safety contracts change. Depend on the named type you need, or ask the engine for its current registry. ```bash caveman-engine registry caveman-engine detect < payload.txt ``` ## Elision markers Lossy compressors replace dropped runs with explicit markers. A marker states how much was removed, and some compressors attach invariants calculated from those exact units. ```text … 42 lines elided (caveman) … … 18 context lines elided (caveman) … ``` Markers are part of the idempotence contract. Compressing an already compressed view does not keep collapsing its own markers or append a second contract line. ## Query-aware selection JSON, logs, search results, and text can receive a query. Query-aware compressors use deterministic BM25 to keep matching units alongside their fixed safety anchors. They do not use embeddings or a model call. A query may change which records remain visible. It may not make the output larger than the queryless view. Exact original bytes remain behind the same recovery handle. ## Code builds With cgo enabled, the code route uses tree-sitter for Go, Python, JavaScript, and TypeScript. A build without cgo, including WASM, uses Go's standard parser and compresses Go only. Unsupported languages pass through. Comment removal is opt-in. Default code compression keeps comments and Python docstrings, elides bodies, then parses the result again. A result that no longer parses is rejected. ## Shared safety rules Every compressor returns either a candidate or `ok: false`. The engine applies the remaining gates around it: ```text parse or shape mismatch -> original bytes recovery write failure -> original bytes candidate not smaller -> original bytes unknown forced type -> original bytes ``` Keeping errors, names, keys, signatures, or tool parameters reduces risk. It does not prove that every model will answer identically from every compressed view. Use task graders for that claim, and keep recovery available. ## Reduction figures Content-type ranges shown on the engine overview are targets, not guarantees. Payload size, repetition, query, and important-line density decide whether a result is smaller. The engine reports the result of each run as `inferred`; it passes through when no reduction survives the gates. --- --- title: Recoverable compression summary: "Why lossy stays honest: every removed byte can be fetched back." canonical: https://docs.caveman.so/docs/proxy/recoverable layer: proxy license: BSL-1.1 capability: engine updated: 2026-08-30T15:24:20+02:00 basis: inferred --- # Recoverable compression > Why lossy stays honest: every removed byte can be fetched back. Recoverable compression lets the model see a smaller view without destroying source bytes. Caveman calls the local store CCR, short for Context Recovery. [Diagram: one request, from your agent through the skill and the engine to the provider, with a recovery path back.] ## Store before showing less A lossy transform follows this order:
The compressor creates a smaller candidate and the token counter confirms that it is smaller.
The engine writes the original bytes and accounting metadata to CCR.
Only after that write commits does the engine return the smaller view and its handle.
If step two fails, step three never publishes a lossy view. The caller receives the original bytes, a zero ratio, and no recovery handle. ## Handle format CCR hashes the original with SHA-256 and uses the first 16 digest bytes in the public handle: ```text ccr_<32 lowercase hexadecimal characters> ``` The same input produces the same handle and is stored once. A handle identifies content, not a session or a filename. ## Persistent store - Default path: `~/.caveman/ccr.db` - Override: `CAVEMAN_CCR_DB` - Default budget: 512 MiB of retained payload and metadata bytes. - Budget override: `CAVEMAN_CCR_MAX_BYTES` - Host backend: SQLite with WAL and a busy timeout. `CAVEMAN_HOME` changes the parent directory when `CAVEMAN_CCR_DB` is absent. WASM uses an in-memory store because the host SQLite implementation is unavailable there. A WASM handle lasts only as long as that in-memory store. The budget accepts a positive byte count. At the limit, new lossy transforms pass through. Existing handles are not evicted to make room, because eviction would invalidate a recovery promise already shown to an agent. ## Retrieve ```bash caveman-engine retrieve ccr_xxxxxxxx > original.bin caveman-engine retrieve ccr_xxxxxxxx "connection pool" > relevant.txt ``` An empty query returns the stored original byte for byte. A query asks for a deterministic BM25-selected view of the original and is useful when full recovery would refill the context window. The MCP surface exposes the same distinction: ```text caveman_retrieve({ recovery_handle: "ccr_..." }) caveman_retrieve({ recovery_handle: "ccr_...", query: "connection pool" }) ``` Unknown handles fail with an explicit error. Retrieval never guesses, returns a nearby payload, or treats a missing handle as empty content. Only empty-query retrieval is byte exact. Query recovery selects complete relevant sections from stored original bytes. Use full retrieval when exact order, omitted records, or a byte-for-byte comparison matters. ## Shared and separate stores The engine CLI, MCP server, and caveman-shrink use the shared CCR path by default, so a handle minted by one can resolve in a later process through another. cavemem keeps its recovery data under `~/.caveman/mem/ccr.db`. Recover a memory hit with `cavemem recover` or `caveman mem recover`; do not send that handle to the engine store. ## What recovery does not prove Recovery proves that omitted bytes remain available. It does not prove that the model will ask for them, that a compressed answer has equal quality, or that the local token count matches a provider bill. Those are separate grader, behaviour, and accounting questions. --- --- title: Token counting summary: The offline counter behind every ratio the engine reports. canonical: https://docs.caveman.so/docs/proxy/tokens layer: proxy license: BSL-1.1 capability: engine updated: 2026-08-30T15:24:20+02:00 basis: inferred --- # Token counting > The offline counter behind every ratio the engine reports. The engine needs a stable local token count to decide whether a transform is smaller. Its default counter uses an offline `o200k_base` tokenizer and records enough identity to say exactly which counter produced a result. - Backend: `tiktoken-go/o200k_base` - Pinned revision: `github.com/tiktoken-go/tokenizer@v0.8.0` - Input surface: Raw text, with no normalization. - Network: None. - Product basis: `inferred` ## What exact means here For valid UTF-8, the counter returns the exact result of the pinned `o200k_base` codec on the raw string. That is exact for this local backend and this input surface. It is not an exact provider bill. Providers may count a request envelope, messages, tools, cached input, or model- specific tokens differently. Provider-reported usage from the real response remains authoritative for spend. The code keeps these two claims separate: ```text local tokenizer result -> deterministic local evidence provider response usage -> observed provider evidence ``` Public compression reports still use `basis: "inferred"` because a local counter did not observe the bill. ## Fallback counter If the codec cannot be constructed, or if a count fails, the engine uses a deterministic approximation: ```text max(1, floor(UTF-8 rune count / 4)) ``` Empty input counts as zero. Invalid UTF-8 also takes the approximation path. Detailed count evidence labels this backend `approx/chars-per-four`, marks it modeled rather than exact, and records the fallback reason. The fallback is not presented as an `o200k_base` result. Backend, revision, semantic digest, exactness, and reason travel with detailed evidence so callers can reject modeled counts when their gate needs a pinned tokenizer. ## Provider-specific registry The token package also supports exact provider, model, and surface bindings. A binding must name a reviewed local backend. Unknown or unsupported tuples return `unmeasured`. They do not silently fall back to a nearby model or the default counter. A caller that wants a modeled fallback must choose it explicitly. Supported surfaces are: - Raw text - Messages - Tools - Full request envelope A backend may support one surface without supporting the others. Exactness never expands beyond the surface in its descriptor. ## How compression uses it The engine counts input and candidate output with the same counter. A candidate is used only when output count is strictly lower. Equal counts pass through. ```bash cat payload.json | caveman-engine compress > compact.txt caveman-engine stats ``` Reports include tokens before, tokens after, ratio, and basis. The ratio is a property of that payload and run. It is not annualized, converted to currency, or relabeled `verified`. ## Determinism and cache identity The `o200k_base` descriptor pins backend name, module revision, semantic contract digest, artifact digest, and surface. That identity lets caches and build locks distinguish a real counter change from the same name backed by different code. No Unicode normalization runs before counting. The same byte sequence and counter identity produce the same result. ## What the counter cannot tell you - What a provider charged for a request - How cached input was billed - Which model answered - Whether compression preserved answer quality - A monthly or dollar saving Use provider usage for observed token accounting and graders for quality. Keep local token estimates labeled `inferred`. --- --- title: "Memory: Overview" summary: cavemem holds the context you would otherwise paste into every session. canonical: https://docs.caveman.so/docs/memory layer: memory license: MIT capability: mem updated: 2026-08-26T03:57:26+02:00 basis: inferred --- # Memory: Overview > cavemem holds the context you would otherwise paste into every session. cavemem is a local store for the context you keep re-explaining. You write something once, and later ask a question. It returns only the parts that answer the question, compressed through the engine on the way out. - Licence: Go core BSL 1.1. The JavaScript and Python clients are MIT. - Storage: `~/.caveman/mem` - Ranking: BM25 behind a conservative threshold. - Basis: Always `inferred`. - Binary: `cavemem` ## Build it ```bash go build -o cavemem ./public/mem/cmd/cavemem ``` ## Use it ```bash cavemem remember "the deploy key lives in vault under ops/deploy" cavemem recall "where is the deploy key" cavemem recall "full migration context" 5 0 cavemem supersede mem_xxxxxxxx "deploy key moved to vault ops/deploy-v2" cavemem history mem_yyyyyyyy cavemem forget mem_xxxxxxxx cavemem recover ccr_xxxxxxxx > original.txt ``` `recall` returns JSON with the hits and a `basis` field. The two trailing numbers on the second recall are the result limit and the token budget. A budget of `0` means unlimited, and you have to write it out, because unbounded recall should be a decision rather than a default. Run `cavemem` with no subcommand and it speaks MCP over stdio: ```json { "mcpServers": { "cavemem": { "command": "cavemem" } } } ``` ## Four guarantees **Byte safe on write.** Raw text is stored first. Compression happens on the way out, never on the way in, so a compressor bug can never cost you the original. **Fails toward nothing.** An off-topic query recalls nothing rather than returning the closest thing it found. A memory system that always answers is worse than one that admits it has nothing. **Bounded by default.** Recall packs at most 2,000 inferred tokens unless you explicitly opt out. **Reversible.** Every compressed hit carries a recovery handle, and `cavemem recover` returns the original bytes exactly. ## Superseding rather than editing `supersede` writes a new version and links it to the old one. `recall` returns only current entries. `history` shows the chain from oldest to current. Facts about a codebase go stale, and a store that silently overwrites gives you no way to notice when the thing you remembered stopped being true. ## Clients Both clients shell text over stdin rather than passing it as an argument, so a large block does not hit an operating system argv limit. ```js await remember("the deploy key lives in vault under ops/deploy"); await recall("where is the deploy key", 5, 2000); ``` ```python cavemem.remember("the deploy key lives in vault under ops/deploy") cavemem.recall("where is the deploy key", limit=5, token_budget=2000) ``` Set `CAVEMEM_BIN` if the binary is not on `PATH`. An oversized `remember` exits with code 65. The npm client `cavemem` is published but currently behind the source in this repository, and its registry description describes older behaviour. There is no PyPI package yet. Until both catch up, build the core from source and use the Python client from the repository. ## One thing to know about handles cavemem keeps its own recovery store at `~/.caveman/mem/ccr.db`, separate from the engine's store at `~/.caveman/ccr.db`. Handles are not interchangeable between the two. A handle from a cavemem recall is recovered with `cavemem recover`, not with the engine. --- --- title: Recall and offload summary: Moving a heavy instruction file out of the prompt and into recall. canonical: https://docs.caveman.so/docs/memory/offload layer: memory license: MIT capability: mem updated: 2026-08-26T04:05:35+02:00 basis: inferred --- # Recall and offload > Moving a heavy instruction file out of the prompt and into recall. Offloading moves recurring context out of an always-read instruction file and into local cavemem. A short pointer stays where the block used to be, so an agent knows how to recall the compact form and recover exact source bytes. ## Good candidates Offload context when all of these are true: - The same block appears across several sessions - It is useful for some tasks, not every turn - A specific recall query can find it - Pointer plus recall costs fewer inferred tokens than keeping the block resident - Removing it does not weaken a load-bearing instruction Repetition is evidence of recurring cost, not proof that content is unnecessary. Security rules, required build commands, repository boundaries, and other load-bearing instructions stay in place. ## Measure first ```bash caveman learn --json caveman learn apply recurring_context: --dry-run ``` The learn report identifies repeated blocks by local session evidence. A candidate contains a locator, expected token figures, and proposed pointer text. It does not contain a trusted copy of the block body. Use the consent-gated editing flow for the full procedure: ```bash caveman learn implement ``` ## Safe offload sequence
Re-read the block from its real source file. Verify its file, session line, block index, and SHA-256 against the candidate locator. Stop if source changed after the scan.
Store exact block text. `--` ends option parsing, so a block beginning with a rule such as `---` stays literal. ```bash caveman mem remember -- "" ```
Recall it with a topic query and record `tokens_added`. ```bash caveman mem recall "" ```
Compare resident cost against pointer plus recall cost. If the new path is not smaller, forget the new memory and leave source untouched. ```bash caveman mem forget mem_xxxxxxxx ```
After approval, replace source block with pointer. Confirm recall still returns a hit before finishing.
## Pointer shape A useful pointer names the topic and both recovery paths: ```text Recurring migration context lives in cavemem. Recall: caveman mem recall "migration context" Exact source: caveman mem recover ``` Do not put the removed block into the pointer. That would preserve the recurring cost under a new heading. ## Recall remains bounded Normal cavemem recall returns up to five hits and packs at most 2,000 inferred tokens. The direct `cavemem` binary allows an explicit token budget of zero for unlimited recall: ```bash cavemem recall "migration context" 5 0 ``` Unbounded recall is opt-in. The `caveman mem recall` wrapper keeps the safe default and exposes only the result limit. ## Exact recovery Each compressed recall hit carries a `recovery_handle` from cavemem's own store: ```bash caveman mem recover ccr_xxxxxxxx > original.txt ``` That store lives under `~/.caveman/mem`, separate from the engine's shared CCR database. Use the memory recovery command for a memory handle. ## Auto-recall is optional Pointer-driven recall is the default. A verified host hook can inject relevant memory on each prompt, but it is off until you enable it: ```bash caveman mem hook install claude caveman mem hook uninstall claude ``` Only hosts with a declared live prompt hook are eligible. Injection fails open, so a recall problem does not block the user's prompt. Every injected hit discloses its inferred token cost. Safe offload has two required end states: a pointer remains in source and recall returns the stored content. If either is missing, restore source and delete the new memory. ## Undo Undo requires both parts: restore original block to its source location, then remove stored memory with `caveman mem forget `. Report both changes so the user can verify that recurring context did not disappear. --- --- title: CLI summary: "The command surface: compress, detect, learn, and agent setup." canonical: https://docs.caveman.so/docs/cli license: MIT capability: cli updated: 2026-08-30T15:03:57+02:00 basis: inferred --- # CLI > The command surface: compress, detect, learn, and agent setup. `caveman` is the command surface for local tools, supported coding agents, and connected account operations. The same binary is also available as `cave`. - npm package: `@caveman-ai/cli` - Runtime: Node.js 22.13 or newer. - Licence: MIT. - JavaScript dependencies: None at runtime. - Heavy work: Companion Go binaries. ## Install ```bash npm install -g @caveman-ai/cli caveman setup ``` The bare `caveman` npm package is unrelated. Use the scoped package name. `setup` shows which companion binaries are available. Install or repair the signed local bundle with: ```bash caveman setup --install ``` Artifacts are checked against a key-signed manifest and their own SHA-256 before atomic installation under `~/.caveman/bin`. Resolution checks an explicit `CAVEMAN_*_BIN` override, `PATH`, then that directory. ## Command groups ```bash caveman help caveman tools caveman cloud ``` `tools` contains local commands and needs no account. `cloud` contains connected commands and requires login. Compatibility aliases keep older top-level commands working, but grouped discovery is the clearest way to see the current surface. Common local commands: ```bash caveman tools compress < payload.json caveman tools shrink -- npm test caveman tools toon encode < payload.json caveman tools mem recall "migration context" caveman tools retrieve ccr_xxxxxxxx caveman tools browse snapshot https://example.com caveman tools evals run caveman tools stats --json caveman tools config get ``` Some commands also keep their top-level form, such as `caveman learn`, `caveman stats`, `caveman mem`, and `caveman retrieve`. ## Launch an agent ```bash caveman claude caveman codex caveman gemini caveman aider caveman hermes caveman kilo caveman openclaw caveman opencode caveman pi caveman qwen ``` Each shortcut calls `caveman wrap `. The profile registry decides binary name, protocol, setup method, hooks, and fallback. See [Wrap an agent](/docs/proxy/wrap) for what that hop actually does. On the first interactive local wrap, the CLI can install the signed runtime bundle and continue the same command. Non-interactive runs do not change installation state without an explicit setup command. ## Learn from local sessions ```bash caveman learn caveman learn --plain caveman learn --json caveman learn implement claude ``` The profiler is read-only. Applying a proposed file change belongs to the consent-gated `caveman-learn` skill. See [caveman learn](/docs/skill/learn) for sink classes and edit gates. ## Stable pipelines Compression writes payload bytes to stdout and accounting to stderr: ```bash cat large.json | caveman tools compress > compact.json 2> report.json ``` When the engine binary is missing, this path emits original bytes and a structured zero-ratio warning. It does not break the pipe or claim compression. Use machine modes when output feeds another process: ```bash caveman learn --json caveman learn --plain caveman stats --json caveman tools skills list --json ``` They do not open interactive menus or prompt for input. ## Missing runtime behaviour Most affected local commands degrade to an explicit pass-through when a companion binary is missing. Output stays unchanged, reduction is zero, and the warning names the repair command. `wrap` starts the [local proxy](/docs/proxy) for that session when it can. If a foreign process already owns the port, wrap will not restart or trust it. An interactive invocation can offer to launch the agent directly when the proxy binary is missing. A script must make sure the listener is up. The npm package does not contain compression, memory, browsing, or token-counting implementations. It resolves and drives the public binaries. `caveman setup` is the source of truth for what this machine can run. ## Numbers and telemetry Local reduction figures stay `inferred`. Commands do not convert them into monthly savings or `verified` claims. Anonymous CLI telemetry covers command name, version, platform, duration, exit class, and aggregate local token counts. It excludes prompts, code, file paths, arguments, model names, credentials, and dollar fields. Disable it with any of these: ```bash caveman telemetry off CAVEMAN_TELEMETRY=0 caveman learn DO_NOT_TRACK=1 caveman learn ``` See [Telemetry](/docs/telemetry) for the complete payload and precedence. --- --- title: TypeScript SDK summary: Compress payloads and read spend from Node. canonical: https://docs.caveman.so/docs/sdk/typescript license: MIT capability: sdk-ts updated: 2026-08-26T04:05:35+02:00 basis: inferred --- # TypeScript SDK > Compress payloads and read spend from Node. `@caveman-ai/sdk` is a zero-runtime-dependency TypeScript client for provider calls, recoverable compression, tracing, deferred tools, and request policy. It talks to a Caveman service you configure; it does not embed the local engine. - Package: `@caveman-ai/sdk` - Runtime: Node.js 22.13 or newer. - Dependencies: None at runtime. - Module: ES module with bundled TypeScript declarations. - Licence: MIT. ## Install ```bash npm install @caveman-ai/sdk ``` ## Create a client ```ts const cave = new Cave({ apiKey: process.env.CAVE_API_KEY!, baseURL: "http://127.0.0.1:8787", agent: "support-agent", }); ``` `apiKey`, `baseURL`, and `agent` are required. Service URLs must be absolute HTTP or HTTPS URLs without embedded credentials, query strings, or fragments. ## Compress ```ts const result = await cave.compress("large payload"); console.log(result.output); console.log(result.tokensBefore, result.tokensAfter); console.log(result.ratio, result.basis); console.log(result.recoveryHandle); ``` The SDK sends the payload to the configured compression endpoint and maps the engine report. On a transport or parse problem it returns original payload, ratio zero, and no recovery handle. It never reimplements a compressor inside JavaScript. `basis` is `inferred`. Token fields come from the compressor's local counter, not provider usage. ## Provider clients ```ts const openai = cave.openai({ upstreamKey: process.env.OPENAI_API_KEY }); const anthropic = cave.anthropic({ upstreamKey: process.env.ANTHROPIC_API_KEY }); const gemini = cave.gemini({ upstreamKey: process.env.GEMINI_API_KEY }); const vertex = cave.vertex({ upstreamKey: process.env.GOOGLE_ACCESS_TOKEN }); const response = await openai.responses.create({ model: "gpt-5.6", input: "Summarize this incident", }); ``` Provider clients constrain raw requests to their provider prefix. The separate `bedrock()` method returns a validated descriptor for AWS SDK configuration and performs no request. ## Narrow one request Request options can switch project-enabled work off for one call: ```ts await cave.openai().responses.create(body, { cave: { optimize: "off" }, }); await cave.openai().responses.create(body, { cave: { optimize: { compress: false, cacheHints: false } }, }); ``` Boolean `true` asks for the corresponding capability. It does not grant permission or bypass project policy. Unknown option fields and styles throw before the request. ## Read disclosure headers ```ts const response = await cave.openai().raw("/v1/responses", { method: "POST", body, }); const receipt = parseReceipt(response.headers); ``` The receipt may include mode, applied optimizations, cache status, request id, inferred compression counts, and a recovery handle. A missing header remains absent. The parser does not invent a default value. ## Local helpers Several helpers run without a request: - `assemble()` orders stable, session, and volatile context slots - `retryLoopBreaker()` stops a consecutive identical tool-call loop - `parseReceipt()` decodes response headers - `gatewayHeaders()` and `gatewayConfig()` build provider client configuration - `policyUnitFraction()` produces deterministic policy assignment input `trace()` and `exporter()` create correlated spans. `tools()` can keep an initial subset of a catalog and load more through connected search. `context.pack()` is also connected and returns deferred item ids rather than silently discarding omitted context. The `jobs` surface is reserved. Its methods throw `cave_async_jobs_unavailable` locally and send no request. This package needs a configured service for provider calls and `compress()`. Accountless local engine compression ships through the CLI and Go runtime. Installing the SDK alone does not start that runtime. ## What it will not claim SDK compression counts are not provider counts. Response receipts are not invoices. No local SDK result becomes a monthly or `verified` saving. --- --- title: Python SDK summary: The same surface for Python agents. canonical: https://docs.caveman.so/docs/sdk/python license: MIT capability: sdk-python updated: 2026-08-26T04:05:35+02:00 basis: inferred --- # Python SDK > The same surface for Python agents. `caveman-sdk` is the Python client for the same connected surface as the TypeScript SDK. It uses only the Python standard library and includes type information. - Distribution: `caveman-sdk` - Import: `caveman_cloud` - Runtime: Python 3.13 or newer. - Dependencies: None at runtime. - Licence: MIT. ## Install ```bash python -m pip install caveman-sdk ``` The `caveman` package on PyPI is unrelated. Distribution and import names are deliberately different. ## Create a client ```python from caveman_cloud import Cave cave = Cave( api_key=os.environ["CAVE_API_KEY"], base_url="http://127.0.0.1:8787", agent="support-agent", ) ``` `api_key`, `base_url`, and `agent` are required. URLs must be absolute HTTP or HTTPS service URLs without embedded credentials, a query, or a fragment. ## Compress ```python result = cave.compress("large payload") print(result.output) print(result.tokens_before, result.tokens_after) print(result.ratio, result.basis) print(result.recovery_handle) ``` `compress()` delegates to the configured service. A transport or response problem returns original payload, ratio zero, and no recovery handle. Python does not carry a second compressor implementation. Counts use `basis="inferred"`. They are local compressor estimates, not provider-reported usage. ## Provider clients ```python openai = cave.openai(upstream_key=os.environ.get("OPENAI_API_KEY")) anthropic = cave.anthropic(upstream_key=os.environ.get("ANTHROPIC_API_KEY")) gemini = cave.gemini(upstream_key=os.environ.get("GEMINI_API_KEY")) vertex = cave.vertex(upstream_key=os.environ.get("GOOGLE_ACCESS_TOKEN")) response = openai.responses.create({ "model": "gpt-5.6", "input": "Summarize this incident", }) ``` The provider wrappers expose native request paths through the configured provider prefix. `bedrock()` returns a validated configuration descriptor without making a network call. ## Narrow one request ```python cave.openai().responses.create(body, optimize="off") cave.openai().responses.create( body, optimize={"compress": False, "cache_hints": False}, ) ``` Boolean `True` asks for a capability and remains subject to project policy. It does not enable anything by itself. Unknown fields and invalid styles raise `ValueError` before a request. ## Read disclosure headers ```python from caveman_cloud import parse_receipt receipt = parse_receipt(response.headers) print(receipt.mode, receipt.optimizations) print(receipt.tokens_before, receipt.tokens_after) print(receipt.recovery_handle) ``` `parse_receipt` accepts urllib headers, a dictionary, or key-value pairs. Missing headers stay `None`. ## Local helpers Python mirrors the TypeScript names in Python style: - `assemble()` orders context by stability - `retry_loop_breaker()` interrupts identical consecutive tool calls - `parse_receipt()` decodes response disclosure - `gateway_headers()` and `gateway_config()` build provider configuration - `policy_unit_fraction()` returns deterministic policy assignment input Tracing, OTLP export, deferred tool search, context packing, shared context, checkpoints, and artifacts are also available. Connected features use the configured service. The reserved jobs client raises `cave_async_jobs_unavailable` locally and performs no request. Install `caveman-sdk`, then `import caveman_cloud`. Avoid both bare `caveman` package names. They belong to other projects. ## What it cannot do alone Installing the Python package does not install or start the local engine. It does not turn inferred compression counts into provider usage, currency, monthly savings, or `verified` results. --- --- title: MCP server summary: Expose compression and recall as tools any MCP client can call. canonical: https://docs.caveman.so/docs/mcp license: MIT capability: mcp updated: 2026-08-26T04:05:35+02:00 basis: inferred --- # MCP server > Expose compression and recall as tools any MCP client can call. `caveman-mcp` exposes engine compression, recovery, statistics, and TOON conversion to any MCP host. It speaks line-delimited JSON-RPC over stdin and stdout and opens no network connection. - Transport: MCP over stdio. - Protocol version: `2024-11-05` - Tools: Compression, recovery, session stats, TOON encode, and TOON decode. - Reports: `inferred` - Core licence: BSL 1.1. ## Build from source `caveman-mcp` is not published on npm today. Build the server from the public source: ```bash git clone https://github.com/JuliusBrussee/caveman go build -o ./bin/caveman-mcp ./public/mcp/cmd/caveman-mcp ``` Register the resulting binary with an MCP host: ```json { "mcpServers": { "caveman": { "command": "/absolute/path/to/bin/caveman-mcp", "args": [] } } } ``` The main CLI can install host configuration after the binary is available: ```bash caveman tools mcp install claude --server caveman caveman tools mcp uninstall claude --server caveman ``` ## Tools | Tool | Input | Result | | --- | --- | --- | | `caveman_compress` | `input`, optional `content_type` | Smaller text or original input, counts, ratio, basis, content type, method, and optional handle. | | `caveman_retrieve` | `recovery_handle`, optional `query` | Exact original for an empty query, or a query-selected view. | | `caveman_stats` | None | Session requests, tokens before and after, ratio, and `basis: "inferred"`. | | `caveman_toon_encode` | JSON string in `input` | TOON text plus input and output byte counts, or original input with a note. | | `caveman_toon_decode` | TOON string in `input` | Decoded JSON, or an explicit invalid-TOON error. | Compression detects content type unless the caller forces one. Malformed, incompressible, or non-smaller input passes through with ratio zero. A recovery persistence error also passes through only when the returned bytes and accounting prove that no lossy view escaped. ## Recovery The server opens the shared store at `~/.caveman/ccr.db`. `CAVEMAN_CCR_DB` selects another path, and `CAVEMAN_HOME` changes the default parent directory. ```text caveman_compress({ input: largePayload }) caveman_retrieve({ recovery_handle: "ccr_..." }) ``` Set `CAVEMAN_MCP_EPHEMERAL=1` for an in-memory store. Handles from that process stop resolving when the process exits. Use recovery as a last resort. Elision markers and visible invariants often answer count or field questions without adding another agent turn. When recovery is needed, one broad query is usually cheaper than many narrow calls. An unknown handle returns `cave_unknown_handle`. It never becomes an empty successful result. ## TOON conversion TOON encoding is explicit and checks JSON round-trip. Encoding may return a valid TOON result even when it is not smaller, because the caller asked for conversion and receives both sizes. Invalid or unsupported JSON returns unchanged input with a note. Decoding fails with `cave_invalid_toon` on invalid input. It never emits raw TOON while claiming the result is JSON. ## Protocol limits Inbound JSON-RPC lines and ordinary tool results are capped at 16 MiB. Oversized values return `cave_payload_too_large` and the server continues serving later requests. Exact recovery is exempt from the result cap so a valid original is never made unrecoverable by the MCP framing limit. Handler panics become `cave_tool_panicked` tool errors instead of terminating the server. Unknown tools also return an explicit tool error. stdout carries JSON-RPC only. Logs and diagnostics go to stderr. Wrapping this binary with a script that prints a banner to stdout breaks MCP framing. ## What it cannot do The server does not browse, store durable semantic memories, call a model, or connect to a hosted account. Its session stats are local inferred compression accounting, not provider usage or `verified` savings. --- --- title: "caveman-shrink" summary: Shrink command output before it reaches the model. canonical: https://docs.caveman.so/docs/shrink license: MIT capability: shrink updated: 2026-08-30T15:24:20+02:00 basis: inferred --- # caveman-shrink > Shrink command output before it reaches the model. `caveman-shrink` reduces MCP and OpenAI tool catalogs before a model reads them. It keeps the structural surface used to form a tool call and shortens model-visible annotation text. - Input: MCP or OpenAI tool-catalog JSON. - Stdin limit: 32 MiB. - Core licence: BSL 1.1. - npm launcher: MIT. - Reports: `inferred` ## Build the documented source ```bash git clone https://github.com/JuliusBrussee/caveman go build -o ./bin/caveman-shrink ./public/shrink/cmd/caveman-shrink ``` A published npm launcher also downloads a matching binary and verifies a key-signed manifest plus artifact SHA-256. The registry release may trail the source documented here. ```bash npx -y caveman-shrink lint tools.json ``` ## Compress a catalog ```bash cat tools.json | caveman-shrink > tools.min.json ``` Compressed JSON goes to stdout. The JSON accounting report goes to stderr, so a pipeline receives catalog bytes only. The report includes tokens before, tokens after, ratio, basis, content type, and a recovery handle when a lossy result was emitted. Accepted shapes include an MCP `{ "tools": [...] }` object, an OpenAI tool array, a `{ "functions": [...] }` object, and a single named tool object. ## What survives The structural selection profile preserves: - Tool and parameter names - Parameter types - Enum values - Required fields - Default and constant values - Internal reference targets Short descriptions remain whole. Long descriptions keep their lead and recognized constraint sentences. Examples, titles, comments, and other annotation bloat may be removed. ```bash caveman-shrink lint tools.json ``` `lint` prints inferred before and after counts per tool and for the complete catalog. It does not commit a recovery record or replace the file. Tests prove that names, parameters, enums, and required fields survive. Description text remains model-visible, so only a model eval can show that a given model selects the same tool for your cases. ## Recover ```bash caveman-shrink recover ccr_xxxxxxxx > tools.original.json ``` Before returning a lossy view, shrink stores exact original bytes in the shared CCR database. The default is `~/.caveman/ccr.db`; `CAVEMAN_CCR_DB` selects another path. Recovery works from a later process. Malformed input, an unavailable recovery store, or a candidate that is not smaller returns the original catalog unchanged with ratio zero and no handle. Input beyond 32 MiB fails with `cave_input_too_large`. ## CLI wrapper The main CLI exposes two different shrink paths: ```bash caveman tools shrink -- npm test caveman tools compress catalog < tools.json caveman tools compress catalog lint tools.json caveman tools compress catalog recover ccr_xxxxxxxx ``` The first command compresses command output. The catalog commands delegate to `caveman-shrink`. They are separate because terminal output and tool definitions have different structures and safety contracts. ## Licence boundary The npm launcher is MIT. Go source and downloaded binary use BSL 1.1 with the first-party self-hosting grant. Offering the core functionality to third parties as a hosted, managed, or embedded service needs a commercial licence. --- --- title: "caveman-browse" summary: Read web pages as compressed accessibility trees instead of raw HTML. canonical: https://docs.caveman.so/docs/browse license: BSL-1.1 capability: browse updated: 2026-08-26T04:05:35+02:00 basis: inferred --- # caveman-browse > Read web pages as compressed accessibility trees instead of raw HTML. `caveman-browse` is a local Chrome driver for agents. It reads Chrome's accessibility tree, turns it into compact UID-addressed text, and stores raw tree bytes behind a recovery handle. - Transport: MCP over stdio, with a direct CLI helper. - Browser path: Chrome DevTools Protocol. - Snapshots: Compressed accessibility text, not raw HTML. - Basis: `inferred` - Licence: BSL 1.1. ## Build from source `caveman-browse` is not published on npm today. ```bash git clone https://github.com/JuliusBrussee/caveman go build -o ./bin/caveman-browse ./public/browse/cmd/caveman-browse ``` Run with no arguments to serve MCP over stdio: ```bash ./bin/caveman-browse ``` ## MCP tools | Tool | Purpose | | --- | --- | | `browser_snapshot` | Navigate when a URL is supplied, wait, then return a compact accessibility view. | | `browser_act` | Click, type, select, scroll, or wait using a UID from the latest snapshot. | | `browser_eval` | Evaluate a JavaScript expression in the page. | | `browser_recover` | Return raw accessibility bytes, or a query-selected view of them. | Configure an MCP host with the absolute binary path: ```json { "mcpServers": { "caveman-browse": { "command": "/absolute/path/to/bin/caveman-browse", "args": [] } } } ``` ## Take a useful snapshot Large pages should include the task as a query: ```text browser_snapshot({ url: "https://example.com/settings", query: "save notification settings" }) ``` The query keeps matching accessible nodes and their ancestors. CCR retains full raw tree. This usually costs less than taking a full snapshot and recovering later. `interactive: true` keeps UID-bearing nodes and their ancestors only. It is useful for dense control surfaces but hides most page text. Query focus remains the better default when the agent must read. Snapshot output is text: ```text [a1] button "Save" [a2] checkbox "Email alerts" caveman before=... view=... after=... ratio=... basis=inferred handle=ccr_... ``` Only actionable or unknown custom roles receive UID tokens. The trailing line counts the exact result delivered to the agent. `view` isolates compact tree cost; `after` includes the accounting line. ## Act, then resnapshot ```text browser_act({ action: "click", uid: "a1" }) browser_act({ action: "type", uid: "a3", text: "alerts@example.com" }) browser_act({ action: "select", uid: "a4", option: "Daily" }) browser_act({ action: "scroll", uid: "a5" }) browser_act({ action: "wait" }) ``` UIDs belong to the latest successful snapshot. Unknown or stale UIDs fail explicitly. Click, type, select, and scroll report that dispatch succeeded, but they do not prove application state settled. Take another focused snapshot to verify the result. `wait` is a fixed short delay, not a condition-based page assertion. ## Direct CLI ```bash caveman-browse snapshot https://example.com "save settings" caveman-browse snapshot -i https://example.com caveman-browse act click caveman-browse eval "document.title" caveman-browse recover caveman-browse close ``` Direct commands share one detached, isolated Chrome until `close`. ## Navigation and limits Navigation accepts HTTP, HTTPS, `about:blank`, and bounded `data:text/html` URLs. Local files and privileged browser schemes fail with `cave_browser_url_denied`. Snapshot wait is limited to 30 seconds. Query text is limited to 4 KiB, and a data URL to 1 MiB. A snapshot that cannot produce a smaller recovery-backed UID view fails and keeps the previous UID map rather than dumping raw AX JSON into model context. ## Browser selection Environment variables select runtime behaviour: ```text CAVEMAN_BROWSE_CHROME explicit Chrome executable CAVEMAN_BROWSE_CDP attach to an existing CDP endpoint CAVEMAN_BROWSE_USER_DATA_DIR profile directory CAVEMAN_BROWSE_HEADFUL=1 show the browser window CAVEMAN_BROWSE_EPHEMERAL=1 use in-memory recovery ``` Default mode is headless. The recovery store otherwise uses the shared CCR path. This tool exposes accessibility structure and page JavaScript. It does not prove visual layout, screenshot appearance, animation, or pointer geometry. Use a visual browser tool when those properties matter. `browser_eval` runs caller-supplied JavaScript in the active page. Treat it as code execution in that browser session and use it only on pages you intend to control. --- --- title: Overview summary: Build an agent that is efficient by construction. canonical: https://docs.caveman.so/docs/agent-sdk license: MIT capability: agent-sdk updated: 2026-08-30T15:03:57+02:00 basis: inferred --- # Overview > Build an agent that is efficient by construction. `@caveman-ai/agent` is a TypeScript runtime for tool-using agents. You define the agent, its tools, context, memory, output contract, and evals in one source graph. Every successful run returns a receipt. A run that fails after spending throws with the same partial receipt attached. It does not need a Caveman account. On a machine with Node and a provider key it calls the provider directly, in `observe-only` mode: no local transform, and no efficiency claim. When the local Caveman engine is running, eligible calls can run in `optimized` mode. Local figures stay `inferred`. Nothing in this package emits `verified`. - Package: `@caveman-ai/agent` - Source version: `0.2.0` - On npm today: `0.1.0` - Runtime: Node.js 22.19 or newer. - Licence: MIT. The pages that follow document the 0.2 source in [JuliusBrussee/agent-sdk](https://github.com/JuliusBrussee/agent-sdk). npm still ships 0.1.0, which is an older surface. Use the checkout if you are testing anything described here. ## How a run is assembled
Definition. agent(), tools, context, memory, output, evals. One source graph.
Runtime. Sandbox, budgets, breakers, compaction, receipts. Tools never skip schema, timeout, or abort checks.
observe-only Direct to your provider. Default when the local engine is not running.
optimized Local engine reachable. Eligible transforms and recovery can run. Still inferred.
Receipt. Model calls, tool calls, catalog cost basis, stop reason, compactions, subagents, resume state. claimBasis: "inferred".
A mixed graph that partly bypassed the local runtime reports observe-only. The label under-claims rather than averaging.
`auto()` picks a configured model. It does not classify tasks or route between models. That is a different product; see [Model router](/docs/router). ## Smallest agent ```ts const support = agent({ id: "support", instructions: "Answer from policy. Never invent policy.", model: auto(), }); const result = await run(support, "Can I get a refund?"); console.log(result.text); console.log(result.mode); console.log(result.receipt); ``` That run works with nothing but Node and a provider credential. `result.mode` is `observe-only` there. Prefer a directory if the agent will grow: ```text support-bot/ ├── instructions.md ├── agent.ts ├── skills/ ├── tools/ ├── subagents/ └── evals/ ``` ```ts const support = await loadAgentDir("./support-bot"); const result = await run(support, "Where is order A-123?"); ``` Skill descriptions enter the stable prefix. Skill bodies stay on disk until the model asks for one, so adding a large skill does not enlarge every request. ## What lives in this product The Agent SDK is one thing you install, with several surfaces behind it. Source build, doctor, and the npm version gap. The initializer that writes a typechecking project. agent(), context, output, filesystem, subagents. Effects, result policies, schemas. run, stream, receipts, budgets, durable resume. required, fixture, and host. Next-turn recall, local only. Recover, summarise, clamp, stop. One caveman_code cell. Paginated provider data, fail closed. Profile, search, freeze, holdout. Exact-pinned framework lanes. The [TypeScript SDK](/docs/sdk/typescript) is a different package. `@caveman-ai/sdk` talks to a configured Caveman service. `@caveman-ai/agent` is the agent runtime. Installing one does not start the other. The Agent SDK repository also ships a frozen wire-contract package used by other Caveman products. It is not this runtime, and this site does not document it. ## What it will not do It will not mint `verified` savings. `claimBasis` is `inferred` on every public run. USD figures are public-catalog list-price subtotals, never invoices. Token counts are not money. It will not silently call itself optimized. If any call in the graph went straight to the provider, `mode` is `observe-only`. It will not invent a routing policy from `auto()`. A pinned string model is a pinned string model. A lock hash binds reviewed bytes and policy. It is not a signature, a runtime attestation, or proof that those bytes served production traffic. Claude locked compilation is refused. The public Claude lane stays unlocked. Do not publish a savings percentage from a local build report. --- --- title: Install summary: Build 0.2 from source, run doctor, and the gap with the npm 0.1 package. canonical: https://docs.caveman.so/docs/agent-sdk/install license: MIT capability: agent-sdk updated: 2026-08-30T15:03:57+02:00 basis: inferred --- # Install > Build 0.2 from source, run doctor, and the gap with the npm 0.1 package. The documented surface is `@caveman-ai/agent` 0.2 in source. npm currently publishes 0.1.0, which is an older API. Clone the repository if you are following these pages. - Node: 22.19 or newer. - CLI binary: `caveman-agent` - Source: `JuliusBrussee/agent-sdk` - npm today: `@caveman-ai/agent@0.1.0` ## Build 0.2 from source ```bash git clone https://github.com/JuliusBrussee/agent-sdk.git cd agent-sdk npm ci --prefix packages/pebble-protocol npm ci --prefix packages/agent npm ci --prefix packages/create-caveman-agent npm --prefix packages/agent run build ``` The package binary is `caveman-agent`, from `packages/agent`. After the build you can run it with `node packages/agent/dist/cli.js`, or link the package into a project that depends on `@caveman-ai/agent@0.2.0`. A full repository test pass is `npm test` at the repo root. Restricted macOS sandbox and loopback tests need host permissions before a failure counts as a product defect. ## Check the machine before a provider call ```bash caveman-agent doctor caveman-agent doctor --json ``` Doctor makes no model request. Human output prints `verified savings: $0`. | Check | Pass | Warn | Fail | |---|---|---|---| | `node` | 22.19 or newer | | older Node | | `sandbox` | containment probe passed | | probe failed | | `engine` | transform registry loaded | engine missing; observe-only still works | | | `runtime_cli` | `caveman` CLI answers `version` | CLI missing; observe-only still works | | | `gateway` | local runtime reachable | not reachable; telemetry off, observe-only | | | `project` | `caveman.config.ts` loads | no config in this directory | config, entry, eval, or context error | | `lock` | lock is current | no lock; unlocked runs are fine | lock drift | | `provider` | selected model has a credential | no model, or several credentials and no `CAVE_MODEL` | bad `CAVE_MODEL` or `provider.json` | Missing engine, runtime CLI, or local runtime is a warning and doctor still exits 0, because observe-only runs still reach a real model. Broken sandbox containment, invalid config, or lock drift fails. Optional local engine: ```bash npm install -g @caveman-ai/cli caveman start ``` The Agent SDK accepts a loopback runtime only after identity, process state, and executable ownership check out. An unrelated listener on the same port does not receive provider credentials; the run goes direct in observe-only mode. ## CLI ```bash caveman-agent --help caveman-agent --version caveman-agent doctor [--json] caveman-agent dev [entry] [prompt] caveman-agent build [config] [--verbose] [--accept-prefix-shrink] caveman-agent check [config] caveman-agent register caveman-agent connect … ``` `dev` watches the project and reuses one staged source snapshot until source, config, eval, or file context changes. `build` and `check` own Cave Build execution. `register` is a separate command; it is not a substitute for `build`. A generated project wires the same commands through npm scripts: `npm run doctor`, `npm run dev`, `npm run build`, `npm run check`. ## npm 0.1.0 ```bash npm install @caveman-ai/agent ``` That installs 0.1.0 today. These pages describe 0.2 source. Do not treat a registry install as the surface documented here until 0.2 is published. `@caveman-ai/agent` is this runtime. `@caveman-ai/sdk` is the thin client in [TypeScript SDK](/docs/sdk/typescript). The bare `caveman` package on npm is unrelated. ## What it will not do Doctor does not spend a provider call, and a green doctor is not a lock. Observe-only is a valid run mode, not a broken install. Linking `@caveman-ai/cli` is optional; without it the SDK still calls your provider. --- --- title: Create an agent summary: The initializer that writes a typechecking project with a starter eval graph. canonical: https://docs.caveman.so/docs/agent-sdk/create license: MIT capability: create-agent updated: 2026-08-30T15:03:57+02:00 basis: inferred --- # Create an agent > The initializer that writes a typechecking project with a starter eval graph. `@caveman-ai/create-agent` writes a TypeScript agent project that typechecks, loads under doctor, and has a starter eval graph. It has no runtime of its own. The generated project depends on `@caveman-ai/agent`. - Package: `@caveman-ai/create-agent` - Source version: `0.1.0` - On npm today: `0.1.0` - Node: 22.19 or newer. - Licence: MIT. npm has published this package. The template it currently ships targets `@caveman-ai/agent` 0.2. A generated project will not resolve that dependency from the registry until 0.2 is published; build the Agent SDK from source and point the project at that checkout, or wait for the registry. ## Scaffold From a published initializer: ```bash npm create @caveman-ai/agent@latest my-agent cd my-agent ``` From the Agent SDK checkout, after `npm ci --prefix packages/create-caveman-agent` and its build: ```bash node packages/create-caveman-agent/dist/index.js my-agent ``` Non-interactive: ```bash npm create @caveman-ai/agent@latest my-agent -- --provider anthropic ``` Supported providers: `anthropic`, `openai`, `google`. Exactly one detected provider credential is selected without a prompt. Zero or several credentials prompt once. Secrets are never printed or written. Skip dependency installation when another tool owns it: ```bash npm create @caveman-ai/agent@latest my-agent -- --provider openai --no-install ``` The initializer copies the `support-bot` template, rewrites the pinned model line for the chosen provider, and writes `.caveman/provider.json` with mode `0o600`. | Provider | Model written into `agent.ts` | |---|---| | `anthropic` | `anthropic/claude-sonnet-5` | | `openai` | `openai/gpt-5.4-mini` | | `google` | `google/gemini-2.5-flash` | Those pins exist so the frozen prefix clears the provider's minimum cacheable length. `auto()` is not the template default, because it can pick a model whose cache floor the prefix does not meet. ## What you get ```text my-agent/ ├── instructions.md ├── agent.ts ├── run.ts ├── caveman.config.ts ├── AGENTS.md ├── skills/ ├── tools/ │ └── lookup_order.ts ├── tickets/ ├── evals/ │ └── support.eval.ts └── package.json ``` Scripts in the generated `package.json`: ```bash npm run doctor npm run ticket -- tickets/refund-request.md npm run dev npm run build npm run check ``` Review `evals/support.eval.ts` before `npm run build`. That command runs every declared fixture inside the configured search budget. There is no separate approval gate. ## What it will not do The initializer does not log you in, start the local engine, or spend a provider call. Doctor on the generated project still makes no model request. Local build evidence stays `inferred`; verified savings stay `$0`. --- --- title: Define an agent summary: agent(), context, output, filesystem, and subagents as one frozen definition. canonical: https://docs.caveman.so/docs/agent-sdk/define license: MIT capability: agent-sdk updated: 2026-08-30T15:03:57+02:00 basis: inferred --- # Define an agent > agent(), context, output, filesystem, and subagents as one frozen definition. An agent is one frozen `AgentDefinition`. `agent()` is the constructor. Everything downstream, sandbox, Context IR, build, receipts, sees that object. There is no second hidden definition. ```ts id: "support", instructions: "Answer from policy. Never invent policy.", model: auto(), }); ``` `id` must match `^[a-z0-9][a-z0-9_-]{0,95}$`. Duplicate tool names fail at construction. Tool names starting with `cave_` are reserved and also fail at construction. Default sandbox is `required`. Default reasoning is `low`. Options: `off`, `minimal`, `low`, `medium`, `high`. ## Model selection `model` is one of: `auto()`, a `provider/model` string, or a Pi model object. `auto()` resolves in this order: `CAVE_MODEL`, then `.caveman/provider.json`, then the baseline model for the sole supported credential in the environment. | Credential | Baseline if it is the only one | |---|---| | `ANTHROPIC_API_KEY` | `anthropic/claude-haiku-4-5` | | `OPENAI_API_KEY` | `openai/gpt-5.4-mini` | | `GEMINI_API_KEY` or `GOOGLE_API_KEY` | `google/gemini-2.5-flash` | Zero credentials fail. Several credentials and no `CAVE_MODEL` fail. A pinned string must use `provider/model` form. `auto()` never classifies the task or picks between models on quality. ## Context and output ```ts const playbook = context({ id: "support.playbook", kind: "skill", source: file("./support.md"), stability: "build", safety: "S0", priority: "required", }); const answer = output({ maxTokens: 500, schema: schema.object({ answer: schema.string() }), }); ``` `context()` labels a segment. Kind, stability, safety class, priority, recovery, cache region, and privacy class are all explicit. Defaults: safety `S0`, priority `required`, recovery `none`, privacy `local_sensitive`, cache region `frozen_prefix` when stability is `build` and `live_zone` otherwise. Build-stable context enters the frozen prefix. Session and turn context stay live. Volatile data in the stable cache zone is rejected. On drift or transform failure the runtime fails open to the original provider-visible bytes. `output({ maxTokens })` is a token budget on the answer. An optional schema constrains the shape. ## Filesystem layout `loadAgentDir(rootDir)` lowers a convention directory into an ordinary `agent()` call. ```text support-bot/ ├── instructions.md ├── agent.ts ├── skills/ │ ├── refund-policy.md │ └── shipping-claims.md ├── tools/ │ └── lookup_order.ts ├── subagents/ └── evals/ ``` `agent.ts` default-exports an `AgentDirConfig`: model, optional budget, breakers, and extra context. Instructions come from `instructions.md`. Each `tools/*.ts` default-exports a `tool()`. Each `skills/.md` contributes a short description to one build-stable skill segment; the body is served later by `cave_skill` as a live-zone tool result. The model picks from descriptions. There is no embedding ranker. The loader writes a generated module entry at `.caveman/agent-dir-entry.mjs`. Directory-loaded runs print a receipt by default, because stdout is yours. `run()` from a hand-built definition does not, because stdout may be a protocol channel. ## Subagents ```ts const researcher = agent({ id: "researcher", instructions: "Search, then return sources.", model: auto(), }); const root = agent({ id: "support", instructions: "Delegate research. Answer from policy.", model: auto(), tools: [ subagent({ name: "research", description: "Look up one question.", agent: researcher, }), ], }); ``` Defaults: `maxInputChars` 32,768, `maxCalls` 1, `maxCostUsd` 1, `maxContextTokens` 128,000. Under a USD-metered parent the child's wallet is carved from the remaining budget and unused remainder returns. A token-metered parent cannot fund a child that declares no `maxTokens`. A host-mode child is refused under a `sandbox: "required"` parent. ## Optional workspace files The core runtime does not search the repository. Products that want `AGENTS.md`, Agent Skills, Agent Plugins v1, or Vercel OpenPlugin can opt in: ```ts applyAgentEnvironment, loadAgentEnvironment, } from "@caveman-ai/agent/plugins"; const environment = await loadAgentEnvironment({ cwd: process.cwd() }); const reviewer = applyAgentEnvironment(agent({ id: "reviewer", instructions: "Review requested changes.", model: "openai/gpt-5.4", sandbox: "host", }), environment); ``` Metadata enters the stable prefix. Full skill bodies and command markdown enter only after activation. Plugin MCP, hooks, custom agents, and plugin subprocesses are reported and not launched. Ambient secrets are not inherited. ## What it will not do `agent()` does not start a process, open a network connection, or look for files. Shared-memory provenance is refused here, at construction, not after a model call. A definition with `sandbox: "host"` cannot produce a Cave Build lock. --- --- title: Tools summary: Input schemas, effect classes, timeouts, and what happens to a tool result. canonical: https://docs.caveman.so/docs/agent-sdk/tools license: MIT capability: agent-sdk updated: 2026-08-30T15:03:57+02:00 basis: inferred --- # Tools > Input schemas, effect classes, timeouts, and what happens to a tool result. A tool declares its input, side effect, timeout, and what happens to the result, then implements `execute`. The runtime validates input before your code runs, and validates output before any result can enter model context or a durable journal. ```ts const lookupPolicy = tool({ name: "lookup_policy", description: "Read current refund policy.", input: schema.object({ region: schema.string() }), output: schema.object({ region: schema.string(), refundWindowDays: schema.number(), }), effect: "read", result: "auto", async execute({ region }) { return { region, refundWindowDays: 14 }; }, }); ``` Names match `^[a-zA-Z][a-zA-Z0-9_-]{0,127}$`. Names beginning with `cave_` are reserved by the framework (`cave_skill`, memory tools, recovery). Duplicate names on one agent fail at `agent()`. Default timeout is 30,000 ms. Default result policy is `auto`. `effect` has no default; you have to say it. ## Effects | Effect | Meaning | |---|---| | `read` | No durable side effect. Fixture sandbox allows it. Programmatic mode may speculate it. | | `write` | Mutates something. Fixture sandbox blocks it. Host mode executes it. | | `idempotent` | Safe to retry with the same arguments. Never speculated. | | `external` | Leaves the process. Never speculated. | Repeated identical calls are stopped by default. Set `allowRepeat: true` on a polling tool whose job is to be called again with the same arguments. ## Result policies | Policy | What the model sees | |---|---| | `auto` | A locked plan may choose inline, paging, compression, or exact recovery. | | `inline` | The result stays in current context. | | `page` | Bounded pages. | | `compress` | An eligible locked transform. | | `exact_ccr` | Replacement only after byte-exact recovery is stored. | `artifact()` can stand in for a result policy when you need paging or exact recovery with an inline token cap. Default artifact strategy is `page`, default `maxInlineTokens` is 1,200, default recovery is `exact_ccr`. ## Schemas `schema` is a small TypeBox wrapper: `string`, `number`, `integer`, `boolean`, `object`, `array`, `optional`, `union`, `literal`, `null`, `any`. `input` also accepts Standard Schema v1. Libraries that emit Standard JSON Schema v1 convert to draft-07 for the provider. Validation-only libraries pass `inputJSONSchema`; the framework still runs the vendor validator first, including async transforms. `output` follows the same rules. Output validation runs after `execute` and before anything reaches the model. A mismatch is a tool error. The invalid raw value stays hidden. Schema-less tools keep native JSON serialization. Standard Schema validators can close over mutable state. Ordinary runs allow that. Cave Build locks and durable runs refuse opaque validator identity. Pass `schemaSemanticsSHA256` as the lowercase SHA-256 of validator code, dependencies, and captured state, and change it when any of those change. ## Nested tools A composite tool may declare `nestedTools`. Nested tools are flat: no nested-nested. A `read` composite cannot contain an effectful child. Nested dispatch still goes through the same budget, breaker, schema, timeout, and receipt path as a top-level call. Programmatic mode is the usual reason to do this. See [Programmatic tools](/docs/agent-sdk/code). ## Sandbox reminder Production tools with the default `sandbox: "required"` run in a restricted worker imported from a staged source graph. Programmatic `run()` must pass `RunOptions.entryPath` pointing at the module that exports the definition. The CLI supplies this. The [Sandbox](/docs/agent-sdk/sandbox) page is the contract. ## What it will not do A tool closure does not inherit the parent environment. Signing, deployment, bootstrap, database, and ambient secret names are denied in the worker. `sandboxProfile.network: true` is not scoped egress; it requests unbounded egress and fails closed, because scoped egress does not exist yet. --- --- title: Run a turn summary: run, stream, receipts, budgets, and durable resume. canonical: https://docs.caveman.so/docs/agent-sdk/run license: MIT capability: agent-sdk updated: 2026-08-30T15:03:57+02:00 basis: inferred --- # Run a turn > run, stream, receipts, budgets, and durable resume. `run()` executes one turn and returns a `RunResult`. `stream()` yields typed events for the same work. Both take an `AgentDefinition`, an input, and optional `RunOptions`. ```ts const result = await run(support, "Can I get a refund?"); console.log(result.text); console.log(result.mode); console.log(result.receipt); ``` `result.claimBasis` is always `"inferred"`. `result.receipt` is schema `caveman.agent.run-receipt.v1`. USD fields are public-catalog list-price subtotals. ## Observe-only and optimized On a machine with only Node and a provider key, `mode` is `observe-only`: the SDK calls the provider's own base URL. No local transform runs. Provider usage and local context estimates remain. With `@caveman-ai/cli` installed and `caveman start` running, eligible calls can return `mode: "optimized"`. Anthropic, OpenAI, and Google can take that path. Other providers stay direct and still report `observe-only`. Set `cave: "off"` in `RunOptions` to force observe-only. A run that carries a Cave Build lock or candidate plan refuses silent downgrade with `cave_gateway_required_for_locked_plan`. A graph that mixed the two modes reports `observe-only`. The label under-claims rather than averaging. ## Conversations and streaming ```ts const conversation = createConversation(); await run(support, "My order is late.", { conversation }); const followUp = await run(support, "What should I do next?", { conversation }); ``` Concurrent use of one conversation fails closed. A failed turn rolls back conversation and cache state. Definition, model, or plan changes rotate the cache epoch so stale prefix bytes are not replayed. `stream()` emits run, context, model, completion, and error events. Calling the iterator's `return()` aborts in-flight provider, tool, and subagent work before conversation ownership is released. Terminal `run_end` and `run_error` release ownership before delivery. ## Receipts and errors Every successful result includes the receipt: model calls, tool calls, provider usage basis, catalog cost basis, stop reason, compactions, retries, subagents, and resume state. A failure after spend throws `CavemanRunError` with the same partial receipt on `error.receipt`. `run_error` events carry it too. A run that fails after spending never loses its per-call breakdown. Directory-loaded agents print the receipt to stdout by default and write JSON under `.caveman/runs/`. Hand-built `run()` does not print, because stdout may be a protocol channel. Set `printReceipt: true` to opt in. ## Budgets and ceilings ```ts const budget: RunBudget = { maxTokens: 12_000, onExhausted: "stop", }; const result = await run(support, "Summarise this ticket.", { budget }); ``` `onExhausted` is `compact` or `stop`. Default is `compact`. See [Compaction](/docs/agent-sdk/compaction). `RunOptions.maxCostUsd` is a best-effort local spend cap in USD at public catalog list prices. It is not a provider invoice, a platform quota, or a cross-process reservation. Each priced root or descendant call reserves worst-case catalog price before the request and settles measured catalog cost after it. Exhaustion ends the run with `cave_run_cost_budget_exceeded` before the next model call. An unpriced model cannot consume `$0` of a USD cap: with the cap set, that call fails closed. Leave the cap unset for unpriced models and bound them with call ceilings instead. `maxCostUsd` and `budget` are mutually exclusive. Default `maxModelCalls` is 64. Reaching it ends the run between calls with `stopReason: "call_budget_exhausted"` and returns the partial result; it does not throw. Default `maxToolCalls` is 64. A tool call past that ceiling is blocked (the model sees a blocked result) and the run continues. Breakers (repeated tool calls, no-progress, fan-out) are opt-in. A run that declares none does not guess at a loop. ## Durable resume ```ts const result = await run(support, "Continue the investigation.", { durable: { runId: "case-42-analysis-1" }, }); ``` Durable mode journals call intent before network work. Resume restores known spend and the last committed execution boundary. A request that was in flight during a crash stays unknown, because the SDK cannot know whether the provider billed it. The receipt says so rather than guessing. `runLocked()` embeds a Pi Cave Build after `caveman-agent check` has passed at deployment. It accepts only Pi locks. Before provider traffic it validates lock integrity, agent definition, runtime, adapter, catalog, Context IR, selected plan, and the live engine registry when transforms exist. Parsing the lock file alone does not prove source freshness; `check` does. ## What it will not do `run()` cannot inject a plan or build identity. Locked and candidate execution belong to `dev`, `build`, `check`, and `runLocked()`. Costs are catalog estimates. `verifiedSavingsUsd` stays 0 on this path. Unknown model, pricing, or usage fails closed or returns an honest zero; it does not become a favourable number. --- --- title: Sandbox summary: "required, fixture, and host: where tool closures run, and what lock eligibility requires." canonical: https://docs.caveman.so/docs/agent-sdk/sandbox license: MIT capability: agent-sdk updated: 2026-08-30T15:03:57+02:00 basis: inferred --- # Sandbox > required, fixture, and host: where tool closures run, and what lock eligibility requires. Every agent has a sandbox mode. The default is `required`. The mode is set on the definition, not guessed at run time. | Mode | Where tool closures run | Writes | Lock eligible | |---|---|---|---| | `required` | Isolated, network-denied Node workers, from a staged source graph | Allowed inside the worker | Yes, when the rest of the build passes | | `fixture` | Host process. Trusted tests only | Blocked before execute | For fixture corpora | | `host` | This process, no worker | Execute | No | Host mode is an explicit opt-in. It is never the default, and it is never isolation. A host-mode child under a `required` parent is refused, so a subagent cannot walk out of its root's containment. `compile` refuses a host-mode agent with `cave_host_sandbox_lock_ineligible` before any search run. Live coding sessions therefore cannot produce a Cave Build. Coding agents that need a lock compile against fixture corpora with a contained sandbox. ## Required mode Before provider traffic, the framework copies the project-relative source graph into a per-run immutable staging directory, imports tools only from that snapshot, and tears the staging down when the stream settles. `RunOptions.entryPath` must point at the module that exports the definition. The CLI fills this in. Programmatic `run()` of a tool-using required-sandbox agent without `entryPath` is refused. The worker starts from a fixed environment baseline (`LANG`, `LC_ALL`, `PATH`, `TZ`, plus a fixture marker), not a spread of the parent env. One runtime-owned provider capability may be requested: `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, or the Google pair (`GEMINI_API_KEY` / `GOOGLE_API_KEY` as aliases). Signing, deployment, bootstrap, database, loader, and ambient secret names are denied. Network egress is blocked at the OS: a network namespace on Linux (`unshare --net`), `sandbox-exec (deny network*)` on macOS. In-process monkeypatching is defence in depth, not the boundary. Requesting unbounded egress fails closed with `cave_sandbox_network_egress_unbounded`. There is no scoped-egress mechanism yet. Linux unix-domain sockets are not covered by a network namespace. That gap is documented in the package threat model; do not treat required mode as a complete host firewall. ## Windows Ordinary agent runs, runtime startup, and explicit `sandbox: "host"` coding tools work on native Windows through `cmd.exe`. `sandbox: "required"` is fail-closed on native Windows because the package has no verified OS network-isolation boundary there. Use WSL2 for production sandboxed tools. `doctor` reports `cave_sandbox_os_network_isolation_unavailable` and the WSL2 remedy. Do not replace required sandbox with fixture mode in production. ## Fixture mode Trusted tests may set `sandbox: "fixture"`. Closures run in the host process, which is why writes are blocked: a test that needs to observe a write belongs on a contained worker or an explicit host-mode agent, not in fixture. ## What it will not do `sandbox: "host"` is not a synonym for isolation. Required mode is not available on native Windows. Fixture mode is not a production fallback. A passing doctor sandbox check is a containment probe, not a proof that every OS gap is closed. --- --- title: Memory summary: "Local, opt-in, one-turn-behind recall. Never verified, never in the frozen prefix." canonical: https://docs.caveman.so/docs/agent-sdk/memory license: MIT capability: agent-sdk updated: 2026-08-30T15:03:57+02:00 basis: inferred --- # Memory > Local, opt-in, one-turn-behind recall. Never verified, never in the frozen prefix. Agent memory is local, opt-in, and one turn behind on purpose. Turn N starts retrieval while the model runs. Turn N+1 consumes whatever finished, without waiting. Recalled text is injected immediately before the current user message. It never enters the frozen system prefix and never mutates append-only conversation history. Every injected block is labelled `inferred` and potentially stale. Current user intent, code, tools, and runtime evidence win. - Default TTL: `30d` - Default recall budget: 800 tokens. - Provenance: `local` - Consent: `local_only` - Scope: tenant, agentId, namespace ## Declare it ```ts agent, auto, createMemoryEngine, memory, run, } from "@caveman-ai/agent"; const definition = agent({ id: "support", instructions: "Resolve support requests.", model: auto(), memory: memory({ namespace: "support" }), }); const engine = createMemoryEngine({ scope: { tenant: "tenant-1", agentId: "support", namespace: "support" }, ttlMs: 30 * 86_400_000, }); await run(definition, "Remember that I prefer email updates.", { memory: { tenant: "tenant-1", engine }, }); ``` TTL must be a positive `m`, `h`, or `d` duration. `provenance: "project"` or `"external"`, and `consent: "project_shared"`, fail at `memory()` construction. The package does not implement a shared backend, so it refuses the config before a model call could discover that. Two agents that declare the same namespace in one process still do not see each other's rows: scope includes `agentId`. `RunOptions.memory.root` defaults to `CAVE_AGENT_MEMORY_ROOT`, else `~/.caveman/agent-memory`. `tenant` defaults to a single-tenant value. A one-shot `run()` without an engine keeps the explicit memory tools and starts no background work, so nothing is still running after return. Reuse one engine across turns. Coding sessions can pass `memory: true`, which creates one scoped engine and reuses it; `session.close()` flushes. ## What the engine does `@caveman-ai/agent/memory` exposes the engine, workflow helper, and adapters. 1. `beginTurn()` returns finished recall from the prior turn and queues current retrieval. Embedding latency does not block the model. 2. `endTurn()` queues the assistant turn for session search and optional extraction. 3. Explicit `remember`, `search`, `searchSessions`, `forget`, and `link` cover active workflows. Native agents expose remember, memory search, and session search as framework tools (`cave_memory_*`). 4. `endSession()` flushes, extracts remaining turns, and runs optional consolidation. Default retrieval is a dependency-free sparse lexical vector plus lexical overlap. It is useful with no extra service. It is not semantic. Optional embedding adapters and bounded graph expansion (`relates_to`, `supersedes`, `contradicts`, `derived_from`, depth default 1, cap 3) exist behind `@caveman-ai/agent/memory`. An OpenAI-compatible embedding adapter uses `fetch`, takes an explicit API key, and never reads ambient key environment variables. No extra model call happens unless you supply a sidecar adapter for review, extraction, or consolidation. Consolidation is reversible: superseded records stay as inactive evidence. Sidecar output is never verified policy and never verified savings. ## Storage Default persistence is per-namespace JSON with atomic temp-write plus rename. Vectors store as normalized int8 plus base64, not floating-point arrays. Expired records become inactive reversible evidence. In-process writes are serialized per file. Across processes, last writer wins on concurrent update. Deployments that need multi-writer transactions supply a storage adapter (`read` plus serialized atomic `update`). Obvious private keys, provider tokens, credential assignments, and access keys are rejected before storage, indexing, or sidecar processing. For a host agent that is not this runtime: ```ts const memory = createMemoryWorkflow(engine, sessionId); const memoryContext = memory.beforeTurn(userText); const answer = await callAgent({ userText, memoryContext }); memory.afterTurn(answer); await memory.close(); ``` `beforeTurn` returns a string or `undefined` and does not wait. ## What it will not do Memory is not a saving. Recalled text is not a source of truth against current tools. Shared or project-scoped memory is not implemented. Semantic recall is not claimed for the default sparse vector. Closing the engine is your job if you created one. --- --- title: Compaction summary: "A fail-closed ladder: recover, summarise, clamp, then stop. Only inside a declared budget." canonical: https://docs.caveman.so/docs/agent-sdk/compaction license: MIT capability: agent-sdk updated: 2026-08-30T15:03:57+02:00 basis: inferred --- # Compaction > A fail-closed ladder: recover, summarise, clamp, then stop. Only inside a declared budget. Compaction runs only inside a declared token or USD budget. It is not a background summariser that fires because the context "looks large". The runtime applies a fail-closed ladder, then stops.
1 · Exact-recovery eviction. Drop what can be fetched back byte for byte.
↓ still over budget
2 · Typed summary. Replace eligible history with a cave.context-summary.v2 capsule.
↓ still over budget
3 · Output clamp. Bound what the model may still emit.
↓ still over budget
4 · Stop. The run ends. The receipt keeps every call that already happened.
Ember is the last exit. Nothing after stop invents a cheaper context.
```ts const budget: RunBudget = { maxTokens: 120_000, onExhausted: "compact", compaction: { keepRecentTokens: 8_000, summaryMaxTokens: 2_048, preserveFirstUserMessage: true, }, }; const result = await run(support, "Continue investigation.", { budget }); ``` `onExhausted: "stop"` skips the compact rungs and ends the run at the cap. ## Capsule rules The public API lives at `@caveman-ai/agent/compaction`. ```ts parseContextSummary, summarizationInstruction, validateContextSummaryTransition, } from "@caveman-ai/agent/compaction"; ``` A replacement is accepted only when all of these hold: - the schema parses - generation increments by exactly one - required user sources are covered, with matching source IDs and SHA-256 digests - every prior critical anchor survives byte-identically, unless a later current user source grounds a new critical anchor with an explicit `supersedes` edge - root user intent and a self-contained recent tail remain verbatim - the rewritten context is smaller - tool content cannot mint critical policy One paid summary is the default. Structural support for repeated compaction is tested. A higher default is not shipped without repeated live-model semantic evidence, and that evidence is not claimed here. ## Harness ```ts runContextCompactionHarness, type ContextCompactionSummarizer, } from "@caveman-ai/agent/compaction"; const summarize: ContextCompactionSummarizer = async (request) => { return explicitClient.complete({ messages: [ ...request.messages, { role: "user", content: request.instruction }, ], maxTokens: 2_048, }); }; const report = await runContextCompactionHarness(fixture, summarize, { repetitions: 20, }); if (!report.stable) throw new Error(report.failures.join("\n")); ``` The harness owns fixtures and validation. The adapter owns the model and transport, with explicit credentials. Test code does not inherit ambient secrets. The public harness measures transition validity, anchor recall, exact-recovery coverage, and compression ratio. Structural stability is tested. Semantic superiority is not claimed. Compaction events land on the run receipt with the rest of the spend. ## What it will not do Compaction is not a quality improvement. A smaller context that dropped a commitment is a failed transition, not a savings. Ratios from the harness are local, inferred, per run. They are not monthly figures and not `verified`. --- --- title: Programmatic tools summary: One caveman_code cell instead of a large JSON tool catalog. Nested calls still go through the runtime. canonical: https://docs.caveman.so/docs/agent-sdk/code license: MIT capability: agent-sdk updated: 2026-08-30T15:03:57+02:00 basis: inferred --- # Programmatic tools > One caveman_code cell instead of a large JSON tool catalog. Nested calls still go through the runtime. Programmatic mode replaces a large JSON tool catalog with one provider-visible tool named `caveman_code`. The model writes a bounded JavaScript cell. Nested tools are called through typed proxies. Every nested call still passes through the runtime's schema, effect policy, budget, breaker, timeout, abort, and receipt path. The cell does not bypass those checks.
caveman_code The only tool the provider sees.
↓ cell source, possibly still streaming
Nested dispatch. Same kernel as a normal tool call. Receipt records the cell and each nested call.
↓ early reads only
Speculative reads. Literal effect: "read" calls may start while the cell is still streaming. Writes, idempotent calls, external calls, and variable-dependent arguments never speculate.
Ember marks the only early path. Anything that can change the world waits for a complete cell.
## Coding session New applications import `@caveman-ai/coding-agent`. `@caveman-ai/agent/code` remains a compatibility export while that package is extracted. ```ts createCodingAgent, runCodingTurn, startCodingSession, } from "@caveman-ai/agent/code"; const codingAgent = createCodingAgent({ workspace: process.cwd(), toolMode: "programmatic", }); const session = await startCodingSession(codingAgent); await runCodingTurn(session, "Find failing tests and fix root cause."); ``` `toolMode: "direct"` exposes ordinary JSON tools instead. `speculativeToolCalls: false` keeps programmatic mode without early reads. Programmatic mode supports host agents only. The code-cell worker is not an isolation boundary. Use a normal `sandbox: "required"` agent when containment matters. Declaring `effect: "read"` in this mode means the work is safe to start and abandon: it may run even if the generated cell is later discarded. Unknown stream provenance executes fresh work rather than reusing stale speculation. ## Generic embedders ```ts createProgrammaticToolRuntime, PROGRAMMATIC_TOOL_NAME, } from "@caveman-ai/agent/programmatic-tools"; ``` That kernel does not require the coding-agent helpers. Nested tools still cannot skip the dispatcher. ## Interactive coding agent `@caveman-ai/coding-agent` is the interactive coding agent on this runtime, with host-sandbox `read_file`, `grep`, `bash`, `write_file`, and `edit_file` over one workspace. `read_tool_output` pages or literal-searches large captured results without repeating the original command. The CLI binary is `caveman-code`. With the local engine present, a session starts the local runtime and applies a default efficiency plan: one recoverable route per live-zone segment kind (`tool_result` through `caveman.engine.terminal.v1`, `history` through `caveman.engine.text.v1`), with `cave_retrieve` registered so the model can pull original bytes back. Only CCR-recoverable transforms are eligible. Two routes matching one runtime segment collapse into `dynamic_route_ambiguous` and that segment passes through untouched. When the runtime cannot be reached, the session degrades to observe-only and says so: a banner, `session.notices`, the prompt, and every turn's bill. A turn that already carries a plan refuses to degrade on its own (`cave_gateway_required_for_locked_plan`). The runtime is probed once per session, not once per turn. After every turn the session prints context tokens before and after transforms, tokens saved labelled `inferred (local estimate)`, provider usage with `usageBasis`, and spend in USD with `priceBasis`. Savings are never printed as currency. `/prove-recovery` round-trips a recorded tool output through the same compress/retrieve pair and compares SHA-256. Tool output is capped before compression (24 KB for `read_file` and `bash`, 16 KB for `grep`). Captured results are bounded to 8 MiB each and 16 MiB total in memory. Handles expire with the process. Live coding sessions are never lock-eligible. ## Long-running commands `@caveman-ai/agent/command-session` is the kernel behind hosted command tools. You pass an explicit `env`. Ambient `process.env` is never inherited. Session state is `running`, `exited`, `timed_out`, `killed`, or `unknown_after_restart`. Reads are cursor-based and can wait for bytes or a literal match. This is a credential boundary, not a sandbox: a `bash` session on a host agent is uncontained. ## What it will not do A code cell is not a sandbox. Host execution can run `bash`. Programmatic mode will not speculate a write. The coding session will not silently drop into observe-only on a planned turn, and it will not call a local token reduction a dollar saving. --- --- title: Connect summary: Paginated provider data through one stable tool. Fail closed. The daemon is not in this package. canonical: https://docs.caveman.so/docs/agent-sdk/connect license: MIT capability: agent-sdk updated: 2026-08-30T15:03:57+02:00 basis: inferred --- # Connect > Paginated provider data through one stable tool. Fail closed. The daemon is not in this package. Caveman Connect lets an agent read allowed provider data through one stable tool. The SDK owns configuration, bounded invocation, quality policy, and evidence. OAuth, credentials, encrypted storage, and sync execution stay in a local Connect daemon. The SDK does not bundle that daemon. ```ts const data = createConnect({ sources: [{ id: "work-github", provider: "github", collect: ["issues"], models: ["Issue"], }], }); id: "issue-triage", instructions: "Use connected source data. Never invent missing records.", model: auto(), tools: [data.tool], }); ``` Authorize once: ```bash caveman-agent connect github ``` Start configured collection from the app or a job: ```ts await data.collect("work-github"); ``` `collect()` triggers configured syncs and returns daemon acknowledgements. It does not pretend a queued sync is finished. Poll with `connected_data` `operation: "sync_status"`, then read records. Recurring schedules are not part of the current protocol; call `collect()` from whatever scheduler you already run. When a source omits `connectionId`, the runtime accepts exactly one active saved connection for that provider. Zero or several matches fail closed. Set the exact id to remove the ambiguity. ## Why the prefix stays small One stable `connected_data` schema enters the provider-visible prefix. Provider catalog, action schemas, sync schemas, and records stay out of the prompt until the agent asks. 1. `sources` returns a tiny allowlist. 2. `search_syncs` or `search_actions` loads bounded matching metadata. 3. `collect` triggers an allowlisted sync. 4. `records` reads exact paginated records. No retrieval model, embedding, semantic reranker, or automatic summariser runs by default. That avoids hidden model spend and keeps the provider cache prefix byte-stable inside one cache epoch. Token reduction is still not a savings proof. Extra model turns, expensive collection, stale data, or retries after incomplete context can cost more than the prefix you saved. ## Incomplete reads fail closed Paginated reads never silently summarise or skip records. Capped output returns `complete: false`, exact continuation when available, and `must_refuse: true`. The agent is supposed to refuse to answer from a partial page rather than invent the missing rows. Hosts that need Connect's native tool catalog without building a second MCP client can use `ConnectRuntime` from `@caveman-ai/agent/connect`. Default Connect timeout is 30,000 ms. ## What it will not do Connect will not copy credentials into the agent process. It will not hide a truncated page as a complete answer. It will not schedule recurring syncs by itself. It will not claim that a smaller prompt saved money. --- --- title: Evals and builds summary: Profile, search, freeze, holdout. Failed evals never produce a lock. canonical: https://docs.caveman.so/docs/agent-sdk/build license: MIT capability: agent-sdk updated: 2026-08-30T15:03:57+02:00 basis: inferred --- # Evals and builds > Profile, search, freeze, holdout. Failed evals never produce a lock. A Cave Build is an eval-gated freeze of one plan. The command searches on development cases, writes nothing if quality gates fail, then opens holdout cases that the search never saw. Failed evals never produce a lock.
Profile. Declared split: "profile" evals, or content-blind traces already under .caveman/traces/.
Search on development. Candidate plans run only on split: "development" cases.
↓ quality gates pass
Freeze. The winner is written. Holdout has not been opened yet.
Holdout. Untouched split: "holdout" cases. If they fail, there is no lock.
Ember is the first time holdout is visible. Search never trains on it.
```bash caveman-agent build caveman-agent check ``` In a generated project those are `npm run build` and `npm run check`. No Caveman account is required. `npm run build` runs every declared fixture inside the configured search budget. The SDK adds no approval prompt. ## Eval splits ```ts id: "profile-a", lineageId: "profile-family", split: "profile", input: "representative task", quality: [{ type: "exact_match", expected: "expected result" }], }); id: "development-a", lineageId: "development-family-a", split: "development", input: "different representative task", quality: [{ type: "exact_match", expected: "expected result" }], }); id: "holdout-a", lineageId: "holdout-family-a", split: "holdout", input: "unseen representative task", quality: [{ type: "exact_match", expected: "expected result" }], }); ``` `lineageId` is the stable task-family identifier. Graders the native compiler can lower without a network: `contains`, `not_contains`, `tool_called`, `exact_match`, `json_schema`. `exact_match` is trimmed and case-insensitive by default; `case_sensitive` and `remove_punctuation` opt into stricter variants. A v3 build needs explicit splits. Unsplit legacy suites still produce Pi Cave Build v2. If content-blind Caveman `RunResult`, OpenTelemetry, or OpenInference rows already exist under `.caveman/traces/`, the same `build` command imports them and skips profile-eval spend. Raw prompt and result span attributes are refused. Generic OTel and OpenInference spans stay unpriced; only strict Caveman evidence can be repriced from the pinned public catalog. ## What a successful v3 build writes - `.caveman/agent.lock.json`: Cave Build proof envelope - `.caveman/workload-profile.json`: content-blind profile and provenance - `.caveman/build-report.json`: search cost, holdout evidence, claims, and a local inferred point-estimate `break_even_tasks` when search cost is complete and holdout catalog delta is positive (`null` otherwise) No envelope is written when usage is missing, the model is unpriced, cache regresses, recovery fails, sandbox or privacy fails, quality drops, search is incomplete, or the cost ceiling is exceeded. `check` rejects drift before a model call. ## Who can change behaviour Exact native Pi (`tool-free-v1`) owns candidate generation and the locked runner. For agents with no declared tools it can select a priced model, lower reasoning effort, add reversible Context IR routes with derived recovery, and lower output budget. Any root tool, including a subagent, refuses that path before runner spend. Generic Pi, Vercel AI SDK, Eve, and Mastra v3 builds remain baseline-equivalent: they bind identity and evidence around an unchanged baseline plan. They do not currently construct changed model, reasoning, context, transform, recovery, retry, or output-budget behaviour from compiler output. Claude Cave Build compilation and registration refuse. Public `runClaudeAgent` stays unlocked. Callers cannot inject native candidates, runners, or target identity into `compileProfiledNativePi`. `compileProfiled` remains the generic caller-owned-runner API and emits baseline-equivalent v3 only. Hashes bind canonical bytes for integrity. They are not signatures, SBOM provenance, runtime attestation, or proof that registered bytes served traffic. ## Running a lock ```ts const build = parseAnyCaveBuildLock(JSON.parse( await readFile(".caveman/agent.lock.json", "utf8"), )); const result = await runLocked(support, "Can I get a refund?", build); ``` `runLocked()` accepts only Pi locks. Source freshness remains `caveman-agent check` at deployment. A Pi lock cannot authorise Claude or third-party execution. ## What it will not do A local build report is not a bill. Publish no savings percentage from it. `break_even_tasks` is a point estimate that stays `null` when evidence is incomplete; missing evidence is not a favourable zero. Verified savings remain `$0` until real traffic passes separate rollout and ledger gates that this package does not run. --- --- title: Adapters summary: "Exact-pinned framework lanes. Presence is not a grant of execution." canonical: https://docs.caveman.so/docs/agent-sdk/adapters license: MIT capability: agent-sdk updated: 2026-08-30T15:03:57+02:00 basis: inferred --- # Adapters > Exact-pinned framework lanes. Presence is not a grant of execution. Framework adapters are separate packages with exact upstream version pins. Install only the lane you run. A shared capability manifest records what the adapter actually implements. The registry is discovery, not a grant of execution and not a release certificate. Adapter presence never implies behavioural compiler support or live provider certification. Candidate conformance never mints either. ## First-party lanes These are the lanes the 0.2 source treats as execution harnesses: ```bash npm install @caveman-ai/agent @caveman-ai/adapter-vercel-ai-sdk ai@7.0.84 npm install @caveman-ai/agent @caveman-ai/adapter-mastra @mastra/core@1.63.2 npm install @caveman-ai/agent @caveman-ai/adapter-eve eve@0.29.2 ``` | Package | Upstream pin | Node | Locked native compile | |---|---|---|---| | `@caveman-ai/adapter-pi` | `@earendil-works/pi-agent-core@0.83.0` | 22.19+ | Yes, tool-free native Pi only | | `@caveman-ai/adapter-claude-agent-sdk` | `@anthropic-ai/claude-agent-sdk@0.3.220` | 22.19+ | No. Public lane is unlocked | | `@caveman-ai/adapter-vercel-ai-sdk` | `ai@7.0.84` | 22.19+ | Baseline-equivalent v3 | | `@caveman-ai/adapter-mastra` | `@mastra/core@1.63.2` | 22.19+ | Baseline-equivalent v3 | | `@caveman-ai/adapter-eve` | `eve@0.29.2` | 24+ | Baseline-equivalent v3, `reasoning: "none"` only | Eve 0.29.2 requires Node.js 24. The other lanes keep the package minimum of 22.19. Each adapter owns and tests its pin. `@caveman-ai/agent/adapters` and `@caveman-ai/agent/claude` remain compatibility exports. Vercel and Mastra host integration is not duplicated there. ## Claude lane ```ts const result = await runClaudeAgent(support, "Can I get a refund?", { entryPath: fileURLToPath(new URL("./agent.js", import.meta.url)), maxTurns: 8, maxBudgetUsd: 0.50, }); ``` The public lane is always unlocked. `claimBasis` is `"inferred"`. Verified savings are `$0`. It disables Claude built-in tools and settings, maps declared read-and-inline Caveman tools into one in-process MCP server, reuses the same immutable source snapshot and sandbox executor as Pi, and enforces declared model, reasoning, and output schema. Write, idempotent, and external tools, plus `auto` / page / compress / CCR results, reject before SDK or tool execution, so a side effect cannot land without recovery. Memory and framework subagents fail closed in this lane until equivalent semantics exist. The caller cannot inject Cave Plan or Cave Build identity. Default is 16 turns. Pinned Agent SDK reports aggregate output tokens but no authoritative thinking-token split, so `reasoningUsageBasis` is `"unavailable"`. `reasoningTokens: 0` is a non-evidence placeholder; do not read it as measured zero. Anthropic's SDK is not open source. The Agent SDK remains MIT; using the Claude lane still means reviewing Anthropic's terms. ## Other adapter packages in source The repository also contains observation and lifecycle adapters. They are not the five execution lanes above. | Package | Upstream pin | |---|---| | `@caveman-ai/adapter-langgraph` | `@langchain/langgraph@1.4.13` | | `@caveman-ai/adapter-openai-agents` | `@openai/agents@0.17.0` | | `@caveman-ai/adapter-cloudflare-agents` | `agents@0.22.0` | | `@caveman-ai/adapter-strands-agents` | `@strands-agents/sdk@1.15.0` | A passing candidate report is not compiler support and not live provider certification. ## What it will not do An adapter will not lower a baseline plan into a cheaper one unless its capability manifest proves that behaviour, and today only exact native Pi does. Mixing a Pi lock into Claude or a third-party runner is refused. Local adapter results stay `inferred`. --- --- title: "Cloud: What Cloud adds" summary: "The managed plane: fleet visibility, spend attribution, and verified numbers." canonical: https://docs.caveman.so/docs/cloud layer: cloud license: Commercial updated: 2026-08-26T03:57:26+02:00 basis: inferred --- # Cloud: What Cloud adds > The managed plane: fleet visibility, spend attribution, and verified numbers. Everything else on this site runs on your machine and reports `inferred`. Caveman Cloud is the layer across the network boundary, and it exists to answer one question the local tools structurally cannot. ## The question local tools cannot answer A local tool sees your bytes. It does not see your invoice. It can tell you a payload got 82 percent smaller. It cannot tell you what that was worth, because the price depends on the model, the provider, the cache state at the time, and your contract. It also cannot tell you whether the smaller payload produced the same answer, because it only saw one arm of the comparison. That gap is the whole reason the managed plane exists. It runs both arms, holds the price table, and compares against traffic that actually happened. Caveman Cloud is in private development and the waitlist is open. This page describes what it is for, not how it is built. When it opens, its own surfaces get documented here in full. ## What it adds - Fleet visibility: One view across every agent, developer and workflow, instead of one local database per laptop. - Spend attribution: Which person, project, agent or branch produced which part of the bill. - Eval gates: An optimisation is only applied to traffic that passed a grader on that traffic. - Verified numbers: The only place the word verified is allowed, because it is the only place with both arms of the comparison. ## The vocabulary does not change The same rules that bind the local tools bind the managed plane. `inferred` stays `inferred` when it arrives from a laptop. A number does not get promoted by crossing a network. The four money figures are kept separate and are never added together: measured spend, inferred headroom, verified savings, and observed outcome. There is no fifth bucket, and in particular there is no "realised savings" figure that quietly blends the others. ## Getting access The waitlist and contact details are on [caveman.so](https://caveman.so). Self-hosting the open layers needs no account and no waitlist. If all you want is compression on your own machine, you already have everything you need in the three layers below this one. --- --- title: Model router summary: "One model id: classify the task, apply a cost tier, route to measured evidence, fall back to your baseline." canonical: https://docs.caveman.so/docs/router layer: cloud license: Commercial updated: 2026-08-30T10:15:35+02:00 basis: inferred --- # Model router > One model id: classify the task, apply a cost tier, route to measured evidence, fall back to your baseline. Set `model` to `auto` and the gateway picks the model for each request: it classifies the task, applies a cost tier, and routes to the cheapest model with evidence it can hold quality there. Every decision comes back in response headers, and a request that routing cannot answer falls back to your baseline model instead of failing. - Model id: `auto` - Cost tiers: `low · medium · high · xhigh · max` - Tier override: `{"auto:"}` in the model field, or a `cost_tier` body field - When routing cannot run: The request goes to your project's baseline model. - Receipts: Response headers on every routed request. ## One model id Send your provider's native request through the gateway with `auto` as the model. Nothing else about the request changes. ```bash curl https://gateway.caveman.so/openai/v1/chat/completions \ -H "authorization: Bearer cave_live_…" \ -H "content-type: application/json" \ -d '{"model":"auto","messages":[{"role":"user","content":"…"}]}' ``` ```ts const completion = await client.chat.completions.create({ model: "auto", messages: [{ role: "user", content: "…" }], }); ``` `cave-auto` is an alias for `auto` and behaves identically. A request that names a real model is never rewritten. Routing only ever acts on requests that ask for it by sending `auto`. ## Cost tiers A tier is a cost band, not a quality promise. There are five, in ascending spend: `low`, `medium`, `high`, `xhigh`, `max`. By default you do not set one. The router reads each prompt's complexity and weights cost against quality accordingly, so a one-line question and a long refactor in the same session are spent on differently. To pin a band yourself, either suffix the model id or send a body field: ```json { "model": "auto:low", "messages": [] } ``` ```json { "model": "auto", "cost_tier": "high", "messages": [] } ``` If both are present, the model suffix wins. An unknown tier value is rejected with a client error rather than forwarded upstream as a garbage model name. A pinned tier narrows the public-matrix rankings. When the decision is driven by your project's own graded outcomes instead, those rankings are not price-binned: the pin does not narrow them, and the tier header stays absent so the receipt never claims it did. Pinning can also change what the evidence rests on. Bare `auto` reads every tier's cells and prefers direct evidence wherever it finds it; a pinned tier reads one cell, so a task measured directly in a pricier band may answer a cheap pin from the fallback table instead. That is what asking for the cheap band means. ## How a request is routed
model: auto · your request, unchanged otherwise
1 · Classify. Task type (one of about thirty, like agent:multi_step_planning ) and a complexity score that sets the tier, unless you pinned one.
2 · Look up evidence. Your project's own graded outcomes for this task type when there are enough of them, otherwise the public benchmark matrix.
3 · Apply your policy. Allowed models, denylist, provider health, and capacity ceilings only ever narrow the pool.
4 · Route with fallbacks. The top surviving model gets the request; the rest are ordered fallbacks.
↓ anything missing at any step
Baseline. The request goes to your configured baseline model. It never fails because routing was unavailable.
The pipeline narrows, then routes. Every exit lands on a model you approved.
The evidence in step 2 has an order. Once your project has graded outcomes for a task type, roughly twenty or more covering at least two candidate models, the router ranks by what worked on your traffic. Before that, it ranks by the public matrix: measured quality per dollar from published benchmark results, refreshed weekly. Your own evidence always outranks the public table, because it is the only table that measured your workload. ## Receipts Every routed response carries the decision: | Header | Meaning | |---|---| | `x-cave-router-version` | Which router answered. | | `x-cave-route-from` | The model string you sent, for example `auto:high`. | | `x-cave-route-to` | The model the request was sent to. | | `x-cave-route-tier` | Your pinned tier, present exactly when it narrowed the decision. Absent means no pin, or a pin the decision did not consult. | | `x-cave-route-decision-latency-us` | What deciding cost, in microseconds. | ## When routing does not run Routing is a per-project capability, and a project can mark it opt-in. On an opt-in project, a request activates it by sending `x-cave-optimize: route`. Lossless and pass-through requests are never routed, because those modes promise byte-identical forwarding. And a named model is a named model: the router does not touch it. ## What it will not do, and what its numbers mean The router does not claim savings. It records decisions; what a decision was worth is computed by the spend plane against traffic that actually happened, under the same vocabulary as everything else here: benchmark-derived rankings are measured on those benchmarks, your task-type rankings are measured on your graded traffic, and neither is `verified` by itself. Where the public matrix has no evidence for a task and tier, the router abstains and uses your baseline rather than guessing. Abstaining is a routing decision and shows up in the receipts like any other. Public-matrix rankings come from published benchmark results with declared cost bases, and cells without usable evidence stay empty. Your project tier is built only from your own graded outcomes. Neither number is a promise about your next request; the receipts exist so you can check the router against your own traffic. --- --- title: Licensing summary: Which surfaces are MIT, which are BSL 1.1, and what that means for you. canonical: https://docs.caveman.so/docs/licensing license: MIT updated: 2026-08-30T15:03:57+02:00 basis: inferred --- # Licensing > Which surfaces are MIT, which are BSL 1.1, and what that means for you. Caveman uses three licences, one per kind of surface. Which one applies depends on what a piece of code is, not on how much you use it. Nothing here has a usage threshold that changes your terms. [Diagram: the licence split across MIT, BSL 1.1 and Commercial surfaces.] ## MIT: adoption and interop Anything a developer installs to work with Caveman is MIT, and stays MIT inside your commercial product. The skill, the CLI, the TypeScript and Python SDKs, the Agent SDK and its initialiser, cavekit, the eval graders, the extension shell, the wire contracts, the provider catalog, and the JavaScript and Python clients for cavemem. ## BSL 1.1: the engine-linked runtime The compression engine and everything that links it: the local proxy, the cavemem core, the caveman-shrink core, the MCP server core, caveman-browse, and the shared platform libraries. BSL 1.1 is source available, not open source. The distinction is one paragraph long. ### What you may do Read the source, modify it, run it. Internal evaluation, local development, CI, integration work, and self-hosted use for your own first-party traffic, production included. ### What needs a commercial licence Offering Caveman or its functionality to third parties as a hosted, managed or embedded service. That is the OEM and platform boundary, and it is the only restriction. ### When it becomes Apache 2.0 Each BSL version converts on the earlier of 21 June 2030, or the fourth anniversary of that version's first public distribution under BSL. The conversion is per release, so an old version converts before a new one. ## Commercial: Caveman Cloud Multi-tenancy, verified accounting, governance, billing and the hosted dashboards. These run on our machines under an account and are not distributed. ## Mixed directories Two packages ship an MIT launcher around a BSL binary: the MCP server and caveman-shrink. In those directories the launcher terms are in `LICENSE.launcher`, and the Go source plus downloaded-binary terms are in `LICENSE` and `BINARY_LICENSE.md`. ## The default for new code One sentence, quoted because the wording is the rule: > A new module that imports, links, embeds, or ships as part of Engine-linked runtime is BSL 1.1 unless a > later ADR explicitly classifies it as MIT adoption surface. ## Contributing In MIT areas, inbound equals outbound. Your contribution is MIT. In BSL areas, contributions need a DCO sign-off and a relicense grant to Julius Brussee. That grant is what makes the eventual Apache 2.0 conversion possible, since a licence cannot be changed on code the project does not have the rights to change. ## Third-party code The pixel renderer is a Go port of pxpipe, which is MIT, and uses glyph atlases from Spleen 5x8 (BSD 2-Clause) and GNU Unifont (OFL 1.1, or GPLv2 with the font embedding exception). caveman-browse vendors chromedp, which is MIT. Notices ship next to the code in each case. ## Trademarks "Caveman" and the Caveman logos are trademarks of Julius Brussee. A code licence grants no trademark rights. Nominative use is fine when it is true: "Powered by Caveman" and "Optimized by Caveman" are both allowed if that is what your product does. The authoritative text is `LICENSING.md` and `TRADEMARKS.md` in the repository, alongside the licence files themselves. Where this page and those files disagree, those files win. --- --- title: Telemetry summary: What the CLI sends, what it never sends, and the three ways to turn it off. canonical: https://docs.caveman.so/docs/telemetry license: MIT capability: cli updated: 2026-08-26T03:57:26+02:00 basis: inferred --- # Telemetry > What the CLI sends, what it never sends, and the three ways to turn it off. The CLI sends anonymous usage counts. It is on by default, it is disclosed on the first run that would send anything, and there are three separate ways to turn it off. This page lists the exact contents. Nothing here is a summary of a longer policy: this is the payload. The local tools are free and stay free. Anonymous counts are how we see which commands people actually reach for, which ones fail, and whether anyone comes back after the first day. That is what tells us where to spend the work, and it is the reason the open layers can stay free instead of needing a licence to justify themselves. If you would rather not be counted, the switch is one line and we do not treat it as a loss. ## What is sent Two kinds of event, both counts. **Command events.** One per command you run. - Event name: Which of the known command names ran. - CLI version: The version of @caveman-ai/cli. - Platform: Operating system, architecture, and Node major version. - Duration: How long the command took. - Exit class: Whether it succeeded, and if not, which class of failure. **Engine session events.** One aggregate after a local wrapped session that was eligible for compression. - Counts: Request count and token counts. - Eligibility: How much of the session was eligible for compression. - Before and after: Measured totals and the amount cut. - Cache counters: Hit and miss counts. - Coverage flags: Which parts of the session could be measured at all. - Mode: observe or compress. There is also a one-time `first_run` event carrying scan aggregates. ## What is never sent This list is enforced server side by an allowlist, not by convention on the client. - Prompts, completions, or any model output - Your code, file paths, or URLs - Command arguments - Provider or model names - Request, session or account identifiers - Payload hashes - Any dollar figure The token figures in a session event are local `inferred` measurements. They cannot become a verified saving, because there is no account attached to them and no bill to compare against. ## The anonymous id The first interactive run of a real command writes a stable anonymous id to your config, so a returning user is not counted as a new one. It is not linked to an account, and there is nothing on our side that can turn it back into a person. Automation never mints one. CI and non-interactive runs default to off and do not persist a decision, because generating an anonymous identity for a build machine would corrupt the counts it is meant to inform. ## Turning it off Any one of these is enough. ```bash caveman telemetry off ``` ```bash ``` ```bash ``` `DO_NOT_TRACK` is the cross-tool convention and it is honoured here without needing to know that Caveman exists. Setting `CAVEMAN_TELEMETRY=1` turns it on for a single automated run without persisting a decision. ## If you already answered the old prompt Earlier versions asked a yes or no question on first run. Whichever way you answered, that answer is kept and is never rewritten by the current default. Opting out once means opting out permanently. ## The disclosure line On the first interactive run that would send anything, the CLI prints this and does not hide it behind a flag: ```text anonymous usage stats on — command + aggregate engine counts only, never prompts, code, or file paths · caveman telemetry off ``` ## What is deliberately outside this Agent SDK economic receipts contain a per-call spend tree. They are never sent to this endpoint. Moving them anywhere would need an authenticated, account-scoped transport with explicit consent, and that is a different decision from this one. Download counts come from the npm registry. They are not collected by the client. --- --- title: Eval graders summary: The grader set used to check that compression did not change an answer. canonical: https://docs.caveman.so/docs/evals license: MIT capability: evals updated: 2026-08-26T04:05:35+02:00 basis: inferred --- # Eval graders > The grader set used to check that compression did not change an answer. `@caveman/evals` is the public fail-closed grader package. Each grader takes one candidate and returns `{ passed, reason }`. Unknown types, invalid options, missing measurements, and unsafe execution paths fail. - Package: `@caveman/evals` - Runtime: Node.js with no production package dependencies. - Result: `{'{ passed: boolean, reason: string }'}` - Unknown grader: `passed: false` - Licence: MIT. ## Build from source No public registry install is documented for this package. Build the workspace package from source: ```bash git clone https://github.com/JuliusBrussee/caveman cd caveman pnpm install --frozen-lockfile pnpm --filter @caveman/evals build ``` ## Grade a value ```ts const result = await grade( { type: "exact_match", expected: "ready" }, "READY", ); if (!result.passed) { throw new Error(result.reason); } ``` Exact match is case-insensitive by default and compares structured values with sorted object keys. Optional `case_sensitive` and string-only `remove_punctuation` knobs change that behaviour. Invalid knob types fail. ## Deterministic graders Text and structure: - `exact_match`, `contains`, `not_contains` - `regex`, `not_regex`, `blocklist` - `json_schema`, `json_path_assertion` - `bleu_score`, `rouge_score`, `context_f1` - `localization_f1`, `no_pii` Tool and request evidence: - `tool_called`, `tool_not_called`, `tool_sequence` - `tool_argument_assertion` - `http_status`, `latency_threshold`, `cost_threshold`, `token_threshold` Threshold graders require a present, finite, non-negative measurement. An absent cost, token count, or latency cannot pass a ceiling by being treated as zero. The JSON Schema grader implements a documented subset: primitive type, enum, required keys, properties, and items. An unknown schema type fails rather than being ignored. ## Network graders `custom_webhook` posts `{ candidate }` and requires a JSON response with `passed`. Judge graders are `llm_judge`, `llm_score`, `llm_category`, `llm_pairwise`, and `llm_answer_match`. Network calls share a default ten-second deadline and refuse redirects. Private, loopback, link-local, and documentation IP ranges are blocked. A hostname requires an injected transport that pins the checked DNS result through connection setup; a normal fetch wrapper cannot claim that property. Model judges also fail when configured judge and subject model share a known family. A model grading its own family is a bias risk, not independent evidence. ## Regex limits Patterns are capped at 1,024 characters and candidates at 64 KiB. Backreferences and risky quantified groups are rejected. Accepted patterns run in a worker with a 250 ms execution budget. These limits choose a failed grade over a process hang. They do not truncate the candidate and pretend the partial result is complete. ## Localization evidence `localization_f1` accepts compact `path:start-end` lines or a structured file-to-ranges map. It scores both cited files and line overlap. Empty, unparseable, or zero-quality candidates fail even when a threshold is explicitly zero. This makes it suitable for checking repository explorer output without treating a correct filename and wrong lines as a full match. ## Run engine fixtures The engine bundles a local fixture harness around the grader contract: ```bash caveman tools evals run caveman tools evals run --fixtures ./fixtures ``` Caller-supplied fixture paths are confined to the supplied directory. A failing grader makes the command exit non-zero. Exact match proves normalized equality. A token threshold proves a reported token field stayed under a ceiling. Neither proves general answer quality. Choose graders that cover the failure modes of the task and keep holdout cases separate from development cases. --- --- title: Provider catalog summary: The model price table every cost figure is read from. canonical: https://docs.caveman.so/docs/provider-catalog license: MIT capability: provider-catalog updated: 2026-08-26T04:05:35+02:00 basis: inferred --- # Provider catalog > The model price table every cost figure is read from. The provider catalog is the public, source-backed table of model prices and operational facts. Cost calculations read a specific catalog version instead of scattering model prices through application code. - Package: `@caveman/provider-catalog` - Source: `catalog/current.yaml` - Snapshots: Immutable files named by price verification date. - Currency: Declared per row, currently USD rows. - Licence: MIT. ## What a row says One row identifies provider, model, region, currency, prices, capabilities, source links, and verification times. Typed fields may also describe lifecycle, aliases, historical price intervals, tokenizer support, count surface, and cache behaviour. Support facts use three states: | State | Meaning | | --- | --- | | `supported` | Reviewed source says the capability exists. | | `unsupported` | Reviewed source says it does not exist. | | `unknown` | No reviewed fact is available. | Unknown is not false and not permission to guess. ## Read generated data Build consumers use generated JSON rather than parsing YAML on a request path: ```text generated/catalog.json generated/manifest.json ``` The catalog carries a semantic digest, price-provenance digest, source digest, and content digest per entry. The manifest records snapshot digests and entry counts. These hashes detect drift; they do not prove that a provider served a request or charged a particular invoice. Use the generated manifest to read current counts. Do not copy a fixed model or provider count into dependent code, because rows change as providers publish and retire models. ## Unknown prices fail closed An unknown provider, model, or region combination is zero-priced with `unpriced:` provenance. Callers must not borrow a nearby model's price or assume two regions match. Zero in this case means no supported price entered the calculation. It does not mean the model is free. Any total containing an unpriced row is incomplete. Surface the `unpriced:` tag next to the total so a reader can distinguish missing price data from a real zero rate. ## Price provenance `verified_at` is an RFC3339 timestamp tied to price sources. Current rows may not be future-dated or older than 120 days. Every current row must match an immutable `catalog/YYYY-MM-DD.yaml` snapshot selected by its verification date. A price change adds a new dated file. It never rewrites an old snapshot. Sources use provider-owned HTTPS pricing or model documentation. Automated proposals carry a review marker and cannot pass default validation until a person confirms each changed row against its sources and removes the marker. Capability provenance has its own timestamp. Updating a non-price capability does not pretend that price was re-verified on that day. ## Validate a change From the catalog package directory: ```bash python3 -m pip install -r requirements-dev.txt python3 validate_catalog.py python3 -m unittest discover -s tests -p 'test_*.py' ``` From repository root: ```bash pnpm --filter @caveman/provider-catalog build pnpm --filter @caveman/provider-catalog lint pnpm --filter @caveman/provider-catalog test ``` Validation rejects duplicate YAML keys, duplicate provider/model/region identities, unsafe identifiers, invalid rates, stale provenance, broken aliases, unavailable replacements, unknown fields, and price changes without a matching snapshot. ## Lifecycle and routing Lifecycle distinguishes discovery, review, routing eligibility, and retirement. A reviewed price row is not automatically eligible for routing. Routing needs its own capability and evidence gates outside this data file. Aliases stay provider-local, may not collide with canonical model ids, may not form cycles, and may not resolve to a retired or missing model. ## What this catalog does not prove The table can support list-price accounting from observed usage. It does not turn inferred local token reductions into saved money, prove a contract rate, or compare against an invoice. Those claims need observed traffic and the right accounting authority.