#4120·9router

Feature request: add lean-ctx as a Token Saver (external /v1/compress — verified compatible with the Headroom slot)

Author: jsapedeCreated Sep 17, 2026Updated Sep 17, 2026

Summary

Add lean-ctx (https://github.com/yvgude/lean-ctx) as another external token saver in the Dashboard → Endpoint → Token Saver slot, alongside Headroom. It exposes the same POST /v1/compress messages-in/messages-out surface that open-sse/rtk/headroom.js already calls, so the integration reuses the existing code path instead of adding a new architecture.

  • License Apache-2.0, Rust single binary, runs fully locally (nothing leaves the machine)
  • Compression is deterministic + reversible (content-addressed recovery of every rewrite), prompt-cache-safe by contract
  • v3.10.2 verified today, live, against the exact payload shape 9Router sends

Compatibility (measured, not assumed)

What open-sse/rtk/headroom.js sends today:

javascript
const payload = { messages, model };
if (compressUserMessages) payload.config = { compress_user_messages: true };
fetch(`${url}/v1/compress`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload) });
// requires: res.ok && Array.isArray(data.messages)

What lean-ctx returns for that exact call (real output, v3.10.2):

json
{"messages":[],"stats":{"original_tokens":10778,"compressed_tokens":492,"saved_tokens":10286,"saved_pct":95.4,"tokenizer":"llama","model":"…"},"tokens_before":10778,"tokens_after":492,"compression_ratio":0.05}
  • messages[] is present → passes the only structural check in the caller. ✅
  • Extra fields are tolerated: sending config: { compress_user_messages: true } returns 200 (ignored, no error). ✅
  • Fail-open is already covered by the existing code path (null on any error). ✅

Measured savings on the exact workload RTK targets — a 400-line grep -rn result (~29 KB, 10,778 tokens):

without: 10,778 tokens
with:       492 tokens   → 95.4% saved, output still readable ("400 lines → 400 unique" stub + sample lines)

Short user/system messages are left untouched by default (0% change on a 2-line prompt) — compression targets tool results and history.

Two small adapters it needs

  1. Auth header (optional). lean-ctx's /v1/compress requires a Bearer (its own proxy token) and returns 401 without it — the current caller sends only Content-Type. Either add an optional API-key field to the Token Saver config, or run lean-ctx auth-less for loopback/trusted-network deployments (proxy_loopback_open).
  2. Stats key mapping (3 lines). formatHeadroomLog() reads stats.tokens_before / tokens_after / tokens_saved; lean-ctx reports stats.original_tokens / compressed_tokens / saved_tokens (plus top-level tokens_before/tokens_after). Without mapping, the "reported token delta" log line reads 0 (functional impact: none). The byte-level formatHeadroomSizeLog keeps working unchanged.

Deployment

  • Single static binary — install.sh, cargo install lean-ctx, npm i -g lean-ctx-bin, brew; no Dockerfile in the repo yet, so a thin sidecar image would be needed (same pattern as the Headroom service in the Docker network).
  • Service: lean-ctx proxy start --port=4444.

Positioning vs the existing savers

What it compresses Where
RTK tool_result content built-in
Headroom external /v1/compress built-in slot
lean-ctx messages-in/messages-out (tool results + history), AST-aware file reads and 95+ shell-output patterns on its own path same external slot as Headroom — users choose
Caveman / Ponytail output tokens prompt injection

No conflict with the built-ins: it is one more option in the same slot.

Happy to run a build against my setup (9Router in Docker on a small homelab, Claude Code / Codex clients) if a first cut lands.