Put Headroom compression in front of Hermes Agent persistently and globally, using OpenRouter as the model provider. Every tool output, log, file and RAG chunk gets compressed before it reaches the model — typically 60–95% fewer tokens for the same answers.
This is not a per-session launcher. It wires Headroom in once, so the
gateway (Discord, Telegram, etc.), cron jobs, and the plain hermes CLI
all route through it automatically:
Hermes gateway / Discord / all profiles / crons / kanban / subagents / CLI
│ (every process reads OPENROUTER_BASE_URL from ~/.hermes/.env)
▼
Headroom proxy ──► OpenRouter ──► the model
(systemd --user service, always on; compresses + caches)
It works because Hermes reads OPENROUTER_BASE_URL from ~/.hermes/.env and
applies it to every process that uses the openrouter provider. Setting that one
variable to the local proxy routes the gateway, all profiles, crons, kanban workers,
delegated subagents, and the CLI through Headroom at once — no config.yaml or
per-profile edits, and it survives profile/model changes.
- Hermes Agent installed (
hermeson your PATH), normally run as a gateway service (hermes gateway install/start). - uv installed:
curl -LsSf https://astral.sh/uv/install.sh | sh - An OpenRouter API key — https://openrouter.ai/keys.
Headroom is installed for you by setup.sh (or uv tool install "headroom-ai[proxy,mcp]").
The default model is DeepSeek V4 Flash (deepseek/deepseek-v4-flash) — fast,
~1M context, very cheap. Change MODEL in .env for anything else.
cd hermes-headroom
./setup.sh # checks tools, installs Headroom, creates .env (key + model)
./install.sh # runs the proxy as a service + points Hermes at it, for good
hermes gateway restart # one-time: gateway picks up the new configThat's it. Nothing to launch per session. Verify it's working:
systemctl --user status headroom-proxy.service # proxy is active
headroom perf --hours 24 # tokens / cost saved (last 24 hours) No flag for last 7 days.Either run LITELLM_LOCAL_MODEL_COST_MAP=true headroom perf, or set it once so plain headroom perf works:
fish:
set -Ux LITELLM_LOCAL_MODEL_COST_MAP truebash/zsh:
echo 'export LITELLM_LOCAL_MODEL_COST_MAP=true' >> ~/.bashrcTo restart the proxy, run:
systemctl --user restart headroom-proxy.service
To change the model or port later, edit .env and re-run ./install.sh.
To remove everything, ./uninstall.sh.
- A user service at
~/.config/systemd/user/headroom-proxy.servicerunningheadroom proxy --backend openrouteron127.0.0.1:<PORT>, enabled so it starts on login and auto-restarts on failure. Your.envis the service'sEnvironmentFile, so the OpenRouter key lives there and nowhere else. (This.envdeliberately does not setOPENROUTER_BASE_URL, so the proxy itself still targets real OpenRouter — no loop.) ~/.hermes/.env— a small, clearly-marked, removable block that setsOPENROUTER_BASE_URL=http://127.0.0.1:<PORT>/v1. This is the global lever: every Hermes process using theopenrouterprovider now flows through the proxy. Noconfig.yamlor per-profile edits; survives profile/model changes. Any pre-existingOPENROUTER_BASE_URLis preserved and restored on uninstall.- (Optional,
ENABLE_MCP=1) the default~/.hermes/config.yamlgets aheadroomentry undermcp_serversfor the retrieve tool (timestamped backup first). MCP servers have no global env lever, so this is added to the default config only — it is the one thing that can't be made profile-wide without per-profile edits, which we deliberately avoid. Compression works fully without it. - A boot-ordering drop-in at
~/.config/systemd/user/hermes-gateway.service.d/10-headroom.confso the gateway starts after the proxy. This is a separate file — it does not modify the unit Hermes generates, and surviveshermes gatewayregenerating it.
uninstall.sh reverses all of the above (and cleans up any legacy wiring from
earlier versions).
OPENROUTER_BASE_URL only wraps traffic Hermes routes through OpenRouter. When
Hermes shells out to Codex or Claude Code as subagents, those CLIs call
Anthropic/OpenAI on their own endpoints, so they bypass this proxy. To compress
those too, run a second Headroom proxy with --backend anthropic (or openai) and
point ANTHROPIC_BASE_URL / OPENAI_BASE_URL at it, or use headroom wrap codex /
headroom wrap claude. That's a separate, additive setup.
Put any OpenRouter slug in .env as MODEL=, exactly as shown on
https://openrouter.ai/models. Set CONTEXT_LENGTH to that model's real window
(Hermes needs ≥ 64000 for agent use; DeepSeek V4 Flash uses 1000000). Re-run
./install.sh after changing either.
| Setting | What it is |
|---|---|
OPENROUTER_API_KEY |
Your OpenRouter key. Read by the proxy service only. |
MODEL |
OpenRouter model slug to run. |
PORT |
Local port for the proxy (default 8787). |
CONTEXT_LENGTH |
Context window in tokens Hermes should assume. |
ENABLE_MCP |
1 wires Headroom's retrieve/stats MCP tools into Hermes; 0 skips. |
HEADROOM_EXTRA_ARGS |
Proxy flags; includes --memory --memory-storage=user by default. |
With --memory in HEADROOM_EXTRA_ARGS (default), the proxy automatically extracts
important facts from conversations and injects relevant compressed memories into future
requests. This is Headroom's proxy-level inter-agent context sharing:
- Agent A discovers something in conversation → the proxy remembers it
- Agent B starts a new session → the proxy injects relevant compressed memories (~80% smaller) as context automatically
- No code changes to Hermes needed — it works at the HTTP transport layer
This is the transport-level equivalent of Headroom's SharedContext Python API
(from headroom import SharedContext). The library API is for in-process agent code;
the --memory flag does the same thing for agents that talk through the proxy.
| Mode | Flag | Behavior |
|---|---|---|
project (default) |
--memory-storage=project |
One SQLite DB per project/workspace — no cross-project bleed. |
user (recommended) |
--memory-storage=user |
Single DB for all traffic — all Hermes conversations share memories. |
global |
--memory-storage=global |
Legacy single shared DB. |
We default to user because all Hermes traffic flows through one proxy and you want
context shared across gateway, crons, subagents, and the CLI.
headroom memory list # List stored memories
headroom memory stats # Show memory statistics
headroom memory show <id> # Full details of a memory
headroom memory prune --older-than 30d # Prune old memories
headroom memory export --output file.json # Export for backupAdd --learn to HEADROOM_EXTRA_ARGS for deeper inter-agent intelligence:
the proxy watches traffic for error→recovery patterns, environment facts, and user
preferences, then saves them to agent-native memory files (MEMORY.md, .cursor/rules,
AGENTS.md). Implies --memory. More powerful but more opinionated — start with
just --memory and graduate to --learn after you're comfortable.
With ENABLE_MCP=1 (default), the agent gets headroom_retrieve (pull back the
exact original bytes behind a compressed block, by hash) and headroom_stats,
pointed at the same always-on proxy. Inside Hermes they appear as
mcp_headroom_headroom_retrieve / mcp_headroom_headroom_stats. Proxy-backed
originals are cached ~5 minutes, so retrieval is for in-flight detail, not recall
from hours ago.
Because the whole agent now routes through the proxy, if the proxy is down, all Hermes model calls fail (gateway included). Two things mitigate this:
-
The proxy runs with
Restart=always, so systemd brings it straight back if it crashes, and the gateway is ordered to start after it at boot. -
For belt-and-suspenders, add a direct-to-OpenRouter fallback so the bot keeps working (uncompressed) if the proxy is ever unreachable. This needs your key in
~/.hermes/.env(OPENROUTER_API_KEY=...), then add to~/.hermes/config.yaml:fallback_providers: - provider: openrouter model: deepseek/deepseek-v4-flash
If linger isn't enabled for your user, the proxy (and gateway) won't survive a full
logout/reboot. The Hermes gateway docs cover this — loginctl enable-linger $USER.
Check if set with
loginctl show-user $USER | grep Linger
ls /var/lib/systemd/linger/If your gateway already survives reboots, the proxy will too.
- Proxy won't start / bot stops responding.
systemctl --user status headroom-proxy.serviceandjournalctl --user -u headroom-proxy.service -e. Most common cause: a badOPENROUTER_API_KEYin.env. install.shcan't reachsystemctl --user(headless/SSH): runexport XDG_RUNTIME_DIR=/run/user/$(id -u)then re-run.- "model not found". The
MODELslug in.envis the thing to fix; use the exact slug from openrouter.ai/models, then re-run./install.sh. - Want the original config back? Each
install.shleaves a timestamped backup:~/.hermes/config.yaml.bak-pre-headroom-*. headroom perfshows "list price unknown" / no $ savings. The model is newer than LiteLLM's price database (true today fordeepseek-v4-*).install.shfixes this by registering live OpenRouter prices into LiteLLM's local cost map (lib/register_pricing.py) and settingLITELLM_LOCAL_MODEL_COST_MAP=true. Re-run./install.sh(orpython3 lib/register_pricing.py --slugs <your/model>) afteruv tool upgrade headroom-ai, or for any other new model. TheperfCLI must see the flag too, so run it asLITELLM_LOCAL_MODEL_COST_MAP=true headroom perf— or addexport LITELLM_LOCAL_MODEL_COST_MAP=trueto your shell rc so plainheadroom perfworks. (fish:set -Ux LITELLM_LOCAL_MODEL_COST_MAP true.)- Model context limits (important). Headroom's OpenAI-compatible provider
resolves context windows by name pattern and falls back to 128K for
unrecognized models — which includes
deepseek/deepseek-v4-*andminimax/minimax-m3(really ~1M). Left unfixed, the proxy trims/compresses long conversations against a 128K ceiling that doesn't exist.register_pricing.py --from-logs(run byinstall.sh) writes the real windows into~/.headroom/models.json, so the proxy only trims near the true limit. Trade-off: on very long conversations you'll see lower headline token savings (it stops over-trimming) but higher fidelity — per-item bloat compression still runs regardless. If you'd rather trim aggressively for cost/focus, set a deliberate lower number by hand in~/.headroom/models.json(e.g. 256000) — now it's an intentional choice, not an accident. - What model does Headroom run? Essentially none. Compression is
structural/statistical (SmartCrusher JSON, AST code-aware, BM25 relevance,
CacheAligner, RollingWindow). The only ML model is
answerdotai/ModernBERT-base(~150M params) used lazily by the optionalkompresstransform, on CPU via ONNX by default. There is no 8–12B LLM requirement; LLMLingua (which would use a model) isn't in the 0.23.x build. Your GPU is not needed for Headroom. - OpenRouter + caching. The cache-optimization docs cover Anthropic/OpenAI/
Google but not OpenRouter — that's fine: CacheAligner (prefix stabilization)
is provider-agnostic, and DeepSeek/MiniMax do automatic prefix caching with
no special markers, so you get the benefit (your hit rate is ~86%). Headroom
can't insert Anthropic-style
cache_controlthrough an OpenRouter→Anthropic route, but your models don't need it. - Privacy.
HEADROOM_TELEMETRY=offin.envdisables anonymous usage telemetry (on by default upstream). - token vs cache mode.
HEADROOM_MODE=token(default) maximizes token reduction but rewrites prior turns, which churns provider prefix-cache early in a conversation.HEADROOM_MODE=cachefreezes prior turns to preserve cache hits — usually cheaper only for models with steep cache discounts (e.g. deepseek-v4-pro, whose cache reads are ~120× cheaper than fresh input). For flash-dominant traffic, token mode is typically the better bill. Switch in.env, restart the proxy, and compareheadroom perf.
MIT. Headroom is Apache-2.0, Hermes Agent is MIT — this project only orchestrates them; it bundles neither.