Skip to content
Cavemandocs
01Proxy/BSL-1.1

Pixel mode

Render text as an image the model reads for fewer tokens. Which models, and how to check it paid off.

Pixel mode renders text into a PNG from an embedded glyph atlas and sends the image where the text would have gone, because a model that reads images well charges by image patch rather than by token. It is a lossy transform like any other compressor, so the original request is stored under a recovery handle first, and it runs only for models on an allowlist.

Before wiring anything: check whether the model you use is on that list, because for every other model the transform is skipped and nothing changes.

Price a request without sending it#

pixel simulate reads a request body from disk, runs the real transform, and prints what it would do. It sniffs the shape: input is an OpenAI Responses request, contents is Gemini, messages is Anthropic Messages.

terminal
curl -O https://docs.caveman.so/examples/engine/request.json
caveman-engine pixel simulate request.json
json
{
"Compressed": true,
"Reason": "",
"OrigChars": 7869,
"ImageCount": 1,
"ImageBytes": 24550,
"ImagePixels": 272738,
"TextTokensEstimate": 4231,
"ImageTokensEstimate": 1013,
"StaticChars": 7869,
"DynamicChars": 0,
"DroppedChars": 0
}

A payload under the threshold is left alone and says so:

json
{ "Compressed": false, "Reason": "below_min_chars (0 < 2000)", "OrigChars": 0, "ImageCount": 0 }

That < is a <, escaped by the Go JSON encoder. Captured from a caveman-engine build dated 2026-08-24 on a request whose system prompt is 7,871 characters, trimmed to the fields this page names; caveman setup --install today pins bin-v1.1.7, so digits may move. --model overrides or fills the model in the body.

Which models#

The allowlist defaults to claude-fable-5 and gpt-5.6. A model matches when it equals an entry or starts with that entry plus a hyphen, after any […] variant tag is stripped, so dated and vendor-prefixed names resolve to their base.

CAVE_PIXEL_MODELS replaces the list with a comma separated one of your own. Setting it to 0, false, no, off or none empties the list, which turns the transform off everywhere.

The proxy checks the allowlist before it does anything else in pixel mode. Off the list, the request goes upstream as the client sent it. On the list, the transform runs, the original request body is stored under a CCR handle, and the response carries pixel-render in x-cave-optimization. A failed recovery write leaves the original bytes alone.

What gets rendered#

In pixel mode the proxy renders the live zone, the part of the conversation the provider has not cached. See Prompt caching for how that boundary is found. Three size thresholds decide what is worth an image: 2,000 characters for a text block, 6,000 for a tool result, and 6,000 for an injected reminder.

Past the threshold, a profitability gate prices the image against the text. It counts the patch tokens the rendered page would cost, applies a 1.10 safety margin, and compares that against the text at two characters per token for a cached slab. It also prices the cache burn on both sides, using Anthropic's 1.25 write rate and 0.10 read rate, so switching a warm text prefix to an image is charged for the rebuild it causes. The transform runs when the image side comes out lower.

Render a file yourself#

terminal
curl -O https://docs.caveman.so/examples/engine/SKILL.md
caveman-engine pixel render SKILL.md
json
{"width":1565,"height":202,"charsRendered":12352,"droppedChars":0,"estTokens":493,"density":"balanced"}
{"summary":true,"pages":1,"textEstTokens":3235,"imageEstTokens":493,"density":"balanced"}

Pages are written next to the input as SKILL.md.px1.png, SKILL.md.px2.png and so on, one JSON line per page and a summary line last. The two numbers that matter are on the summary: textEstTokens is the offline o200k_base count of the input, imageEstTokens is the patch estimate with its margin. The render paid off when the second is smaller than the first, and droppedChars is 0.

--density picks the cell geometry. conservative is the standard mono grid at 28,080 characters per page, balanced is a denser zebra grid and the default, max stacks two red and blue text layers into one image. The same file at conservative geometry:

json
{"width":1568,"height":328,"charsRendered":12360,"droppedChars":0,"estTokens":740,"density":"conservative"}
{"summary":true,"pages":1,"textEstTokens":3235,"imageEstTokens":740,"density":"conservative"}

This command draws exactly the level you ask for, whatever model you plan to show the image to. max stacks two layers once a page has enough text to fill one, and the report then carries "layers":2; this 12 KB file fits in a single layer, so max and balanced draw the same page.

