A Halley project
Cut Claude Code's token bill on big-file reads by 80–95% — with zero fidelity loss, zero latency, and zero data egress.
When Claude reads a 2,000-line file to answer one question, you pay for 2,000 lines. Spotify's Portal/shunt attacks this by routing reads through a cheaper LLM that summarizes the file — which works, but costs 10–30 seconds per read, ships your source to a second model vendor, and returns lossy summaries (their own writeup admits the worker missed a thread-safety bug).
context-broker takes a different bet: most of what Claude needs from a big file is its structure, and structure can be extracted deterministically. No second model. Nothing leaves your machine. ~1 millisecond.
| Portal / shunt | context-broker | |
|---|---|---|
| Mechanism | second LLM summarizes | structural extraction — no model |
| Latency | 10–30 s per delegation | ~1 ms per interception |
| Fidelity | lossy summary | verbatim source lines, real line numbers |
| Data egress | code sent to a second vendor | zero |
| Read → edit | re-read the whole file to edit | skeleton keeps line numbers → ranged read of just the slice |
| Savings | ~90 % on bulk reads | 80–95 % per intercepted file |
A PreToolUse hook intercepts Claude Code's Read tool:
- File ≤ 350 lines, binary, image/PDF/notebook, or a ranged read (
offset/limit) → passes through untouched. - Otherwise the full read is denied — but the denial contains the answer: the file's exact skeleton (imports, signatures, types, doc comments — verbatim source lines with real line numbers). Claude gets the structure instantly and expands only the bodies it needs via ranged reads, which always pass through.
- Any error, anywhere → fail open. The hook can never break
Read.
Because the skeleton keeps real line numbers, the read→edit path Portal can't handle just works: Claude sees the structure, ranged-reads the 30 lines it wants to change, edits them. No double read.
The interception message teaches the expand/bypass mechanics at exactly the moment they're needed — zero standing prompt-context tax.
42│export interface Post {
│ ⋯ 16 lines (43-58)
59│/** Metadata only — what the bundle gets. */
60│export type PostMeta = Omit<Post, 'body' | 'html'>;
61│
62│export function readPosts(dir: string): Post[] {
│ ⋯ 65 lines (63-127)
/plugin marketplace add halleylabs/context-broker
/plugin install context-broker@context-broker
That's it. The hook is wired automatically. On first session start the plugin compiles the native binary in the background (needs any C++17 compiler); until then — or if no compiler exists — it runs on the bundled Python implementation, which behaves identically at ~25 ms instead of ~1 ms.
git clone https://github.com/halleylabs/context-broker && cd context-broker
make && ./test.shThen add to .claude/settings.json (project) or ~/.claude/settings.json (global):
{"hooks": {"PreToolUse": [{"matcher": "Read", "hooks": [
{"type": "command",
"command": "/path/to/context-broker/cb hook 2>/dev/null || python3 /path/to/context-broker/cb.py hook",
"timeout": 10}
]}]}}The same binary is a standalone tool:
cb skeleton <file> # exact structural view, line-numbered
cb read <file> <start> <n> # exact ranged slice
cb fold < build.log # compress noisy logs: dedupe, keep failures + stack frames
cb bench <repo> # measure would-be savings across a repo (no model calls)
cb stats # cumulative tokens saved (~/.cache/context-broker/ledger.jsonl)
cb hook # PreToolUse handler (stdin JSON → decision JSON)
cb bench on a repo before installing tells you exactly what you'd save — measured, not estimated.
| Env var | Default | Meaning |
|---|---|---|
CB_MIN_LINES |
350 | files at or under this pass through |
CB_MAX_SKEL_LINES / CB_MAX_SKEL_BYTES |
220 / 10000 | skeleton budget |
CB_FOLD_CAP |
300 | fold output line budget |
CB_DISABLE=1 |
— | kill switch |
Set them in the env block of your Claude Code settings, or in your shell profile.
cb bench on a production Python monorepo (506k LOC including its .venv), one command, no model calls:
409 files over 350 lines · 3,832,475 → 455,840 tokens (89% saved) · scanned in 296ms
Two things this run shows beyond the headline:
- Hand-written code never triggered the hook. Every project-authored source file was under the 350-line threshold — the only project file intercepted was
uv.lock. The interceptor is invisible until Claude wanders somewhere expensive. - The savings land exactly where full reads are pure waste — dependencies, lockfiles, and generated artifacts, the files agents dip into when chasing library behavior:
| File | Full read | Skeleton | Saved |
|---|---|---|---|
ruff.cyclonedx.json (SBOM) |
102,448 tok | 91 tok | 100% |
uv.lock |
76,397 tok | 2,520 tok | 97% |
fastapi/routing.py |
63,908 tok | 2,080 tok | 97% |
anthropic/.../messages.py |
33,327 tok | 1,726 tok | 95% |
The SDK skeleton keeps every create / stream / count_tokens signature — sync and async — with real line numbers, which is precisely what an agent needs when checking call shapes. A session that pokes into site-packages a handful of times goes from ~150k tokens of full reads to ~8k of exact structure.
Hand-rolled matchers, no regex on the hot path, single ~200 KB dependency-free binary:
- skeletonize an 800-line file: ~1 ms
- skeletonize a 28,000-line vendored bundle: ~8 ms
- bench an entire repo: ~3 ms
- allow-path overhead added to every
Read: <1 ms
Languages with real structural awareness: TypeScript/JavaScript/TSX, Python, Go, Rust, Java/Kotlin/Swift/Scala/C#, C/C++/Obj-C, Ruby, PHP, CSS/SCSS, shell, SQL, Markdown, YAML/TOML, JSON — plus an indent-based generic fallback for everything else.
- Exact text only. Every emitted line is verbatim from disk with its real line number. A skeleton can be stale-read or incomplete, but it can never be wrong — there is no summarizer to hallucinate a signature.
- The denial is the answer. Blocking a tool call normally costs a re-planning round trip. Here the skeleton rides inside the denial reason, so interception is strictly cheaper than the read it replaced.
- Fail open, always. Unreadable file, binary content, malformed hook input, any exception: the original
Readproceeds../test.shcovers every path. cb.pyis the executable spec. The C++ and Python implementations produce byte-identical output (verified bytest.sh; the one divergence is that oversized skeletons truncate at a line boundary in C++ vs mid-line in Python).
- Skeletons are structure, not behavior — for deep-logic questions Claude should (and will) pull full bodies via ranged reads.
- macOS/Linux. Windows: the Python fallback should work under Git Bash; untested.
- Token counts use a chars/4 estimate for the ledger; real savings depend on your tokenizer.
MIT