mirror of
https://github.com/headroomlabs-ai/headroom.git
synced 2026-08-10 14:27:00 -04:00
## Summary
Full CLI audit + documentation accuracy pass. All 5 commits on this
branch:
### CLI Hardening (4 commits)
- **Clean errors instead of tracebacks**: corrupt manifests, missing
Docker, malformed JSONL, bad `--profile`, invalid env-var values all now
raise `click.ClickException` with helpful messages
- **Range validation**: ~25 numeric flags across 10 files now use
`click.IntRange`/`FloatRange` — `--port 0`, `--hours -1`, `--limit 0`
etc. produce clean usage errors instead of silent wrong behavior
- **Flag combination warnings**: conflicting combos (`--no-rate-limit` +
`--rpm`, `--no-optimize` + `--target-ratio`, `--telemetry` +
`--no-telemetry`) emit yellow warnings on stderr
- **`memory --db-path` default fixed**: was resolving to
`headroom_memory.db` (wrong bare file); now uses project store
`./.headroom/memory.db` if present, else `~/.headroom/memory.db`
- **`memory list --search` + filters**: `--scope`/`--session`/`--since`
were silently ignored when `--search` was also set; now filters are
applied to search results
- **`learn --verbosity --apply` now works**: the output shaper is off by
default (`HEADROOM_OUTPUT_SHAPER`); `--apply` now hot-enables it via
`POST /admin/runtime-env` on a running proxy, or prints explicit `export
HEADROOM_OUTPUT_SHAPER=1` instructions when no proxy is running
- **`perf --hours` overflow**: `1e9` hours no longer raises
`OverflowError`; treated as "all data"
- **`evals memory --categories` invalid input**: `abc,1,2` now raises
`BadParameter` instead of a raw `ValueError` traceback
### Documentation (1 commit, 20 files)
Corrected factual errors found by 3 parallel audit agents across root
docs, wiki, and the published Fumadocs site:
**Critical (caused runtime errors or wrong behavior if followed):**
- `simulation.mdx`: `plan.transforms_applied` -> `plan.transforms`;
`plan.savings_percent` -> computed from available fields (both raised
`AttributeError`)
- `shared-context.mdx`: `import { SharedContext } from "headroom"` ->
`"headroom-ai"` (5x `ImportError`)
- `claude-code-azure-foundry.mdx`: `pip install headroom` -> `pip
install headroom-ai`
- `api-reference.mdx` + `configuration.mdx`: `from headroom import
GoogleProvider` -> `from headroom.providers import GoogleProvider`
- `ccr.mdx`: CCR TTL default 300s -> 1800s (30 min)
**Fabricated flags removed:**
- `wiki/proxy.md` + `wiki/cli.md`: `--no-intelligent-context`,
`--no-intelligent-scoring`, `--no-compress-first` (none exist); replaced
with real CCR flags
- `wiki/configuration.md`: `--no-ccr-responses`, `--no-ccr-expansion`
(none exist); replaced with real flags
- `wiki/troubleshooting.md`, `wiki/metrics.md`,
`docs/troubleshooting.mdx`: `headroom proxy --log-level debug` (flag
doesn't exist)
**Stale content corrected:**
- `llms.txt`: telemetry stated as enabled-by-default (it's opt-in); wrap
list had 5 tools (now 11)
- `README.md`: compatibility matrix added 5 missing `wrap` targets;
`unwrap`, `doctor`, `init`/`install`, savings-analytics now mentioned
- `SECURITY.md`: supported version table showed 0.2.x (current: 0.27.x)
- `wiki/learn.md`: 5 missing flags added; verbosity shaper-off behavior
documented
- `wiki/quickstart.md`: "Configuration Reference" linked to `api.md`
(wrong) -> `configuration.md`
- `CacheAlignerConfig.enabled` default corrected: `True` -> `False`
- `opencode.mdx`: `--port` default wrong ("random") -> 8787; `openai`
backend removed
- `CONTRIBUTING.md`: broken Markdown table cell fixed
- `docs/meta.json`: `claude-code-azure-foundry` added to nav (was
unreachable orphan page)
- `configuration.mdx`: SDK modes vs proxy `--mode` now clearly
distinguished
## Test plan
- [x] `python -m pytest tests/ -x -q` — 857 passed, 0 failures
- [x] 41-combination CLI smoke test (all flag combos across 8 commands)
— 0 tracebacks
- [x] `ruff check` on all modified Python files — clean
- [x] Docs changes are removals/corrections of fabricated or stale
content; no new claims introduced
8 KiB
8 KiB
Quickstart Guide
Get Headroom running in 5 minutes with these copy-paste examples.
Installation
Python:
# Core only (minimal dependencies)
pip install headroom-ai
# With proxy server
pip install "headroom-ai[proxy]"
# Everything
pip install "headroom-ai[all]"
TypeScript / Node.js:
npm install headroom-ai
Docker-native:
curl -fsSL https://raw.githubusercontent.com/chopratejas/headroom/main/scripts/install.sh | bash
See Docker-native install if you want Docker to provide the Headroom runtime while your agent CLIs stay on the host.
Persistent background runtime:
headroom install apply --preset persistent-service --providers auto
See Persistent Installs if you want Headroom to stay up in the background and be reused by wrap.
Option 1: Proxy Server (Zero Code Changes)
The fastest way to start saving tokens. Works with any OpenAI-compatible client.
Step 1: Start the Proxy
headroom proxy --port 8787
Step 2: Verify It's Running
curl http://localhost:8787/health
# Expected: {"status":"healthy","ready":true,"config":{"backend":"anthropic",...},...}
Step 3: Point Your Client
# Claude Code
ANTHROPIC_BASE_URL=http://localhost:8787 claude
# GitHub Copilot CLI (default Anthropic-style proxy route)
headroom wrap copilot -- --model claude-sonnet-4-20250514
# Cursor / Continue / any OpenAI client
OPENAI_BASE_URL=http://localhost:8787/v1 your-app
# Python
export OPENAI_BASE_URL=http://localhost:8787/v1
python your_script.py
Step 4: Check Savings
curl http://localhost:8787/stats
# {"requests_total": 42, "tokens_saved_total": 125000, ...}
Option 2: Python SDK
Wrap your existing client for fine-grained control.
Basic Example
from headroom import HeadroomClient, OpenAIProvider
from openai import OpenAI
# Create wrapped client
client = HeadroomClient(
original_client=OpenAI(),
provider=OpenAIProvider(),
default_mode="optimize",
)
# Use exactly like OpenAI client
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Hello!"},
],
)
print(response.choices[0].message.content)
# Check what happened
stats = client.get_stats()
print(f"Tokens saved: {stats['session']['tokens_saved_total']}")
With Tool Outputs (Where Savings Happen)
from headroom import HeadroomClient, OpenAIProvider
from openai import OpenAI
import json
client = HeadroomClient(
original_client=OpenAI(),
provider=OpenAIProvider(),
default_mode="optimize",
)
# Simulate a conversation with large tool outputs
messages = [
{"role": "system", "content": "You analyze search results."},
{"role": "user", "content": "Search for Python tutorials."},
{
"role": "assistant",
"content": None,
"tool_calls": [{
"id": "call_1",
"type": "function",
"function": {"name": "search", "arguments": '{"q": "python"}'},
}],
},
{
"role": "tool",
"tool_call_id": "call_1",
# This is where Headroom shines - compressing large outputs
"content": json.dumps({
"results": [{"title": f"Result {i}", "score": 100-i} for i in range(500)]
}),
},
{"role": "user", "content": "What are the top 3 results?"},
]
# Headroom compresses the 500 results to ~20, keeping the most relevant
response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
)
print(response.choices[0].message.content)
Simulate Before Sending
Preview optimizations without making an API call:
# See what would happen without calling the API
plan = client.chat.completions.simulate(
model="gpt-4o",
messages=messages,
)
print(f"Tokens before: {plan.tokens_before}")
print(f"Tokens after: {plan.tokens_after}")
print(f"Would save: {plan.tokens_saved} tokens ({plan.tokens_saved/plan.tokens_before*100:.0f}%)")
print(f"Transforms: {plan.transforms}")
print(f"Estimated savings: {plan.estimated_savings}")
Option 3: Anthropic SDK
from headroom import HeadroomClient, AnthropicProvider
from anthropic import Anthropic
client = HeadroomClient(
original_client=Anthropic(),
provider=AnthropicProvider(),
default_mode="optimize",
)
# Use Anthropic-style API
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
messages=[
{"role": "user", "content": "Hello, Claude!"},
],
)
print(response.content[0].text)
Verify It's Working
Method 1: Enable Logging
import logging
logging.basicConfig(level=logging.INFO)
# Now you'll see:
# INFO:headroom.transforms.pipeline:Pipeline complete: 45000 -> 4500 tokens (saved 40500, 90.0% reduction)
# INFO:headroom.transforms.smart_crusher:SmartCrusher: keeping 15 of 500 items
Method 2: Check Session Stats
stats = client.get_stats()
print(stats)
# {
# "session": {"requests_total": 10, "tokens_saved_total": 5000, ...},
# "config": {"mode": "optimize", "provider": "openai", ...},
# "transforms": {"smart_crusher_enabled": True, ...}
# }
Method 3: Validate Setup
result = client.validate_setup()
if not result["valid"]:
print("Setup issues:", result)
else:
print("Setup OK!")
print(f"Provider: {result['provider']['name']}")
print(f"Storage: {result['storage']['url']}")
Common Configuration
Adjust Compression
from headroom import HeadroomClient, OpenAIProvider, HeadroomConfig
config = HeadroomConfig()
# Keep more items after compression (default: 15)
config.smart_crusher.max_items_after_crush = 30
# Only compress if tool output has > 500 tokens (default: 200)
config.smart_crusher.min_tokens_to_crush = 500
client = HeadroomClient(
original_client=OpenAI(),
provider=OpenAIProvider(),
config=config, # Pass custom config
default_mode="optimize",
)
Skip Compression for Specific Tools
response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
headroom_tool_profiles={
"database_query": {"skip_compression": True}, # Never compress
"search": {"max_items": 50}, # Keep more items
},
)
Audit Mode (Observe Only)
# Start in audit mode - see what WOULD be optimized
client = HeadroomClient(
original_client=OpenAI(),
provider=OpenAIProvider(),
default_mode="audit", # No modifications, just logging
)
# Override per-request
response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
headroom_mode="optimize", # Enable for this request only
)
What Gets Optimized?
| Content Type | What Headroom Does | Typical Savings |
|---|---|---|
| Tool outputs with lists | Keeps errors, anomalies, high-score items | 70-90% |
| Repeated search results | Deduplicates and samples | 60-80% |
| Long conversations | Drops old turns, keeps recent | 40-60% |
| System prompts with dates | Stabilizes for cache hits | Cache savings |
Next Steps
- Configuration Reference - All configuration options
- Transform Reference - How each transform works
- Troubleshooting - Common issues and solutions
- Examples - More complete examples
Quick Troubleshooting
"No token savings"
# 1. Check mode
stats = client.get_stats()
print(stats["config"]["mode"]) # Should be "optimize"
# 2. Enable logging to see what's happening
import logging
logging.basicConfig(level=logging.DEBUG)
"High latency"
# Use BM25 instead of embeddings for faster relevance scoring
config.smart_crusher.relevance.tier = "bm25"
"Compression too aggressive"
# Keep more items
config.smart_crusher.max_items_after_crush = 50
See Troubleshooting Guide for more solutions.