All docs
Docs · Deploy & operate
Fuel and TTL
loop and cost budgets
Operator reference for the per-request budget guards: how the chassis stops runaway loops and expensive work, and how to read the errors when it does.
Every request carries two budgets, working as a pair:
| Guard | Envelope field | Question it answers | Exhaustion error |
|---|---|---|---|
| TTL | _txc.ttl | “Is this a loop?” | txcl_scope_ttl_exhausted |
| Fuel | _txc.fuel_used | “Is this expensive work?” | txco_fuel_exhausted |
The two codes are deliberately distinct so an operator can tell a loop
from costly-but-legitimate work at a glance. Implementation: chassis/processor/budget.go.
TTL — the hop counter
_txc.ttl is a countdown, decremented once per stage entry (every
scope advance, @goto, or stage-jump EXEC). It starts at --op-scope-ttl-max (default 500); 0 disables the guard.
A rule may voluntarily lower its remaining budget — EMIT @ttl = 20 before entering a polling loop — but can never raise
it (the IP-TTL idiom: writes are clamped to the current value). Use
this to give a known-risky subflow a tight sub-budget without touching
the chassis-wide cap.
Fuel — the work meter
_txc.fuel_used counts up against --max-fuel-per-request (default 100000; 0 = unlimited). Costs are weighted per action:
| Action | Fuel |
|---|---|
| Entering a scope | 10 |
EXEC dispatch | 25 |
| Nano-op compute, per ms | 10 |
| Secret materialization | 100 |
| Repeated stage transition | 50 |
Calibration: 1 fuel ≈ 100 µs of typical chassis work. So a 1 ms nano-op costs 35 total (25 dispatch + 10 compute); an op wrapping a 1-second LLM call costs ~10,000 — meaning the default cap tolerates roughly ten such calls per request before cutting off.
The final fuel value is logged on the per-request usage line
(fuel=N) — single-tenant deployments can ignore it; tenant-aware
deployments aggregate it for quota or billing.
Repeat transitions: backpressure before the kill
The chassis keeps a per-request seen-set of stage transitions
("from->to", carried as _txc._seen). The first time a transition
happens it’s free; every repeat charges 50 fuel and sleeps --op-repeat-penalty-ms (default 20, 0 disables) before
proceeding. A tight loop therefore degrades gracefully — it slows
down, burns fuel measurably, and shows up in traces — rather than
spinning the CPU until the hard cap lands.
Envelope mechanics
All three fields (_txc.fuel_used, _txc.ttl, _txc._seen) ride the
envelope, so budgets propagate through @goto, EXEC stage jumps,
continuations, and deferred-join fan-out the same way _txc.tenant does — a flow can’t shed its budget by jumping stacks. They are
stripped from the response before it reaches the inlet client (after
the fuel value is captured for usage accounting).
Reading an exhaustion
Both errors return a structured JSON payload as the request’s final response, including the stage where the request gave up and the last three transitions — usually enough to spot the cycle without opening a trace:
{
"code": "txco_fuel_exhausted",
"max_fuel": 100000,
"fuel_used": 100025,
"last_stage": "billing/0",
"last_transitions": [
"retry/0->billing/0",
"billing/0->retry/0",
"retry/0->billing/0"
]
} {
"code": "txcl_scope_ttl_exhausted",
"max_ttl": 500,
"consumed": 500,
"last_stage": "poll/0",
"last_transitions": [
"poll/0->poll/0",
"poll/0->poll/0",
"poll/0->poll/0"
]
} For the full step-by-step picture, pull the trace for that rid.
Apply-time lint: catching typos before runtime
The runtime guards always catch loops eventually; txco apply also
lints the assembled stack for the unambiguous mistakes
(chassis/cli/loop_lint.go):
- Unconditional self-loop — a rule with no
WHENand noEMIT @haltthat@gotos orEXECs back into its own(stack, scope). Usually a typo:@goto = "self/0"meant as"self/1". - Unconditional 2-stack ping-pong — stage A unconditionally points to B, and B unconditionally back to A.
Warnings only (printed to stderr; apply proceeds). The lint is
deliberately conservative: conditional state-machine loops and
intentional polling pass unflagged — those are the runtime guards’ job.
Flags
| Flag | Default | Meaning |
|---|---|---|
--max-fuel-per-request | 100000 | Fuel cap; 0 = unlimited |
--op-scope-ttl-max | 500 | Starting hop budget; 0 = disabled |
--op-repeat-penalty-ms | 20 | Sleep per repeated transition; 0 = off |