One endpoint sits in front of every model you use. You change a base URL, not your code. Responses come back stamped with what the request cost, what it would have cost on your default model, and which work item to bill it to — written to a database on your own disk. Four credible cheapest-model routers appeared or matured between June and July 2026. None of them returns you a receipt.
The proxy speaks the Anthropic Messages API. Anything already pointed at Anthropic — Claude Code, an agent loop, a script, a CI job — points at the fabric instead by setting one environment variable. Request and response shapes are unchanged; the response gains one extra key.
Any client that already speaks the Anthropic Messages API. No SDK swap, no wrapper library.
Accepts POST /v1/messages. Also serves /v1/models and /health.
fabric_proxy/server.py
Resolves model id to provider, enforces a hard per-call cost cap, records the reason for the decision.
fabric_proxy/router.py
Port resolves from the service's own env var — service names, never literals.
# point an existing tool at the fabric ANTHROPIC_BASE_URL=http://<host>:$KEVIN_PORT_WALT # optional per-request headers, all read by server.py X-Kevin-Ticket: KAN-1120 X-Kevin-Holon: <work-unit id> X-Kevin-Precompressed-Bytes: <n>
It does not yet auto-downgrade to the cheapest capable model. That is marked as a future phase in router.py and we are not going to imply otherwise. It is also the part of this layer that stopped being worth anything: a hyperscaler ships a router free inside your account, a model vendor ships one that routes to itself, and an Apache-2.0 project ships one for nothing. The next two sections are the parts that are not free.
A router sold by a model vendor routes into that vendor's catalogue. This one has nothing to sell you at the model layer, so price convergence across providers becomes an arbitrage surface instead of a threat. Each backend is a small adapter that translates the Anthropic request shape to whatever dialect that provider speaks, and normalises the answer back.
one adapter each, in fabric_proxy/providers/
| Backend | Adapter |
|---|---|
| Anthropic | anthropic.py |
| OpenAI | openai_provider.py |
| Groq | groq.py |
| Gemini | gemini.py |
| Local Ollama, on your own GPU | ollama_provider.py |
Four of those five speak the OpenAI chat-completions dialect upstream, which is why adding an OpenAI-compatible backend is a small adapter rather than a redesign. Together, Fireworks, Bedrock and a direct vLLM adapter are named on the roadmap and are not written. If you need one of them today, you are writing it.
Models served from your own hardware are registered at a real price of zero rather than left unpriced. An unpriced model silently costs $0.00 and silently disables the cost cap — a deliberate zero and an unknown model must not look alike in the ledger.
The local proxy behind the Ollama adapter can soft-fall-back through free cloud providers and, as a last resort, a paid one. If it reports that something other than local inference served the request, the adapter raises OllamaFallbackError and fails the call rather than bill a paid request as free local compute. A ledger that quietly absorbs that substitution is worth nothing.
Second-order benefit, and the one operators feel first: provider catalogues churn. Models get deprecated on weeks of notice and free tiers get withdrawn on a date. A routing layer absorbs that; a direct integration in fifteen client codebases eats it.
Every non-streaming response carries a kevin_value block computed for that exact request. It reports two independent things, and it reports them separately.
Field names exactly as emitted by fabric_proxy/kevin_value.py. Values below are zeroed — this is the shape, not a measurement.
"kevin_value": { // routing axis "model_routed": "…", "model_baseline": "…", "provider": "…", "input_tokens": 0, "output_tokens": 0, "cost_routed_usd": 0.000000, "cost_baseline_usd": 0.000000, "routing_savings_usd": 0.000000, "routing_reason": "…", "latency_ms": 0, // compression axis "precompressed_bytes": 0, "precompressed_tokens_est": 0, "compression_ratio": 1.0, "compression_savings_tokens":0, "compression_savings_usd": 0.000000, // rollup of both "naive_cost_usd": 0.000000, "total_savings_usd": 0.000000 }
cost_baseline_usd prices the same measured token counts on your declared default model. routing_savings_usd is the difference. Token counts come from the provider's own usage block on the response, not from an estimate.
If the caller declares how many bytes of source it compressed before sending, the stamp prices the difference at the routed model's input rate. Both the ratio and the dollar figure are reported on their own.
Routing savings and compression savings have different denominators and different failure modes. Routing is checkable against two public rate cards. Compression depends on what the caller claims it compressed, and on a caching baseline that is not yours.
Add them into one headline and no reader can attack either half, so the number cannot be falsified — which is the same as saying it cannot be verified. The rollup fields exist for convenience, and both components are always present beside them so you can take the claim apart.
Precompressed size is converted to tokens at a flat four bytes per token — no tokenizer in the path. Good enough to size a saving, not exact. The constant is in compression_metric.py; go read it.
The precompressed byte count arrives in a request header. The proxy cannot verify it. If you care about the compression axis, the number has to be produced by something you trust, and the routing axis is the one to lean on in an argument.
The rate table in config.py is static and dated, not fetched at runtime. It goes stale silently, so it carries its own reconciliation date and flags any model priced at zero outside the local providers. A counterfactual is only as honest as its rate card.
The stamp answers one request. The ledger is what makes it evidence: one row per call in SQLite on your own disk, inserted and never rewritten, indexed by the work item that caused the spend. None of the routers above ships this layer, which is the reason this page exists. (In the source and in the service table it is called Walt.)
fabric_proxy/telemetry.py — at KEVIN_HOME/db/walt.db
| Column | Why it is there |
|---|---|
| ts request_id | When, and a unique id per call. |
| ticket holon stream_id | The work item, work unit and stream that caused the spend. Indexed. This is what makes cost attributable to a client, a ticket or a run instead of to a month. |
| session_id tool_origin task_type | Which caller, which tool, what kind of work. |
| model_routed provider | Which backend actually served it. |
| input_tokens output_tokens | Billable units, taken from the provider's usage block. |
| cost_routed_usd latency_ms | What the call cost, and what the decision cost in time. |
| pub_ver | Provenance: identifies the writer that produced the row, so records written by different code are distinguishable rather than silently averaged together. |
The write is synchronous, lock-guarded and single-row, and a failure is logged and dropped. Accounting must never be able to fail a request, and a queue that can silently back up is a worse lie than a missing row.
The counterfactual is computed on every request and persisted alongside it: model_baseline, cost_baseline_usd and compression_savings_usd are ledger columns. So a savings rollup over a window is a query, not an estimate.
What it will not do is invent history. A row written before those columns existed has an unknown baseline, stored as unknown rather than as zero, because zero would read as "this request saved nothing" and quietly drag every historical average down. Those rows are excluded from the money columns and reported as a separate coverage figure, so the rollup always shows how much of the window it could actually account for.
Streaming is passed straight through to Anthropic with no stamp and no ledger row. If your traffic is mostly streaming, the ledger will be mostly empty, and you should know that before you install rather than after.
A ClickHouse insert path carries the baseline columns and runs when a server is present. It is opt-in and silently skipped when absent, so on a normal install SQLite is the ledger that exists and the one that has rows.
We publish the schema rather than a savings figure on purpose. Our own ledger currently holds development traffic — overwhelmingly repeated test fixtures. It proves nothing about your workload and we will not quote it at you. The only number worth trusting is the one your own traffic produces on your own hardware, which is why the schema and the read path are the artifact and a percentage is not.
This layer is narrow on purpose, and several of the cases below are ones we cannot win on any timescale that should matter to your decision.
One more, on vendor risk: we are small, and against a well-capitalised competitor that is a legitimate finding in a procurement review. The only answer that holds is structural — it runs on your hardware, the ledger is a SQLite file you own, and the deployment outlives the vendor.