An effect under a budget is a linear resource — no unbudgeted emission (v2.28.0)
A rate limiter bolted on beside a program — a Redis token bucket in
middleware, a Sidekiq throttle, an Airflow pool — is invisible to the
type system and to the program's own reasoning. The program believes
it can call a tool; an external counter, somewhere else, sometimes says
no. v2.28.0 moves that contract INTO the language: an effect under a
budget is a linear resource the compiler sees and the runtime
co-enforces.
The law. An external effect declared under a
budget { … on Tool(X) }is a linear resource. The runtime may not emit it without consuming a token; over-emission is impossible by construction. Token present ⇒ proceed (consume); token absent ⇒block/defer/shedperon_exhausted— never an emission over budget.
Why it is linear (the affine discipline, refilling)
The lease/reconcile kernel already implements affine resources for
intra-flow handles: a LeaseToken is single-use and decays over τ —
acquire it, and it is yours until it expires, then it is gone. v2.28.0
generalizes that token into a RateLease for external effects:
- A
rate:quota is a token bucket of capacitylimitthat refillslimittokens per period (continuously,limit/periodper second). It permits a burst up tolimit, then a steady rate. Refilling, but bounded — never more thanlimitin flight. - A
max:quota is a fixed window: at mostlimitconsumptions per period, the counter resetting only when the window rolls. A hard cap, no intra-window refill.
Both keep the linearity invariant the affine token has: a consumed token is gone until it is refilled. The difference from the lease token is only the refill — single-use becomes N-use — not the discipline. This is the same Logic-pillar affine algebra, applied at the boundary where the program touches the world instead of an internal handle.
Why it is deterministic
A budget decision is a pure function. The bucket's available tokens are
min(capacity, prior + elapsed × rate); the window's availability is
limit − consumed, rolling when now − window_start ≥ period. Refill is
LAZY — computed from the elapsed wall-clock at each acquisition — so the
verdict never depends on a background tick's granularity. Given the same
(bucket state, now), acquire yields the same grant/deny, and the same
post-state, bit-for-bit. Every acquire/deny is therefore replayable and
auditable: an over-budget call did not "probably" fail — it provably
never emitted.
The exhaustion policies — all honest
When a token is absent, the daemon's on_exhausted decides, from a
closed catalog:
block(the fail-closed default) — the step fails with the typedEffectQuotaExhausted(axon-E0810). The call is not emitted.defer— the tick reschedules to the next instant a token frees up (EffectDeferred,axon-E0811), reusing the v2.27.0 coalesced defer ledger. The work is preserved, not dropped, and not over-emitted.shed— best-effort: the call is skipped, the flow continues, and the skip is audited (effect:shed) — never silent.
None of these emits over budget. They differ only in what happens to the rest of the work, not in whether the budget holds.
What this forbids
- No unbudgeted emission. A tool under a
budgetcannot be called without a token. There is no path in the dispatcher that emits the effect while bypassing the gate. - No advisory-only limiting. The budget is not a counter the program may consult and ignore; the dispatch gate consumes-or-denies before the effect, so the limit is structural, not a suggestion.
- No silent overshoot under a single replica. The in-process kernel is exact. (The enterprise multi-replica binding is honest about its bound — at-most-N with a small overshoot window, fail-open on a Redis error — and says so; it does not claim distributed exactly-once it cannot deliver.)
Relation to the other laws
- Generalizes the affine discipline of the lease kernel from intra-flow handles to external effects — the same linearity, now at the boundary.
- The effect-rate sibling of
time_is_an_explicit_input: that law makes when an effect runs a pure function of recorded inputs; this one makes how often it may run a linear resource. Adaemonthat declares both awindowand abudgethas its timing AND its rate co-enforced, deterministically. - Carries the spirit of
no_unwitnessed_advantage: there, no claim without a machine-checkable witness; here, no emission without a consumed token — and the v2.28.0EffectBudgetedproof makes the budget's soundness an independently-verifiable object.
The honest test: if your rate limit lives beside the program and the program cannot reason about it, your effects are not linear — they are hopefully-bounded. AXON makes the budget a resource the program holds and the runtime cannot overspend.