Caveman
Compression enginePixel compression

Pixel compression

Pixel compression renders text into PNG image pages instead of sending it as text tokens. It is lossy, allowlist-gated to models with measured render legibility, and it only ever ships when a profitability gate says the image is actually cheaper than the text it replaces.

S4lossy · recoverable This is a lossy transform — it changes the model-visible bytes by turning text into image blocks. It is ported from pxpipe (MIT) into public/engine/pixel/, and it is never wired into the engine's Detect router: nothing routes to it automatically. It only runs where a caller explicitly asks for it — caveman wrap --pixel and caveman convert.

Why an image can be cheaper than text

Providers price image input by pixel area, not by character count. Anthropic's rate is 750 pixels per token (AnthropicPixelsPerToken in public/engine/pixel/gate.go). If a block of text renders small enough, the image can cost fewer tokens than the text tokens it replaces — but for most short or already-dense text, it doesn't. A profitability gate decides per request, per skill, per render: it estimates image tokens (rendered pixel area, with a 10% safety margin) against text tokens (via the offline counter) and only proceeds when the image side is smaller.

Plain rendering usually loses

A plain, non-reflowed render is frequently larger in tokens than the text it replaces — this is documented directly in the caveman convert implementation (public/cli/src/index.ts:914-917): a 5 KB skill body renders to 1,491 estimated image tokens against 1,178 text tokens plain — worse than the original. The same body in dense mode renders to 313 tokens — about a 73% reduction on that fixture. Dense packing (tighter columns, a fuller page, line breaks preserved as a visible sentinel glyph ) is the mode that wins. Every number here is inferred and specific to the fixtures it was measured on — actual results depend on the text.

The gate, not a flag

Nothing pixel-related is unconditional:

  • Model allowlistpixel.Allowed(model) (public/engine/pixel/applicability.go) checks the request's model against CAVE_PIXEL_MODELS, which defaults to claude-fable-5,gpt-5.6 — models with measured render legibility. Any other model is untouched.
  • Profitability gateEvalCompressionProfitability / IsCompressionProfitable (gate.go) compare estimated image tokens (plus any cache-warm burn cost) against estimated text tokens. The transform only proceeds when the image side is strictly smaller.
  • Fail-closed on parse or render errors — any error, or a render that drops characters it can't represent, aborts the transform and the original bytes pass through unchanged.

Where it runs

caveman wrap --pixel — live request/response streams

Passing --pixel to caveman wrap starts the local proxy in pixel mode instead of record or --compress. The standalone proxy (public/proxy/internal/gateway/pixel.go) applies the gate to each provider request — rendering only the last live user turn's oversized blocks, a lighter subset of the full pxpipe-parity transform (it does not do system-prompt relocation, tool-doc stubbing, system-reminder compression, or history collapse; those live in public/engine/pixel/transform_*.go and today run only under the caveman-engine pixel simulate diagnostic). It stores the original via the recovery store before publishing the transformed bytes, and reports the resulting token estimate as inferred. --pixel-models overrides the allowlist per invocation. --pixel cannot be combined with --compress or --toon — pick one wrap mode.

terminal
caveman wrap --pixel claude
caveman wrap --pixel --pixel-models claude-fable-5,gpt-5.6 claude

Recovery for this path is through caveman mem recover or the Caveman MCP tool — the standalone proxy always stores the pre-transform original before it forwards a pixel-compressed request.

caveman convert — agent skill files

caveman convert pixel-compresses installed agent skills (SKILL.md) in place. Frontmatter (YAML between --- fences) stays text, so agent discovery and triggering still work; the body gets rendered to PNG pages and replaced with a short stub telling the agent to read those images.

text
caveman convert [--agent <id>] [--project] [--skill <name>] [--dir <path>] [--revert] [--dry-run] [--force]

Preview with --dry-run

Renders every discovered skill and reports what would change, without touching any files.

terminal
caveman convert --dry-run

Convert

Runs the same render, then for each skill that clears the gate: writes the untouched original bytes to SKILL.orig.md, moves the rendered pages into the skill directory as SKILL.px1.png, SKILL.px2.png, …, and rewrites SKILL.md as frontmatter + a marker comment + the stub telling the agent to view the images.

terminal
caveman convert

Revert if you want the plain text back

Restores SKILL.orig.md over SKILL.md and deletes the SKILL.px*.png pages and SKILL.orig.md itself.

terminal
caveman convert --revert

Discovery walks every agent profile in the registry that declares a skills block — today that's Claude Code (~/.claude/skills, plus .claude/skills under --project) and Codex (~/.codex/skills). --agent <id> filters to one profile, --skill <name> filters to one skill directory, --dir <path> converts an arbitrary directory of skill subdirectories directly.

What makes a skill get skipped

Every skip prints its reason instead of silently doing nothing:

Skip reasonWhen
no frontmatter — discovery needs it as textSKILL.md has no --- frontmatter block
already convertedbody already starts with the pixel marker (use --force to re-convert from SKILL.orig.md)
stale SKILL.orig.md exists — use --force to overwritea previous conversion's original is present but the stub isn't
caveman-engine not found — kept as text; run \caveman setup``the engine binary can't be resolved
render dropped chars — kept as textthe renderer couldn't represent every character (fail-closed on lossiness)
not smaller — kept as textthe profitability gate says the image + stub cost is not less than the text cost

Recovery is byte-exact, not through CCR

caveman convert does not use the engine's CCR store. Recovery is a direct copy: the untouched original bytes are written to SKILL.orig.md before anything else changes, so --revert (or a --force re-run) restores the exact original even if a previous run failed partway through a page swap.

Reading the report

caveman convert prints, per skill, <name>: <text tokens> → <after tokens> est tokens (−NN% inferred), plus a total line when at least one skill converted. Everything is labeled inferred — it's the offline engine token counter's estimate on that one skill body, not a measured provider bill.

Illustrative (composited from the CLI's test fixtures — two separate skills, one converts and one is skipped as not smaller):

terminal
$ caveman convert
fat: 5000 520 est tokens (−90% inferred)
plain: skipped not smaller
total: 5000 520 est tokens (−90% inferred)