The model profile decides the geometry on the proxy path instead. claude-fable-5, claude-mythos-5, claude-sonnet-5 and claude-opus from 4.8 onward get the higher resolution canvas at whichever level is asked for. gpt-5.6 is recognised too and gets the level on the standard canvas. Any other model, and any model the table does not know, resolves to conservative standard geometry with one layer at every level.

An unrecognised density value is a usage error rather than a silent downgrade:

terminal
caveman-engine pixel render --density ultra SKILL.md
text
--density must be one of conservative|balanced|max (got "ultra")

Pixel-compressing a skill#

caveman convert runs the same renderer over installed agent skills, replacing each SKILL.md body with image pages and a short stub that tells the agent to read them. --dry-run reports the arithmetic and writes nothing:

terminal
mkdir -p skills/release-checks
curl -o skills/release-checks/SKILL.md https://docs.caveman.so/examples/engine/SKILL.md
caveman convert --dir ./skills --dry-run
text
release-checks: 3217 → 613 est tokens (−81% inferred) (dry-run)
total: 3217 → 613 est tokens (−81% inferred)

Drop --dry-run and three files are left in the skill directory:

terminal
caveman convert --dir ./skills
text
release-checks: 3217 → 613 est tokens (−81% inferred)
total: 3217 → 613 est tokens (−81% inferred)
terminal
ls skills/release-checks
text
SKILL.md
SKILL.orig.md
SKILL.px1.png

SKILL.md keeps its frontmatter, because skill discovery reads it as text, and its body becomes the stub:

markdown
<!-- caveman-pixel v1 sha256:b824175f… -->
This skill's full instructions are pixel-compressed into image pages to save
tokens. Read (view) these image files NOW, in order, and follow their contents
as this skill's complete instructions:

1. /path/to/skills/release-checks/SKILL.px1.png

Plain-text original: SKILL.orig.md in this directory
(restore with `caveman convert --revert`).

SKILL.orig.md is written before anything else is touched, so a crash part way through still leaves a complete original to restore from.

Both figures on that line come from a render of the body alone, with the frontmatter stripped, and with --dense on, which is the packing that makes an image beat the text it replaces. That render reports 3,217 text tokens and 493 image tokens for this file. The after figure adds a flat 120 estimated tokens for the stub, giving 613, and that sum is what the not-smaller check compares against 3,217.

Undo it:

terminal
caveman convert --dir ./skills --revert
text
release-checks: reverted

The revert restores SKILL.md from SKILL.orig.md, deletes the page images and removes the backup. Run without --dir, caveman convert walks the skill directories of every agent profile that has one, and --agent <id> narrows it to one.

Conversion is skipped with the reason printed. These are the reasons, verbatim:

text
no frontmatter — discovery needs it as text
render dropped chars — kept as text
not smaller — kept as text
already converted
already converted but SKILL.orig.md is missing
stale SKILL.orig.md exists — use --force to overwrite
caveman-engine not found — kept as text; run `caveman setup`
unknown skill surface format
render failed (exit N)

The stale one is the case that surprises people. A leftover SKILL.orig.md beside a SKILL.md that carries no pixel marker means an earlier revert was interrupted, so the tool leaves the only plain-text copy it can see alone:

terminal
caveman convert --dir ./skills
text
release-checks: skipped — stale SKILL.orig.md exists — use --force to overwrite

The repository's own measurement on the caveman skill is 1,069 estimated tokens to 415, a 61 percent cut. That figure is inferred, and Numbers and limits says what the word covers.

Limits#

Reads it back
Only the allowlisted models. Everything else gets the text.
Recovery
The original request is stored under a CCR handle before any image is sent.
Blocked by size
Under 2,000 characters for a text block, or 6,000 for a tool result, nothing is rendered.
Blocked by price
The gate reports not_profitable and the text goes through.
WASM
The pixel package is never compiled into the browser build, because the atlases are about 4 MB.

Pixel rendering is never chosen by content detection. It is a request-level mode, set by mode: pixel in caveman.yaml, and the engine's content compressors are a separate path. See Modes.

The renderer is a port of pxpipe, MIT licensed, with PNG encoding moved to the Go standard library and token estimates moved to the engine's offline counter.