building vodou.

Your token budget is a comment: 8,000 declared, 12,000 shipped

A registry declared an 8,000-char budget, the code enforced 12,000, and the log recorded 14,188 as proof it worked. Nothing ever joined the three numbers.

Chad Priest / / 5 min read

Three numbers live in the same six lines of my config, and no two of them agree.

8,000 in the registry, 12,000 in the code, 14,188 in the log

I keep a registry of every place text gets spliced into a model’s prompt. One stanza per lane, each declaring a budget. Here is doc_attach, the lane that inlines a document when you type @doc:something:

[[lane]]
name = "doc_attach"
emits = "log"   # PROVEN 2026-08-30: 0 rows -> 1 the moment a doc was attached, 14,188 chars
injector = "MCP-servers/Vodou-Console/src/doc-attach.ts (@doc: resolver)"
budget = 8000                 # chars, INLINE_BUDGET passed as --max-chars

And here is the constant that actually does the cutting, in that injector:

// MCP-servers/Vodou-Console/src/doc-attach.ts
const INLINE_BUDGET = 12_000;

8,000 declared. 12,000 enforced. 14,188 observed, and written into the same stanza as evidence the lane worked. That measurement is 177% of the budget sitting four lines below it. I wrote both comments. I never did the subtraction.

git log -S says the code constant landed 2026-08-10 and the registry line landed 2026-08-28. Eighteen days later I transcribed a number from one file into another and got it wrong by half, and nothing anywhere noticed. Worse: 12,000 is per document. The resolver loops the deduped set of @doc: slugs with no aggregate cap, so three tokens in one message is three inline bodies.

The guard checked that budget existed, never what it meant

There is a pre-commit guard for this. scripts/coherence-guard.py rule 8 refuses a new lane literal unless the registry has a matching stanza, and its failure text reads:

lane x is in lanes.toml without a budget and trust: a registry entry that declares neither is the private budget the rule exists to stop.

Look at what it tests: not entry.get("budget"). Presence. The field is read by exactly two consumers, the guard and a unit test, and both ask only whether the key is non-empty. Meanwhile the assembler in MCP-servers/Vodou-Console/src/llm.ts carries its own table (memory 2000, tool_results 1500, skill 3000) and returns Infinity for anything else. Twenty-five declared lanes, three enforced ones, zero comparisons between the lists.

When I finally wrote the join against the live event log, I got this:

lane                declared  max_chars     pct
convo_recall             200        649    324%
turn_tag                  50         56    112%
memory                  2000       1294     64%
doc_attach              8000        275      3%

Two of those “overages” are fake. convo_recall and turn_tag declare tokens; the log stores chars. The registry never had a units field, so the numbers were never comparable, which is the actual reason nobody had run this query in the year the file has existed.

The same defect one directory over: principal arrives in the POST body

Same day, same shape. The route that exists to stop SDK callers bypassing the identity primitive:

// MCP-servers/Vodou-Console/src/index.ts, POST /chat
const turnPrincipalFromBody = req.body?.principal === 'guest' ? 'guest' : 'owner';

Zuplo’s multi-tenant gateway guide is blunt about this: never trust client-supplied tenant IDs, derive identity from the authenticated key. Correct, and it does not cover the case where there is no key. A loopback gateway with no auth has nothing to derive from, so the identity lands in the body because the body is the only channel that exists. The fix is not “read a header instead”, it is that the process holding the identity has to sign it.

Config that declares a limit, code that owns it, and nothing that joins them

The class: a declared limit and its enforcement point drift the moment they are two separate literals, and they always are. You see it in LLM context allocators (budgets.per_step_tokens in a YAML versus the slice() in the truncator), in multi-tenant RAG (top_k per tenant in config versus the LIMIT the query builder writes), and in API gateways (the rate limit in the policy doc versus the counter in Redis).

The standard advice, stated well in How to Put a Hard Spending Cap on an AI Agent, is that the cap must live one layer down from the agent and be synchronous. Mine was. The engine enforced --max-chars before a single byte came back. That advice covers where the cap fires. It does not cover the case where the number you enforce and the number you publish are different literals, which is how I ended up correct at the enforcement point and wrong in every place a human went to read the limit.

The invariant, checkable against any codebase: every declared limit must have a runtime consumer that reads the declaration and can fail because of it; if the only readers are a schema validator and a person, the declaration is a comment with syntax highlighting.

Five minutes on your own stack. First, find out whether anything reads your config at all:

# every number your config declares as a limit
grep -rEoh '(budget|limit|quota|max_[a-z_]+)[^0-9]*[0-9_]{3,}' config/ | sort -u

# every literal of that magnitude in the code that truncates or rejects
grep -rEn '[0-9]{3,}(_[0-9]{3})?' src/ | grep -iE 'slice|trunc|budget|limit|reject'

# the join: who actually loads the config key at runtime?
grep -rn "budgets\[" src/ ; grep -rn "config.get('budget" src/

Passing looks like one loader line per declared number, inside a path that can throw or cut. Failing looks like mine: the third command returns your schema validator and your tests, and the first two return different numbers.

Then the runtime half, against whatever table records what you actually sent:

SELECT s.lane, b.declared, MAX(s.tokens) AS observed,
       MAX(s.tokens) * 100 / b.declared AS pct
FROM spans s JOIN budgets b USING (lane)
WHERE s.kind = 'inject'
GROUP BY 1, 2 HAVING pct > 100;

Zero rows passes. Rows fail. But watch for the third outcome, which is the one you will probably get: zero rows because the join matched nothing. That means your config keys and your telemetry names are two vocabularies, and you cannot compare them yet. That is not a milder version of the bug. It is the reason the bug survived.

What changed: the registry stanza now fails a test instead of informing a reader

I fixed the number to 12,000, added a units field, and wrote the test that loads the registry and asserts every numeric budget has a live consumer. The test failed on nine lanes the first time, which is the correct result and the reason to write it.

If a number in your config has no code path that can fail because of it, delete it or wire it. Leaving it there is worse than having no limit, because the next person will read it and believe you.