Paritok Code — Claude Code + Paritok compression, in one command


Inspiration

Claude Code is excellent at multi-file changes and complex reasoning, but those conversations hit the context window fast. Paritok compresses agent context by 48–78%, but wiring it up by hand takes two terminals and four steps that are easy to get wrong. Miss one export and you are not compressing; forget to unset it and every later session points at a dead port.

Worse: you cannot see whether it is working. The savings are invisible until the bill arrives, and a broken configuration silently passes content through uncompressed while still returning 200. For a product whose entire value is the saving, "silently saved nothing" is the worst available failure mode.

We built a one-command wrapper that handles the entire lifecycle, refuses to start when compression is unavailable, and shows you the savings live — on screen, as you spend them.


What it does

python paritok_code.py picks a free port, discovers your real upstream (so gateways like agentrouter just work), verifies compression is actually working, starts the Paritok proxy, installs a live status line, launches Claude Code, then tears everything down and hands you a receipt.

Live display pinned to the bottom of the screen:

⬢ PARITOK  compression ON · -48% input · saved 1.7k tok · $0.03 · 0 tool schemas stubbed
context █████░░░░░░░░░░░░░░░  25% · 50k/200k   without Paritok: ~26% · 51.7k

The bar is the context Claude Code is really carrying. The dim tail is where the same conversation would sit uncompressed — the saving, made legible while you work.

On exit, a terminal receipt plus paritok_receipt.html — a self-contained page you can share.

Verified results (reproducible via tools/smoke_compress.py):

  • Single turn: 3,597 → 1,851 tokens (48.5% cut)
  • Facts preserved: 3/3 (file path, line number, function signature)

Savings that don't break the answer.


How we built it

Python 3.10+, using Paritok's [proxy] extra and Claude Code's --settings flag to inject the status line without modifying the user's own config.

Key pieces:

  • Preflight check — before launching, the wrapper verifies the compression backend (hosted GPU or local Ollama) is reachable and refuses to start otherwise. Paritok passes content through uncompressed when its backend is down but still returns 200, so without this check the wiring looks perfect while saving nothing.
  • Status line renderer (paritok_statusline.py) — reads Claude Code's session JSON for context figures and hits the proxy's /stats for compression figures, rendered twice per second. Reconfigures stdout to UTF-8 with ASCII fallbacks so Windows cp1252 doesn't crash it.
  • passthrough tool discovery — Paritok's embedding strategy saves 77.5% on the same turn but drops Claude Code's built-in tools (Read, Glob, Grep) from the request when its ranker doesn't rank them highly. The agent then says "I don't have file-reading tools loaded right now" and stalls. We default to passthrough: schemas untouched, content-only compression. ~48% instead of ~78%, but the agent works.
  • Response-codec check — Claude Code sends accept-encoding: br, zstd. Paritok forwards that header verbatim, upstream replies in brotli, and httpx can't decode it unless brotli/zstandard are installed. The parse failure surfaces as 502 "Upstream returned invalid JSON. Status: 200" — which names JSON and points at upstream, neither of which is the problem. The launcher checks for both codecs and tells you exactly what to install if they are missing.

What we learned debugging Paritok:

  • create_app() and _preflight_backend() load config two different ways, and only one honours PARITOK_API_KEY — so the preflight reports success while the server tries Ollama.
  • ParitokConfig.load(path) silently falls back to defaults when path doesn't exist, so a typo looks like "compression just isn't working."
  • trace.enabled only logs content compression, not tool-schema stubbing, so a session that saved 86% by stubbing 300 schemas wrote zero trace lines.

We reported all three as hackathon-feedback issues with reproduction steps and proposed fixes.


Challenges we ran into

1. Claude Code saying it had no file-reading tools.

Measured: with 30 MCP tools present, Paritok's embedding strategy kept 7 of 46 tools at every k_max from 8 to 20, dropping Read, Glob, Grep, Edit, Write, and Bash each time. Reading tool_topk.apply_selection_adaptive() confirmed why: anything unselected that is not an MCP tool is omitted from the request entirely, not stubbed. So on a turn whose phrasing didn't rank them highly, the agent genuinely did not have them.

Paritok ships a looks_like_missing_tool_help() helper that detects the exact sentence we saw ("I don't have a … tool"), so the failure mode is known upstream.

Solution: strategy: passthrough. Content compression only, all schemas untouched. 48% instead of 77%, but the agent works. Documented the trade-off in the README so users running without MCP can turn embedding on if they want the extra margin.

