# 25 budgets declared, 3 enforced: the fallback was Infinity

> A prompt assembler resolved undeclared lanes to Infinity, so a 600-token cap in the registry never fired once. The fix is a test that joins the two lists.

- Author: Chad Priest
- Published: 2026-09-04
- Canonical URL: https://blog.vodou.ai/25-budgets-declared-3-enforced-the-fallback-was-infinity/
- Tags: ai, typescript, debugging, config

---

Our gateway has a registry, `lanes.toml`, that declares every block of text allowed into a model's prompt: 25 stanzas, 14 of them carrying a numeric token cap. The prompt assembler that actually packs the request knows three of those caps. Nothing compares the two lists. That is the class: a config declares resource budgets, a runtime component enforces its own hardcoded subset, and the gap is invisible because no test joins them.

Here is the specific one. `lanes.toml` line 134 declares the `workbench` lane (operator standing instructions, injected on the user side) at `budget = 600`, with the note "was UNCAPPED operator free text appended verbatim." The enforcement, in `MCP-servers/Vodou-Console/src/llm.ts`:

```ts
const b = laneBudgetTok('workbench');
const over = tokensOf(wbBlock) > b;
if (over) console.error(`[Context] budget: workbench instructions are ${tokensOf(wbBlock)} tok over ${b} tok ...`);
```

And the resolver it calls:

```ts
const CONTEXT_BUDGET_DEFAULT_TOK: Record<string, number> = {
  memory: 2000, tool_results: 1500, skill: 3000,
};
return CONTEXT_BUDGET_DEFAULT_TOK[lane] ?? Infinity;
```

`b` is `Infinity`. `over` is always false. That log line has never printed, the receipt can never carry `state: over_budget`, and the free text still goes in whole, exactly as it did before anyone wrote 600 down. The override path was empty too:

```sql
sqlite> SELECT COUNT(*) FROM gateway_settings WHERE key LIKE 'context.budget%';
0
```

I wrote that `?? Infinity`, and I defended it in a comment: defaulting an unknown lane to 0 would evict a whole lane the moment someone added one and forgot the table. That reasoning is still correct. I was wrong that it was the end of the job. Fail-open is the right default and a silent one, so it needs a reconciler or it eats every ceiling you write down.

## Fail-open caps, two lists, and nobody joining them

The same shape shows up in API gateways with declared versus enforced rate limits, MCP servers with per-tool result caps, and any RAG context builder whose `config.yaml` names buckets the packer does not.

**Every key that declares a limit must resolve to a finite limit at the site that enforces it, and the set difference between declared keys and enforced keys must be asserted empty by a test.**

The [geodocs context-budget spec](https://geodocs.dev/ai-agents/agent-context-window-budgeting-spec) says to declare per-bucket caps in a runtime config with an overflow policy. Good advice, and it does not cover the case where the config is not the object the packer reads. [Ethos](https://ethosagent.ai/docs/building/explanation/tool-result-budget.md) does cover it, almost by accident: its `resultBudgetChars: 80_000` lives in one object that `ToolRegistry.executeParallel` divides. One list, one owner, nothing to drift.

## Print the resolved cap for every section your assembler packs

Paste this at the end of your assembler and send one real request:

```ts
for (const name of Object.keys(sections)) {
  console.error(`[budget] ${name} cap=${budgetFor(name)} used=${countTokens(sections[name])}`);
}
```

Passing looks like `[budget] history cap=4000 used=3110`, two finite numbers on every line. Failing looks like `[budget] tool_schemas cap=Infinity used=4812`, or `cap=undefined`, or a cap so large nothing could ever reach it. Then make it permanent:

```ts
test('every declared cap resolves at the enforcement site', () => {
  for (const [name, cap] of Object.entries(loadBudgetConfig())) {
    expect(budgetFor(name)).toBe(cap);   // not: expect(cap).toBeDefined()
  }
});
```

That comment is not rhetorical. Our test asserted `/budget\s*=/` against the registry, so it passed on a stanza whose number no code ever read. If your budget file has more keys than your resolver, you do not have budgets. You have documentation with units.

---

Source: [25 budgets declared, 3 enforced: the fallback was Infinity](https://blog.vodou.ai/25-budgets-declared-3-enforced-the-fallback-was-infinity/) by Chad Priest, from Building Vodou in Public.
