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.
curl -O https://docs.caveman.so/examples/engine/request.json
caveman-engine pixel simulate request.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:
{ "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#
curl -O https://docs.caveman.so/examples/engine/SKILL.md
caveman-engine pixel render SKILL.md{"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:
{"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:
caveman-engine pixel render --density ultra SKILL.md--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:
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-runrelease-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:
caveman convert --dir ./skillsrelease-checks: 3217 → 613 est tokens (−81% inferred)
total: 3217 → 613 est tokens (−81% inferred)ls skills/release-checksSKILL.md
SKILL.orig.md
SKILL.px1.pngSKILL.md keeps its frontmatter, because skill discovery reads it as text, and its body becomes the stub:
<!-- 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:
caveman convert --dir ./skills --revertrelease-checks: revertedThe 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:
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:
caveman convert --dir ./skillsrelease-checks: skipped — stale SKILL.orig.md exists — use --force to overwriteThe 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_profitableand 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.