An MCP server that offloads LLM context to an external store, keeping your active context window lean across long sessions and session boundaries.
Built in Rust. Single binary, zero runtime dependencies, full ACID persistence.
Long agent sessions accumulate context fast. Past reasoning, file contents, error traces, design notes — they all stay in the window whether you need them right now or not. This inflates token costs, degrades reasoning quality, and means every new session starts blind.
context-hydra gives agents a place to put things down and pick them back up: a compact indexed store with cheap summaries, full-body retrieval on demand, and a trust fence around retrieved content.
Each offloaded entry has two parts:
- Matrix row — a compact index record: ID, type, topic, tags, summary, salience score, status, token estimate
- Body file — the full content, stored on disk, never in context unless explicitly checked out
Agents interact via MCP tools. The matrix is always cheap to read; full bodies are gated behind a checkout/checkin nonce handshake that fences retrieved content as untrusted external data.
| Tool | Description |
|---|---|
peek |
Count-only snapshot (cold/hot/pinned counts, type breakdown). Cheapest orient. |
matrix |
Full index sorted by salience. One line per entry. |
search |
Filter by keyword, context type, status, or minimum salience. |
recall |
Read one entry's summary without pulling the full body. No state change. |
checkout |
Pull full body into context. Issues a nonce; marks entry hot. Body is fenced as untrusted. Warns on recent re-checkout (churn signal). |
checkin |
Return a checked-out entry. Requires the nonce from checkout. Marks entry cold. |
offload |
Move content out of active context. Deduplicates by content hash. Pass compress: true to run through the local model before storing (lossy). Returns a matrix pointer. |
offload_path |
Offload a file by path — server reads it, content never enters your context. Strongest form of context displacement. |
offload_path_sectioned |
Offload a markdown file split by headings — each section becomes a separate entry. Server reads the file; content never enters context. Headings inside fenced code blocks are not treated as split points. |
checkout_remote |
Pull full body, distill via local model, return the distillate fenced as untrusted. Body on disk is unchanged; no checkin required. Returns an error if no local model is configured, the call fails, or the distillation produces more output than the original (expansion guard) — use checkout directly in those cases. |
pin |
Set salience to 1.0, mark entry pinned. Pinned entries survive reap and cleanup passes. |
reap |
Delete stale entries by age and/or salience. Pinned entries are always spared. Use dry_run: true to preview. |
forget |
Permanently delete an entry and its body file. |
stats |
Session operation counts and content volume. No "tokens saved" claims — the server cannot see your context window. |
If your MCP client supports resources, these are available without a tool call:
| URI | Description |
|---|---|
hydra://matrix |
Compact index of all entries |
hydra://body/{id} |
Full body of a specific entry, fenced as untrusted external data |
Bodies retrieved via checkout or hydra://body/{id} are wrapped in a nonce-tagged fence:
<hydra:body:3e7b28a9 id="..." topic="..." offloaded="2026-07-15T10:00:00Z">
...content...
</hydra:body:3e7b28a9>
The nonce is random per call and unknown to stored content, making fence breakout structurally hard. Any occurrence of the closing delimiter in the stored content is escaped before delivery.
The nonce also serves as the checkout token: you must present it to checkin. This couples the security handshake to content retrieval.
offload and offload_path compute a SHA-256 hash of the content before writing. If the same content is already banked under any entry, the duplicate is rejected and the existing pointer is returned.
Bodies are content-addressed too (block_sha): two matrix rows that share the
same stored body share one .zst, and the block is removed only when the last
entry referencing it is forgotten or reaped.
The matrix and bodies persist in ~/.local/share/context-hydra/ across sessions. On startup, any entries left in hot status from a prior session are reset to cold and their stale nonces are cleared.
Min-cold-time tracking (churn warnings after rapid checkout/checkin cycles) is session-scoped and lives in memory only — it doesn't persist, so a fresh session has no artificial cooldown on old entries.
~/.local/share/context-hydra/
hydra.redb # matrix index (redb, pure Rust, ACID)
bodies/ # legacy plaintext bodies; migrated at startup
~/.hashtext/
blocks/{sha[:2]}/
{sha}.zst # zstd payload (level 10)
{sha}.json # BlockMeta (name, description, kind, sizes, tokens)
Bodies are hashtext blocks. Identity is sha256(plaintext): identical
content dedups, changed content is a new block, and reads verify the
fingerprint. The same store is readable by the hashtext CLI and the forseti
pi extension — one body store, two front-ends. Set HASHTEXT_DIR to move it.
Legacy bodies/{uuid}.txt files are imported into the block store on startup
(plaintext is removed after the block is written). content_hash remains the
dedup key for what the caller sent; block_sha identifies the stored body, so
compress: true (which rewrites the text) does not break dedup.
redb was chosen over SQLite for zero C FFI. Linear scan over ~500 rows at <1ms — no indexing needed at this scale.
cargo test # includes cross-language tests (skipped without hashtext)
python3 scripts/smoke_block_backend.py # offload -> checkout -> checkin -> forget via MCPThe cross-language tests write with one implementation and read with the other;
the MCP smoke asserts the CLI can read a block written through offload.
Rust toolchain (stable, 1.80+). Install via rustup.rs if you don't have it:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | shgit clone https://github.com/alytaphoenix/context-hydra
cd context-hydra
cargo build --releaseCopy the binary somewhere on your PATH:
cp target/release/context-hydra /usr/local/bin/
# or
cargo install --path .Add to your MCP client config (e.g. ~/.claude/settings.json for Claude Code):
{
"mcpServers": {
"context-hydra": {
"command": "/path/to/context-hydra",
"args": []
}
}
}Paste the following into any project's CLAUDE.md to give agents a protocol for using context-hydra:
## Context management (context-hydra MCP)
Call `peek` at the start of every session. If entries exist, call `matrix` to orient.
When to offload: stable content > ~400 tokens you won't need this turn — file contents
after reading, error traces after diagnosing, design decisions after settling.
For files not yet read: use `offload_path` (server reads the file, content never enters context).
Retrieval order (cheapest first): peek → matrix → search → recall → checkout.
Try `recall` before `checkout` — the summary is usually enough.
After processing a checked-out entry, call `checkin` with the nonce.
Maintenance: `pin` high-value entries; `reap` stale ones periodically.| Language | Rust (2024 edition) |
| MCP SDK | rmcp 2.2.0 |
| Database | redb 4.1.0 |
| Transport | stdio |
| Hashing | sha2 0.10 (SHA-256 for dedup) |
| UUIDs | uuid 1.x with v4 |
| HTTP client | reqwest 0.12 (rustls, no system OpenSSL) |
| Config | toml 0.8 |
context-hydra can use a local LLM (any OpenAI-compatible endpoint) to compress and summarize content before it leaves the agent context or before it is returned for use with a remote model. This reduces token costs on the remote side without requiring the calling agent to do the compression work itself.
Compress-on-offload (offload with compress: true):
Content is sent to the configured local model with a summarization prompt before being stored. The body file holds the compressed version. The original is gone — use this only for content where lossy compression is acceptable (completed reasoning traces, resolved error logs, finalized design notes). Silently stores raw if compression is unavailable or if the model's output is longer than the input (expansion guard).
Checkout-for-remote (checkout_remote):
Pulls the full body, passes it through the local model to produce a token-efficient version, and returns the compressed output fenced as untrusted external data. The body on disk is unchanged. Returns an error if the call fails or the distillation expands the content — use checkout directly in those cases. Use this when you need to inject stored content into a remote model's context at minimum cost.
The config file lives in the platform data directory:
- macOS:
~/Library/Application Support/context-hydra/config.toml - Linux:
~/.local/share/context-hydra/config.toml
[local_model]
base_url = "http://localhost:8000/v1"
compression_model = "Qwen2.5-7B-Instruct-4bit" # see model selection below
api_key = "" # leave empty if endpoint requires no auth
compress_target_tokens = 200 # target output length for compress-on-offload
checkout_remote_target_tokens = 300 # target output length for checkout_remote distillationIf [local_model] is absent or base_url is empty, compress: true is silently ignored and checkout_remote falls back to plain checkout.
The compression_model field is for compression and summarization only. A large reasoning model configured for general work is the wrong choice here for two reasons.
Thinking/reasoning mode must be disabled. If thinking mode is on, the model spends the entire token budget on reasoning steps and returns empty visible content — the call silently falls back to storing raw and nothing gets compressed.
How to disable thinking:
- Model variant: Choose a non-thinking variant. Instruct variants without a "thinking" or "reasoning" suffix typically don't have it enabled by default (e.g.
Qwen2.5-7B-Instructrather thanQwen3-7B). - Server-level setting: Many local servers (LM Studio, Ollama, etc.) expose a "disable thinking" toggle or
thinking_budgetparameter. Check your server's docs. - Dedicated small model: The simplest option — a separate small model without thinking mode at all.
A smaller dedicated model is often better than a large one. Compression is mechanical — "shorten this, keep identifiers and numbers." It does not require large-model capability, and large models are slower. Benchmarked results on four content types (error traces, reasoning chains, design notes, Rust source):
| Model | RAM | Avg ratio | Avg latency | Efficiency | Notes |
|---|---|---|---|---|---|
Qwen2.5-3B-Instruct-4bit |
2.3 GB | 2.4× | 4.3s | 1.04 | Highest efficiency; best on short prose; fails on dense code |
Qwen2.5-7B-Instruct-4bit |
4.7 GB | 2.3× | 8.6s | 0.50 | Best single-model error trace compression (4.8×); also fails on dense code |
Qwen2.5-32B-Instruct-4bit |
18.5 GB | — | — | — | Not benchmarked; expected to handle code better |
Llama-3.2-3B-Instruct-4bit |
2.0 GB | — | — | — | Not benchmarked; widely supported fallback |
Efficiency = average compression ratio / RAM GB. All Qwen Instruct models have thinking mode off by default.
Code-heavy content needs a larger model. Both Qwen 3B and 7B trigger the expansion safeguard on Rust source — the compressed output exceeds the input length, so the server stores raw. If your workload includes source files, a 30B+ model with thinking disabled is needed to get meaningful compression — or skip compression on code entirely (offload without compress: true) and use checkout_remote for distillation on retrieval instead.
All models available via mlx-community on HuggingFace for Apple Silicon. Run python3 bench_hydra.py --suggest-models to see download paths, or python3 bench_hydra.py --compare-models to benchmark every model on your local endpoint.
- Relevance judgment. context-hydra does not use the local model to decide which stored entries are relevant to the current task — that's the caller's responsibility via
searchandrecall. - Standard offload path. The default
offloadhas no model call, no latency, no dependency. The local model is opt-in per call. - Cross-entry synthesis.
checkout_remoteoperates on one entry at a time.
offload takes content already in your context — you've already paid the tokens for it. The net saving is on future sessions that load the matrix instead of the full content.
offload_path reads the file on the server side. The content never enters the agent's context at all. This is the only operation that displaces tokens in the current session.
The server is a stdio process with no visibility into the client's context window, the tokenizer, or transcript state. Any "tokens saved" figure would be a guess.
bench_hydra.py --window-analysis does this measurement properly: it queries the live tool schema, measures matrix row sizes from your real store, computes break-even content size per turn, and prints recommended token target settings. Measured baseline on the default tool set: ~1540 tokens schema overhead, ~23 tokens per matrix row, break-even at 3+ turns out of context for content ≥ 400 tokens.