2. Every request returning 502 "Upstream returned invalid JSON. Status: 200".

Reproduced deterministically by changing one header: accept-encoding: identity → 200 OK; accept-encoding: gzip, deflate, br, zstd → 502.

Root cause: _forward_headers passes the client's accept-encoding through verbatim, agentrouter responded in brotli, and httpx had no decoder for it because brotli/zstandard are not in the [proxy] extra. resp.json() threw on the compressed bytes, and Paritok reported the parse failure as a 502.

Solution: pip install brotli zstandard, plus a preflight check in the launcher that tells you exactly this if they are missing. Also filed as a hackathon-feedback issue with the suggestion to strip accept-encoding from forwarded headers entirely (it's a hop-by-hop concern the proxy should negotiate itself).

3. The documented quickstart (export PARITOK_API_KEY; paritok up) failing with "Cannot connect to … localhost:11434" while startup reported *"Hosted GPU server OK — API key accepted."*

create_app(config_path=None) calls ParitokConfig(), which ignores env vars. _preflight_backend(config_path=None) calls ParitokConfig.load(), which honours them. So the preflight sees the hosted backend while the server serving requests sees use_gpu_server: False.

Verified:

ParitokConfig().use_gpu_server           # False  <- create_app
ParitokConfig.load().use_gpu_server      # True   <- preflight

Solution (workaround): always pass --config-file with an absolute path. Filed the one-line fix as issue #1.


Accomplishments that we're proud of

  • The compression itself works, and the answer survives. 48.5% on a noisy build log with all three critical facts preserved (file path, line number, function signature). The model answered correctly from input that had been cut by nearly half. That's the part that matters.
  • Honest numbers. without Paritok: ~45% is marked with a tilde because it is an estimate (derived from per-turn deltas, not cumulative totals). The trace writes tmp/paritok_trace.jsonl so the figures can be audited line by line. And the preflight refuses to start rather than let you demo a session that saves nothing.
  • Gateway chaining just works. Claude Code → Paritok → agentrouter needed no code changes; --anthropic-url was enough, and auth forwarded untouched.
  • Found and reported real issues upstream — three with reproduction steps and proposed fixes, all found while building this. The tool-discovery one (apply_selection_adaptive dropping non-MCP tools) is a design flaw worth surfacing, since anyone else integrating Paritok with an agent will hit it.

What we learned

Paritok's compression is excellent; its tool discovery breaks agents. The model is genuinely good — 48–78% depending on strategy, and facts survive. But embedding strategy makes Claude Code unworkable, and the failure mode (agent saying it has no file-reading tools) is subtle enough that it looks like a config mistake rather than a known limitation.

Defensive preflight checks are load-bearing. Three bugs we found all had the same shape: something reports success while the thing actually serving requests has decided otherwise. Verifying the backend before launch, checking for response codecs, and refusing to start on a mismatch caught all three.

Windows encoding matters. The status line raised UnicodeEncodeError on block glyphs under cp1252. Reconfiguring stdout to UTF-8 with ASCII fallbacks fixed it, but it is the sort of detail that only surfaces when you test on the OS most users actually have.

brotli and zstandard being optional dependencies of httpx is a trap when you forward accept-encoding. The client advertises a codec, the proxy forwards that advertisement, upstream complies, and then the proxy cannot decode the reply. The error message points at JSON and upstream — neither of which is the problem — so debugging starts in the wrong place.


What's next for Paritok Code

1. Submit the feedback issues — three genuine bugs found and verified, worth landing upstream.

2. Test with MCP servers attached — we verified passthrough keeps all tools with 30 MCP tools present, but never ran a full session against real MCP. That's the environment where tool discovery matters most.

3. Measure interactive vs. -p compression — the 86.7% figure came from a claude -p run (non-interactive, batch-mode). Interactive sessions have smaller messages and less tool spam per turn, so the distribution may be different. Worth re-measuring.

4. Adaptive k_max based on whether MCP is presentembedding saves 77.5% when Claude Code runs alone. passthrough saves 48.5% but works with MCP. The launcher could detect MCP tools (their names follow a pattern) and switch strategy accordingly, getting the best of both.

5. Make the receipt shareable — it is already a self-contained HTML file with no external dependencies, but it could be better: add a "copy link" button, render the bar as SVG so it scales, and include the trace summary inline rather than pointing at a local .jsonl.


Built with Paritok — an open-source 4B context-compression model, fine-tuned from Qwen3-4B-Instruct-2507 and trained on 45K real coding-agent trajectories.

Built With

+ 6 more
Share this project:

Updates