BUDGET_EXCEEDED
A per-request work budget was exceeded — a compile that would grow past the compiler's bounds, the solver's time or memory running out, or an oversized single value. HTTP 413. Split the assertion or bind repeated work with let.
The request would cost more work than one call may. The budgets bound work, not size: there is no limit on a ruleset's length, its number of rules or its number of claims. The compiler's budgets refuse a compile the moment it would cross them, so a pathological ruleset costs a structured 413 rather than a tied-up worker. The solver's own budgets can only be spent: the work stops the moment one runs out, and the request gets this error rather than a partial answer.
The envelope
{"ok": false,
"error": {"code": "BUDGET_EXCEEDED",
"message": "unroll_product budget exceeded: limit 10000, submitted 1000000",
"docs": "https://docs.agents.jaxon.ai/errors/budget-exceeded.md",
"budget": "unroll_product", "limit": 10000, "actual": 1000000},
"versions": {"...": "..."}}
budget names which limit and limit and actual say by how much.
Two budgets are the compiler's, and a small source can trip them:
graph_nodesis how many nodes one assertion may compile into. A function that calls another twice doubles at every level of nesting, and every call is inlined; ten levels is a thousand copies of the innermost body.unroll_productis the product of the domain sizes of nested quantifiers (ForAll,Exists,CountWhere, ...) in one assertion. Three nested quantifiers over a hundred-member set is a million copies of the predicate.
Two budgets are the solver's own:
solver_timeout_msis the solver time one compile or one check may use, in total. Time spent waiting for the solver does not count; that is [ENGINE_BUSY](engine-busy.md).actualis the time used when the work stopped.solver_memory_mbis the solver's memory. It has noactual: the solver stops at the limit rather than measuring how far past it the request would have gone.
Both are set by the ruleset and the claims rather than by load, so retrying the same call is expected to fail the same way.
Two budgets bound single caller strings, which no policy needs to be long:
claim_value_chars:<claim>is the length of one claim value.approverandnoteon an approval are persisted, so each has a length limit of its own.
The fix
- Split the assertion, or bind an intermediate value with
letso it is computed once rather than inlined at every use. This is the fix for both compiler budgets. - Simplify an assertion that is slow to decide. A ruleset one person can review is usually one the solver decides quickly.
- Send the value, not the passage. A claim value is a number with its unit, a boolean or a vocabulary member.
The budgets are not client-tunable. If a legitimately sized ruleset trips one, that is a finding worth reporting to Jaxon rather than working around.
See also: Error codes.