Use when you need to reduce LLM API token usage or costs — prompt compression, model tiering, caching, context trimming, tool call minimization, output length control, and automated context compression via headroom-ai.
Resources
3Install
npx skillscat add protick-bjit2019/token-cost-optimization Install via the SkillsCat registry.
Token Cost Optimization
Overview
Every token costs money and latency. This skill encodes battle-tested techniques for cutting LLM API spend without degrading output quality. Covers both design-time strategies (how you structure prompts and tools) and runtime strategies (caching, batching, model routing).
Applies to any provider (OpenAI, Anthropic, Mistral, Gemini, etc.) and also to local inference where you pay in GPU time instead of dollars.
When to Use
- You are building an LLM-powered feature and want to keep costs low from day one.
- An existing app's API bill is unexpectedly high and you're diagnosing why.
- You want to set a per-request token budget and enforce it programmatically.
- You are selecting which model tier to use for a given task.
- You want to add semantic caching or prompt caching to an existing pipeline.
Don't use for:
- One-off interactive chat (costs are negligible; don't over-engineer).
- Tasks where output quality is the only constraint (compression may hurt).
Strategy 0 — headroom-ai: Drop-In Context Compression (60–95% savings)
headroom is an open-source library (17k+ ⭐, Apache 2.0) that automatically compresses tool outputs, logs, RAG chunks, files, and conversation history before they reach the LLM — with no accuracy loss.
Benchmarked savings on real agent workloads:
| Workload | Before | After | Savings |
|---|---|---|---|
| Code search (100 results) | 17,765 | 1,408 | 92% |
| SRE incident debugging | 65,694 | 5,118 | 92% |
| GitHub issue triage | 54,174 | 14,761 | 73% |
| Codebase exploration | 78,502 | 41,254 | 47% |
Accuracy on benchmarks: unchanged. (GSM8K ±0.000, TruthfulQA +0.030, SQuAD v2 97%, BFCL 97% with 19–32% compression.)
Installation
pip install "headroom-ai[all]" # Python — everything included
npm install headroom-ai # TypeScript / Node (requires proxy)
docker pull ghcr.io/chopratejas/headroom:latestGranular extras: [proxy], [mcp], [ml], [code], [memory], [image], [langchain], [agno], [evals]. Requires Python 3.10+.
Three usage modes
Mode 1 — Library (minimal code change):
from headroom import SmartCrusher, SmartCrusherConfig
crusher = SmartCrusher(SmartCrusherConfig(
max_items_after_crush=20, # keep top-N rows by importance
first_fraction=0.30, # always keep first 30% of remaining budget
last_fraction=0.15, # always keep last 15%
factor_out_constants=True, # hoist repeated fields to save tokens
))
# ✅ Use .crush() — returns CrushResult
result = crusher.crush(tool_output_str, query="error root cause")
compressed = result.compressed # str — drop-in replacement for tool output
# CrushResult attributes (confirmed v0.23.0):
# .compressed — str, the compressed content
# .original — str, the original content unchanged
# .strategy — str, e.g. "lossless:table(200->len=31363)"
# .was_modified — bool, False if content was too small to compress
# ⚠️ DO NOT use crush_array_json() — it returns a metadata dict that
# wraps the content in extra keys, increasing token count by 50%+.
# Always use crush() for tool outputs.
# Inject compressed result into messages
msgs_after = [
*msgs_before[:-1], # keep all but the last tool-result message
{"role": "user", "content": [
{"type": "tool_result", "tool_use_id": tool_use_id,
"content": compressed}
]},
]
response = client.messages.create(model="claude-sonnet-4-5", messages=msgs_after, ...)
# Check what happened
print(f"Strategy : {result.strategy}")
print(f"Modified : {result.was_modified}")Mode 2 — Proxy (zero code changes):
# Start local proxy on port 8787
headroom proxy --port 8787
# Point any OpenAI-compatible client at it
OPENAI_BASE_URL=http://localhost:8787/v1 your-app
ANTHROPIC_BASE_URL=http://localhost:8787 claude
# Stats endpoint
curl http://localhost:8787/stats
# {"requests_total": 42, "tokens_saved_total": 125000, ...}Mode 3 — Agent wrap (one command):
headroom wrap claude # wraps Claude Code with --memory and --code-graph
headroom wrap codex # shares memory with Claude
headroom wrap cursor # prints config, paste once
headroom wrap aider # starts proxy + launches
headroom wrap copilot # starts proxy + launchesWhat headroom compresses (and what it doesn't touch)
| Content type | Compressor | Typical savings |
|---|---|---|
| JSON arrays of dicts | SmartCrusher .crush() (lossless table reformat + statistical sampling) |
42–95% |
| JSON arrays of strings | Dedup + adaptive sampling | 60–90% |
| Build/test logs | Pattern clustering | 85–94% |
| Source code | CodeCompressor (AST-aware) | 40–70% |
| Plain text | Kompress-base (HuggingFace model) | 30–50% |
| HTML | Article extraction (trafilatura) | ~95% |
| Short content (<200 tokens) | Not compressed (overhead > savings) | — |
| User messages | Never compressed (intent preserved) | — |
| System prompt content | Preserved; only dynamic parts relocated | — |
⚠️ crush_array_json() pitfall: This method returns a metadata dict with an
itemskey containing the original JSON as a string. It does NOT reduce tokens — it increases them by 50%+. Always usecrush()for tool outputs.
How the pipeline works internally
Three-stage compression pipeline:
CacheAligner — extracts dynamic content (dates, UUIDs, tokens) from system prompt prefix and moves it to the tail. Stabilizes prefixes so provider KV caches (Anthropic
cache_control, OpenAI prefix cache) actually hit. Sub-millisecond overhead.ContentRouter → SmartCrusher / CodeCompressor / Kompress-base — detects content type, selects best compressor. SmartCrusher applies field-level statistical analysis (variance, uniqueness, Kneedle algorithm on bigram coverage). Errors and anomalies are always kept regardless of budget. Overhead: 1–50ms.
Context Manager — ensures final message array fits within the model's context window.
- Rolling Window (default): drops oldest messages first, preserving system prompt and recent turns. Tool call + response pairs dropped atomically.
- Intelligent Context (advanced): scores messages on 6 dimensions (recency, semantic similarity, TOIN importance, error indicators, forward references, token density).
CCR (Compress-Cache-Retrieve) — reversible compression: originals are never deleted; they're stored in a local SQLite cache. If the LLM needs full data it calls ccr_retrieve("<hash>") to get it back. Headroom is the only major compressor that is fully reversible.
SDK integrations
# Wrap existing client — Anthropic or OpenAI
from headroom.integrations import withHeadroom
from anthropic import Anthropic
client = withHeadroom(Anthropic()) # all calls auto-compressed
# LangChain
from headroom.integrations.langchain import HeadroomChatModel
llm = HeadroomChatModel(your_llm)
# Agno
from headroom.integrations.agno import HeadroomAgnoModel
model = HeadroomAgnoModel(your_model)
# ASGI middleware
from headroom.integrations.asgi import CompressionMiddleware
app.add_middleware(CompressionMiddleware)
# MCP server (for any MCP client)
headroom mcp install
# Exposes: headroom_compress, headroom_retrieve, headroom_statsProvider cache optimization (built-in)
After compression, headroom automatically applies provider-specific cache hints:
| Provider | Mechanism | Cache savings |
|---|---|---|
| Anthropic | cache_control blocks on stable prefix |
Up to 90% on repeated tokens |
| OpenAI | Prefix alignment for automatic caching | Up to 50% on repeated tokens |
CachedContent API |
Up to 75% on repeated tokens |
Cross-agent shared memory
from headroom.memory import SharedContext
ctx = SharedContext()
ctx.put("key", value, agent="claude") # store from any agent
ctx.get("key") # retrieve from any agent (Claude, Codex, Gemini)headroom learn mines failed agent sessions and writes corrections to CLAUDE.md / AGENTS.md / GEMINI.md automatically.
headroom vs. alternatives
| Tool | Scope | Local | Reversible |
|---|---|---|---|
| headroom | All context — tools, RAG, logs, files, history | ✅ | ✅ |
| RTK | CLI command outputs only | ✅ | ❌ |
| lean-ctx | CLI commands, MCP tools, editor rules | ✅ | ❌ |
| Compresr / Token Co. | Text sent to hosted API | ❌ | ❌ |
| OpenAI Compaction | Conversation history only | ❌ | ❌ |
headroom ships RTK internally and can also use lean-ctx as the context tool (
HEADROOM_CONTEXT_TOOL=lean-ctx).
Performance check
headroom perf # benchmark compression on your actual workloadsStrategy 1 — Measure First
Never optimize blind. Instrument token usage before changing anything.
# openai SDK: response.usage is always present
response = client.chat.completions.create(...)
print(response.usage) # CompletionUsage(prompt_tokens=..., completion_tokens=..., total_tokens=...)
# anthropic SDK
response = client.messages.create(...)
print(response.usage) # Usage(input_tokens=..., output_tokens=...)Key metrics to track per request:
| Metric | Formula |
|---|---|
| Prompt tokens | tokens used by the input |
| Completion tokens | tokens in the output |
| Cost | prompt_tokens × price_in + completion_tokens × price_out |
| Prompt-to-completion ratio | prompt / completion — high ratios mean you're paying for context |
Use tiktoken (OpenAI) or anthropic.count_tokens() to estimate costs before sending:
import tiktoken
enc = tiktoken.encoding_for_model("gpt-4o")
n = len(enc.encode(your_prompt_text))
print(f"{n} tokens ≈ ${n / 1_000_000 * 5:.4f} at $5/1M input")Strategy 2 — Model Tiering
Route tasks to the cheapest model that can do the job. Typical tiers:
| Tier | Examples | Use for |
|---|---|---|
| Micro / fast | GPT-4o-mini, Claude Haiku, Gemini Flash | Classification, extraction, summarization, short Q&A |
| Standard | GPT-4o, Claude Sonnet, Gemini Pro | Multi-step reasoning, coding, analysis |
| Frontier | o3, Claude Opus, Gemini Ultra | Research, long-horizon agents, highest accuracy |
Rule of thumb: Start with the cheapest tier. Only escalate if evals fail.
# Simple routing: route by estimated complexity
def pick_model(prompt: str) -> str:
n_tokens = len(enc.encode(prompt))
if n_tokens < 500 and "summarize" in prompt.lower():
return "gpt-4o-mini" # 15x cheaper than gpt-4o
return "gpt-4o"Strategy 3 — Prompt Compression
3a. Remove Boilerplate
Every word in the system prompt costs tokens on every call. Ruthlessly remove:
- "You are a helpful AI assistant" — LLMs know they're LLMs.
- Long preambles explaining what the AI should generally do.
- Repeated instructions that are already default behavior.
3b. Use Terse Formatting
BEFORE (47 tokens):
"Please carefully analyze the following customer feedback and provide
a detailed response identifying the main issues."
AFTER (12 tokens):
"Analyze feedback. List issues."3c. Few-Shot → Zero-Shot
Each example in a few-shot prompt costs its full token count. Test if zero-shot works first. If accuracy suffers, add one example, not five.
3d. Compress Reference Documents
When inserting long documents into context, summarize or extract only the relevant sections:
# Instead of stuffing 10k-token PDF into context:
# 1. Chunk it
# 2. Embed chunks
# 3. Retrieve top-k by similarity
# 4. Inject only top-k (typically 3-5 chunks × ~300 tokens = ~1k tokens total)Strategy 4 — Context Window Management
Sliding Window (for long conversations)
Keep only recent messages when history grows too large:
MAX_HISTORY_TOKENS = 4000
def trim_history(messages: list[dict], enc) -> list[dict]:
# Always keep system prompt (index 0)
system = messages[:1]
history = messages[1:]
# Count from most recent until budget exhausted
kept, budget = [], MAX_HISTORY_TOKENS
for msg in reversed(history):
cost = len(enc.encode(msg["content"]))
if cost > budget:
break
kept.append(msg)
budget -= cost
return system + list(reversed(kept))Summarize Instead of Truncate
When truncating loses important context, summarize old history with a cheap model first:
def summarize_old_history(old_msgs: list[dict]) -> str:
response = client.chat.completions.create(
model="gpt-4o-mini", # cheap model for summarization
messages=[
{"role": "user", "content": f"Summarize this conversation in ≤100 words:\n{old_msgs}"}
],
max_tokens=150,
)
return response.choices[0].message.contentStrategy 5 — Prompt Caching
Anthropic and OpenAI offer explicit prompt caching — identical prefix tokens are cached at a 90% discount on repeat calls.
Anthropic cache_control
response = anthropic.messages.create(
model="claude-sonnet-4-5",
max_tokens=1024,
system=[
{
"type": "text",
"text": LARGE_SYSTEM_PROMPT, # long static prefix
"cache_control": {"type": "ephemeral"} # mark for caching
}
],
messages=[{"role": "user", "content": user_question}]
)
# cache_read_input_tokens shows how many were served from cache
print(response.usage.cache_read_input_tokens)Rules for Anthropic prompt caching:
- Minimum cacheable prefix: 1024 tokens (Sonnet/Haiku) or 2048 tokens (Opus).
- Cache TTL: 5 minutes (refreshed on each cache hit).
- Cost: cache write = 1.25× normal; cache read = 0.1× normal.
- Structure your prompt so the STATIC part comes first (system prompt, documents) and the DYNAMIC part (user input) comes last.
OpenAI Automatic Caching
OpenAI caches automatically — no code changes needed. The first 1024+ token prefix of any request is cached. You get a discount automatically if the same prefix is reused within ~1 hour.
Check with: response.usage.prompt_tokens_details.cached_tokens
Strategy 6 — Semantic Caching
Cache at the semantic level — if a new query is very similar to a past query, return the cached answer.
# Minimal semantic cache with sentence-transformers + numpy
# pip install sentence-transformers numpy
from sentence_transformers import SentenceTransformer
import numpy as np
model = SentenceTransformer("all-MiniLM-L6-v2") # 80MB, fast
cache: list[tuple[np.ndarray, str]] = [] # (embedding, answer)
SIMILARITY_THRESHOLD = 0.92
def cached_query(question: str) -> str | None:
q_emb = model.encode(question)
for emb, answer in cache:
similarity = np.dot(q_emb, emb) / (np.linalg.norm(q_emb) * np.linalg.norm(emb))
if similarity > SIMILARITY_THRESHOLD:
return answer
return None
def store_in_cache(question: str, answer: str):
cache.append((model.encode(question), answer))For production: use Redis with vector search or Qdrant/Weaviate as the cache backend.
Strategy 7 — Output Length Control
Completion tokens are often more expensive per-token than input tokens. Control output length explicitly:
# Hard limit
response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
max_tokens=256, # hard cutoff — set per task
)
# Soft limit via prompt instruction
system = "Respond in ≤3 sentences. Be direct."
# Structured output reduces verbose prose
# Ask for JSON instead of paragraphs — usually 2-5× fewer tokens
system = "Respond as JSON: {\"issue\": str, \"severity\": 1-5, \"fix\": str}"Use JSON/structured output mode for extraction tasks — it forces concise, parseable answers.
Strategy 8 — Tool Call Minimization
Each tool call round-trip adds tokens (tool schema + tool result injected back into context). Minimize by:
- Batch tool calls — request multiple results in one call instead of chaining.
- Filter tool schemas — only include tools relevant to the current task. Every tool definition in the schema costs ~100-300 tokens per call.
- Inline small lookups — if a tool just returns a short string, compute it before the LLM call and inject it directly into the prompt.
- Return minimal tool results — truncate or summarize large tool outputs before injecting them:
def truncate_tool_result(result: str, max_chars: int = 2000) -> str:
if len(result) <= max_chars:
return result
return result[:max_chars] + f"\n[...truncated {len(result) - max_chars} chars]"Strategy 9 — Batching
If you're making many independent LLM calls, batch them:
# OpenAI Batch API — 50% discount, 24h turnaround
# Good for: eval pipelines, bulk classification, offline enrichment
import json
requests = [
{"custom_id": f"req-{i}", "method": "POST", "url": "/v1/chat/completions",
"body": {"model": "gpt-4o-mini", "messages": [{"role": "user", "content": q}]}}
for i, q in enumerate(questions)
]
# Write JSONL file, upload, then call client.batches.create(...)For Anthropic, use the Message Batches API (client.beta.message_batches.create(...)).
Strategy 10 — Token Budget Enforcement
Fail fast rather than blowing the budget silently:
TOKEN_BUDGET = 8_000 # per request hard limit
def safe_complete(messages: list[dict]) -> str:
total = sum(len(enc.encode(m["content"])) for m in messages)
if total > TOKEN_BUDGET:
raise ValueError(f"Prompt exceeds budget: {total} > {TOKEN_BUDGET} tokens")
response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
max_tokens=min(1024, TOKEN_BUDGET - total),
)
return response.choices[0].message.contentPublishing a Skill to GitHub for Peer Sharing
When sharing a skill via hermes skills install <url>, the SKILL.md must have complete frontmatter or peers get a warning:
"The file lacks proper Hermes SKILL.md frontmatter."
Required frontmatter fields for clean peer install:
---
name: skill-name
description: Use when ... # ≤1024 chars, starts with "Use when"
version: 1.0.0
author: your-github-username
license: MIT
platforms: [linux, macos, windows] # ← REQUIRED — missing = warning
metadata:
hermes:
tags: [tag1, tag2]
homepage: https://github.com/you/skill-name
related_skills: [other-skill]
---Publish workflow:
# 1. Create repo with the skill files
mkdir my-skill && cd my-skill
cp ~/.hermes/skills/<category>/<name>/SKILL.md .
cp -r references/ templates/ . # include support files
# 2. Write README.md with install command, smoke test results, file list
# 3. Init, create remote, push
git init && git add .
git commit -m "feat: initial release"
gh repo create my-skill --public --source . --push
# 4. Peers install with:
hermes skills install https://raw.githubusercontent.com/<user>/<repo>/master/SKILL.mdValidate frontmatter before pushing:
import yaml, re, pathlib
content = pathlib.Path("SKILL.md").read_text(encoding="utf-8")
assert content.startswith("---")
m = re.search(r'\n---\s*\n', content[3:])
fm = yaml.safe_load(content[3:m.start()+3])
assert "name" in fm and "description" in fm and "platforms" in fm
assert len(fm["description"]) <= 1024
print("✅ Frontmatter valid")Smoke Test Output Format
When reporting smoke test results, use this format (emoji headers, "Prompt tokens" label, system counted as a message):
==============================================================
🧪 TOKEN COST OPTIMIZATION — SMOKE TEST
Model : <model-name>
Pricing: $X/1M in · $Y/1M out · $Z/1M cache-read
Scenario: <description>
==============================================================
──────────────────────────────────────────────────────────────
📥 BEFORE (raw, no optimization)
──────────────────────────────────────────────────────────────
Messages : N (incl. system)
Prompt tokens : XX,XXX
↳ tool output : XX,XXX (XX% of total)
↳ system prompt : XX
Est. cost / call : $X.XXXXX
──────────────────────────────────────────────────────────────
📤 AFTER (headroom SmartCrusher — Strategy 0)
──────────────────────────────────────────────────────────────
Messages : N (incl. system)
Prompt tokens : X,XXX
↳ tool output : X,XXX (XX% of total)
Est. cost / call : $X.XXXXX
Compression time : X.XX ms
Rows kept : XX/XXX (XX% removed) | errors always kept: XX
==============================================================
📊 DELTA
==============================================================
Tokens saved : XX,XXX (XX.X%)
Cost saved / call : $X.XXXXX
Cost saved / 1,000 calls: $XX.XX
Cost saved /10,000 calls: $XXX.XXKey formatting rules:
- Use model's actual pricing — not GPT-4o pricing if user is on Claude
- Count system prompt as message 1 →
Messages: 5 (incl. system) - Label is
Prompt tokensnotTotal tokens - Rows format:
kept/originalnotoriginal → kept
Quick-Reference Checklist
Run this mental checklist before deploying any LLM feature:
- headroom installed? For any app with tool calls, RAG, or log output,
pip install "headroom-ai[all]"and wrap the client or start the proxy — typically yields 60–95% savings with zero accuracy loss. - Measured baseline token usage per request type (
response.usage,headroom perf) - Chose the cheapest model tier that passes evals
- System prompt is ≤ 500 tokens (unless prompt caching covers the excess)
- Long static context is marked for prompt caching (or headroom CacheAligner handles it)
- History trimming / summarization in place for multi-turn chat (or headroom Rolling Window)
-
max_tokensset per task (not left at API default) - Tool schemas pruned to only relevant tools
- Extraction tasks use JSON/structured output
- Repeated queries go through semantic cache
- Bulk offline tasks use the Batch API
Smoke Test Report Format
When producing a token cost smoke test report, always use this exact format:
- Header emoji:
🧪for test header,📥BEFORE,📤AFTER,📊DELTA,🗄️caching strategy,🏷️model tiering,✅checklist - Label:
Prompt tokens(NOTTotal tokens) - Messages count: include system as message 1 →
Messages: N (incl. system) - Compression rows:
Rows kept: N/M (X% removed) | errors always kept: N - Model/pricing: ALWAYS use the user's actual active model and Anthropic pricing — never default to GPT-4o or OpenAI pricing
- Indent: 3 spaces inside each section block
Example BEFORE block:
📥 BEFORE (raw, no optimization)
──────────────────────────────────────────────────────────────
Messages : 5 (incl. system)
Prompt tokens : 24,186
↳ tool output : 23,665 (98% of total)
↳ system prompt : 24
Est. cost / call : $0.07256 (@ $3.0/1M in)Strategy 11 — Measure Actual Spend from the Hermes Session DB
When running through Hermes, every session's token counts are written to a local
SQLite DB. Query it directly to see exactly how much you spent today, last 7
days, or last 30 days — no API key or dashboard required.
DB location:
- Windows:
%LOCALAPPDATA%\hermes\state.db - Linux/Mac:
~/.local/share/hermes/state.db
Key columns in sessions: input_tokens, output_tokens,cache_read_tokens, cache_write_tokens, model, started_at.
⚠️ Copilot billing caveat: when billing_provider = 'copilot',estimated_cost_usd is always 0.0. Compute cost yourself from token counts
using Anthropic API-equivalent pricing — this shows what it would cost on the
direct API, not the Copilot subscription fee.
Cost formula:
cost = (
input_tokens / 1e6 * price_in +
output_tokens / 1e6 * price_out +
cache_write_tokens / 1e6 * price_cw +
cache_read_tokens / 1e6 * price_cr
)Cache-read tokens can reach millions per session (every KV-cache hit is counted)
but are cheap ($0.30/1M). Always include them — omitting understates cost by ~30%.
See templates/spend_report.py for a full CLI tool with --today / --7d / --30d
flags and a per-model daily bar-chart breakdown.
See references/hermes-session-db.md for the full DB schema and query patterns.
Support Files
| File | Purpose |
|---|---|
templates/smoke_test_token_opt.py |
Runnable BEFORE/AFTER smoke test (tiktoken only, no headroom install needed). Uses emoji headers and Anthropic claude-sonnet-4-5 pricing by default. Copy and run: python smoke_test_token_opt.py |
templates/cost_count.py |
CLI token counter — counts tokens and estimates cost for any text/JSON conversation. Accepts inline text, file, or stdin. Flags: -f FILE, -m MODEL, --system TEXT, --list-models. Requires tiktoken. |
templates/spend_report.py |
CLI spend reporter — queries Hermes state.db for actual session token counts and computes $ cost. Flags: --today, --7d, --30d, --all, --from/--to. No extra deps beyond stdlib + sqlite3. |
references/headroom-ai.md |
headroom-ai architecture, benchmarks, confirmed Windows uv-venv install (no MSVC), SDK integrations |
references/hermes-session-db.md |
Hermes state.db schema reference — cost-relevant columns, copilot billing caveat, cost computation pattern, model-name variations, cache-read token note. |
Common Pitfalls
Not trying headroom first. For any app that has tool calls, RAG results, or log output in context, headroom is almost always the highest-leverage first move. Install it and wrap your client before hand-tuning anything else.
headroom-ai on Windows — use
uv venv, NOT barepip install. Barepip install headroom-ai/uv pip install headroom-ai --systemtries to compile Rust crates and fails without MSVC. But creating an isolated venv first pulls pre-built wheels and succeeds without any build tools:uv venv headroom-env --python 3.11 source headroom-env/Scripts/activate # bash/git-bash # OR: headroom-env\Scripts\activate.bat (cmd) uv pip install "headroom-ai[all]" python -c "import headroom; print(headroom.__version__)" # ✅ 0.23.0Confirmed working on Windows 10 with Python 3.11.15, uv, no MSVC installed.
Full details:references/headroom-ai.md.crush_array_json()inflates tokens — usecrush()instead.crush_array_json()returns adictwithitems,ccr_hash,dropped_summary,strategy_info,compacted, andcompaction_kindkeys. Serialising this dict adds ~50% overhead. Confirmed with real data: 17,062 raw tokens → 25,589 aftercrush_array_json()vs → 10,688 aftercrush(). Always call.crush(text, query="...")and use.compressedfrom the result.Compressing user messages. headroom never touches user messages for a reason — user intent must be preserved exactly. Don't manually truncate or rewrite user input.
Optimizing before measuring. You often don't know where tokens actually go until you log
response.usageor runheadroom perf. Profile first.Caching dynamic content. Prompt caching only helps if the prefix is truly static. If user ID / session info is in the system prompt prefix, the cache never hits. headroom's CacheAligner automatically moves dynamic content to the tail to fix this.
Setting
max_tokenstoo low. If the model hits the limit mid-sentence the output is truncated and often useless. Set it generously for tasks that need complete responses.Ignoring completion-token cost. For some providers, completion tokens cost 3-5× more per token than input tokens. A 100-token completion can cost more than a 400-token prompt. Control output length aggressively.
Stripping context that's actually needed. Over-aggressive trimming causes the model to lose track of key facts, leading to retries that cost more than you saved. headroom's CCR (Compress-Cache-Retrieve) avoids this by storing originals and letting the LLM retrieve them on demand.
Forgetting tool schema tokens. Each tool in the
tools=array is tokenized into the prompt. 10 tools × 200 tokens = 2,000 tokens added to every request silently.Not testing after compression. Always re-run evals after changing prompts. headroom's
python -m headroom.evals suiteand benchmark table can validate accuracy preservation automatically.
Verification Checklist
- headroom installed and wrapping LLM client (or proxy running) for any tool/RAG/log-heavy app
-
headroom perfrun to measure actual savings on real workloads - Baseline cost/token logged before optimization
- Model tier justified by task complexity
- Prompt caching applied to static prefix (≥1024 tokens for Anthropic; headroom CacheAligner handles alignment automatically)
-
max_tokensexplicitly set per task - Tool schemas filtered to only relevant tools
- History trimming strategy in place for chat pipelines (headroom Rolling Window or manual)
- Semantic cache hit rate measured after deployment
- Evals re-run after prompt compression to confirm no quality regression (
python -m headroom.evals suite --tier 1)