A ceiling in a conditional is one someone deletes
A budget expressed as a value can be read, derived from and audited. Buried in a conditional it can only be obeyed or removed, and it gets removed.
An autonomous run is allowed to stop because it has spent its budget. It is not allowed to stop because the session feels long.
Those two look identical from outside the run, and the only thing that separates them is whether a number was declared somewhere a second reader can find it.
The case this post is about
One field:
# idea-layer/idea-plan.yaml
agent:
max_token_budget: 500000
judge_budget_usd: 2.50
Twelve places in this framework read max_token_budget, and they do not read it to compare against
it. They each derive something different from it:
halt class budget-exhausted the value itself
stale-regen preflight guard value x 0.4
interactive design generation value x 0.3
A ceiling written as if (spent > 400_000) halt() cannot be read by any of those. It can only be
obeyed by the function it sits in.
The symptom
The first shape is the one that made this a rule. An autonomous drive stopped and said the session was getting deep. That is a legitimate-sounding reason and it is unfalsifiable — there is no number to check it against, so a reviewer cannot tell a run that correctly protected a budget from one that got tired two thirds of the way through a cascade.
The second shape is what happens to a ceiling nobody can read. It gets deleted. A conditional
threshold is inconvenient exactly once — the first time a legitimate run hits it — and the cheapest
way past an inconvenient if is to remove it or widen the number in place. The change is one line,
it looks like tuning, and nothing records that a limit used to exist.
The third is the opposite failure and it is subtler: a budget that exists in three places and disagrees with itself. Before this was one declared field, the token ceiling appeared in the main drive loop, in a regeneration guard, and in a design-generation step, each with its own literal. The three were set at different times by different people. There was no version of "the budget" to report; there were three budgets, and which one bound you depended on which step you were in.
The fourth is that none of this shows up in a passing run. Every run under all three ceilings behaves identically whether the ceilings agree or not, so the divergence is discovered by a run that should have been allowed to finish and was not — at which point the diagnosis requires reading three files to find out which number stopped it.
Why the obvious fix failed
Pick one literal and use it everywhere. The first attempt and it decays on contact with the second reader. The regeneration guard does not want the full budget — it wants to refuse to start a regeneration whose projected spend would consume most of what is left. So it needs a fraction, and a shared literal means the fraction is computed at the call site from a number that is also written at the call site. Two literals again, one of them derived, and nothing says they are related.
Put it in an environment variable. Better, and it fails the audit requirement. An environment variable is set by whoever launched the process, so "what was this run's budget" is answerable only if you know how it was launched. A run that is over-budget and a run launched with no budget at all are indistinguishable after the fact, which is exactly the distinction the halt class depends on.
A constant in a shared module. This is genuinely close and it is still code. Changing it is a code change, which sounds like a virtue — it appears in a diff — and in practice means the budget for a project lives in the framework's source, so one project cannot have a different ceiling without a conditional, and we are back to a conditional.
Make the limit advisory — warn and continue. The version that feels responsible. It removes the only thing a budget is for. A ceiling that warns is a ceiling that is crossed, and the warning lands in a log alongside the run that crossed it.
The actual mechanism
Declare the budget as a value in the project's own configuration, and make every consumer read it rather than hold a copy.
Three consequences, and the third is the one I did not anticipate.
The absence of the value is itself meaningful. If no budget was declared, "budget exhausted" is not a reason that can be given. That is not a default of infinity dressed up — it is a distinct state, and the halt adjudicator has to treat it as one. A run that stops citing a budget nobody set is making a claim about a fact that does not exist.
Derivation belongs to the consumer, not the declaration. The regeneration guard's × 0.4 and
the design step's × 0.3 are properties of those steps, not of the budget. Putting them in the
config would mean three fields that have to be kept consistent; leaving them at the call site means
one field and three readers, each of which can say what fraction it used and why.
One engine, enforced. The thing I got wrong was adding a second budget for a second kind of spend — a dollar ceiling for model-judged evaluations alongside the token ceiling. That is legitimate; what was not legitimate was the second ceiling growing its own tracking, its own exhaustion state and its own halt path. The schema now carries an explicit note that the judge budget is a mirror of the declared field and that there is no second budget engine, because two engines means two answers to "is this run bounded".
The fix
The budget is read once, and what consumers get is an object that can answer questions rather than a number they compare against:
type BudgetDecl = { maxTokens?: number; judgeUsd?: number };
class Budget {
private spentTokens = 0;
private spentUsd = 0;
constructor(private decl: BudgetDecl) {}
// ABSENCE IS A STATE. `null` is not infinity with a friendlier name — it is the
// fact that nobody declared a ceiling, which is what makes a budget-exhausted
// claim checkable rather than a mood.
get total(): number | null { return this.decl.maxTokens ?? null; }
spend(tokens: number, usd = 0) { this.spentTokens += tokens; this.spentUsd += usd; }
spent() { return this.spentTokens; }
remaining(): number { return this.total === null ? Infinity : Math.max(0, this.total - this.spentTokens); }
// Consumers derive THEIR OWN fraction and say so. The 0.4 belongs to the
// regeneration guard; putting it in the config would make it a fourth thing to
// keep consistent with the other three.
fractionRemaining(): number | null {
return this.total === null ? null : this.remaining() / this.total;
}
wouldExceed(projected: number, fraction = 1.0): { blocked: boolean; why: string } {
if (this.total === null) {
return { blocked: false, why: "no budget declared — nothing to exceed" };
}
const ceiling = this.total * fraction;
return this.spentTokens + projected > ceiling
? { blocked: true, why: `projected ${this.spentTokens + projected} > ${ceiling} (${fraction}x of ${this.total})` }
: { blocked: false, why: `projected ${this.spentTokens + projected} within ${ceiling}` };
}
// The ONLY place a budget-exhausted halt is legitimate, and it refuses to issue
// one when no number exists to be exhausted.
exhausted(): { halt: boolean; reason: string } {
if (this.total === null) {
return { halt: false, reason: "no budget declared — 'budget-exhausted' is not an available halt class" };
}
return this.spentTokens >= this.total
? { halt: true, reason: `budget-exhausted: ${this.spentTokens}/${this.total}` }
: { halt: false, reason: `${this.spentTokens}/${this.total} spent, ${this.remaining()} remaining` };
}
// Readable off the run, which is the whole argument for a value over a conditional.
report() {
return this.total === null
? `unbounded (no max_token_budget declared) · spent ${this.spentTokens}`
: `bounded ${this.total} · spent ${this.spentTokens} (${Math.round(this.spentTokens * 100 / this.total)}%)` +
(this.decl.judgeUsd !== undefined ? ` · judge $${this.spentUsd.toFixed(2)}/$${this.decl.judgeUsd.toFixed(2)}` : "");
}
}
The three readers from the top of this post, against one declared budget:
const b = new Budget({ maxTokens: 500_000, judgeUsd: 2.50 });
b.spend(310_000, 1.80);
console.log(b.report());
console.log("stale-regen guard :", b.wouldExceed(120_000, 0.4)); // its own fraction
console.log("design generation :", b.wouldExceed(40_000, 0.3));
console.log("halt adjudicator :", b.exhausted());
bounded 500000 · spent 310000 (62%) · judge $1.80/$2.50
stale-regen guard : { blocked: true, why: "projected 430000 > 200000 (0.4x of 500000)" }
design generation : { blocked: true, why: "projected 350000 > 150000 (0.3x of 500000)" }
halt adjudicator : { halt: false, reason: "310000/500000 spent, 190000 remaining" }
Three different answers from one number, and note that the two guards refuse while the drive continues — which is correct and is not expressible with a single threshold. And the case the rule exists for:
const u = new Budget({}); // no budget declared
u.spend(310_000);
console.log(u.report());
console.log(u.exhausted());
console.log(u.wouldExceed(120_000, 0.4));
unbounded (no max_token_budget declared) · spent 310000
{ halt: false, reason: "no budget declared — 'budget-exhausted' is not an available halt class" }
{ blocked: false, why: "no budget declared — nothing to exceed" }
That second line is the one that turns a mood into a refusal. A run in this state that stops and says it has spent its budget is making a claim the object will not corroborate, and the halt adjudicator rejects it on that basis rather than on anybody's judgement about whether the session felt long.
How we proved it
Twelve call sites read the declared field. A grep across the rules, runtimes, schemas and
templates finds max_token_budget referenced in the halt-class enumeration, the halt adjudicator,
the stale-regeneration guard at × 0.4, the interactive design step at × 0.3, the plan schema,
the agent context prompt, the matrix renderer, and an auto-heal recipe. Every one of those is a
reader. None of them holds a number.
The judge budget carries an explicit single-engine note in the schema:
judge_budget_usd: { type: number, required: true } # mirror of agent.judge_budget_usd
# (NO second budget engine)
That comment is load-bearing and it is there because I built the second engine first. Two trackers meant a run could be over on one and under the other, with two different exhaustion states and no single answer to whether it was bounded.
What is reasoned rather than measured is the deletion claim. I am asserting that a conditional ceiling gets widened or removed the first time it is inconvenient, and my evidence for that is the three divergent literals that existed before this was one field — which is evidence of drift, not of deletion. I have not watched somebody delete one.
What I have not solved: nothing verifies that a consumer's derived fraction is sensible. The
× 0.4 and × 0.3 are at their call sites, which is where they belong, and a fourth consumer could
write × 1.5 and no check would object. Making the fractions declarable would create the
consistency problem the design exists to avoid, so the current state is deliberate and genuinely
unguarded.
And a limit on the whole approach: a declared budget bounds a run that is counting. Spend that
happens outside the counted path — a subprocess, an external API billed elsewhere — is invisible to
this object, so bounded 500000 · spent 310000 is a claim about the tokens this run knows about
rather than about everything the run caused to be spent.
What this generalizes to
A limit expressed as a value can be read, derived from, reported and audited. The same limit expressed as a conditional can only be obeyed by the function containing it — and when it becomes inconvenient, edited out of that function by whoever is inconvenienced.
The tell that a limit is in the wrong form is that you cannot answer "was this run bounded, and by how much" without reading the code that ran. If the answer requires a code read, the limit is a behaviour rather than a fact, and behaviours do not survive the first legitimate case that trips them.
One post a week, no digest
New writing when it lands. No roundups, no course pitch, no "10 tips".