All docs

Docs · Reference

Allowances

a tenant's own fuel budgets

Reference for txco://allowance/* and txco allowance: how a tenant gives part of its work a fuel budget of its own, and what happens when that budget is spent.

A tenant pays for its fuel as one budget. An allowance is a budget the tenant carves out of it: so much fuel per UTC hour, day or month, for whatever unit the tenant cares about — a customer, an agent, a team, a feature. A stack puts a request in an allowance; from then on that request’s fuel counts against the allowance and the tenant. When the allowance’s window is spent, its requests are refused until the window resets, while the rest of the tenant keeps running. When the tenant’s own budget is spent, everything stops, allowance or not: whichever runs out first refuses.

The tenant defines and manages its allowances; the chassis only enforces them.

Defining one

An allowance is {name, fuel, per}:

FieldMeaning
name1–64 of a-z 0-9 . _ -, starting with a letter or digit. The tenant’s own label.
fuelThe limit per window, in fuel (positive).
perhour, day (default) or month — fixed UTC windows.

From a stack:

EXEC "txco://allowance/set"
  WITH name = "pony-scout", fuel = 2500000, per = "day"

Or from the CLI, against the tenant your profile signs for:

txco allowance set pony-scout --fuel 2500000 --per day
txco allowance list
txco allowance get pony-scout
txco allowance delete pony-scout

Setting an allowance again replaces it and keeps its counters, so raising the limit mid-window frees what is left of the window at once. Deleting one removes the definition; its counters expire with their windows.

Entering one

# its own scope, before the work it pays for
WHEN @src == "http" && @web.req.url.path == "/work"
  EXEC "txco://allowance/enter"
    WITH name = ._req.customer,
         into = "_al"
  • Write-once. A request enters at most one allowance. Entering another is an op error; entering the same one again just re-checks it. Nothing downstream — a later rule, a goto into another stack, a continuation — can move a request out of the allowance it entered.
  • The refusal comes at the next scope entry. If the window is spent, enter answers {denied: true} and the request ends at its next budget check: the next scope entry, or the next pass of a LOOP. Rules in the same scope as enter still run, so give enter a scope of its own, before the work.
  • A ceiling for the request. If the window has fuel left, the request’s fuel ceiling drops to what it had already burned plus what the window has left (never above --max-fuel-per-request). One runaway request therefore stops near the allowance’s limit instead of at the chassis-wide cap, and it is refused the same way.
  • No definition, no limit. Entering a name that was never set is metered — used counts, by day — but never refused. A tenant can measure first and set limits later.
  • An outage admits. If the KV store can’t be read, the request still enters, metered but unchecked (checked: false). The tenant’s own budget still applies; an allowance outage does not take the tenant down with it.

enter writes the window’s state at into (default _allowance):

{
  "name": "pony-scout",
  "defined": true,
  "per": "day",
  "used": 1830000,
  "fuel": 2500000,
  "remaining": 670000,
  "resets_at": "2026-10-08T00:00:00Z",
  "entered": true,
  "denied": false,
  "checked": true
}

The request carries the allowance as @allowance (_txc.allowance: name, fuel, resets_at, and cap, the ceiling it set). Rules can read it; no rule can write it.

txco://allowance/get reads the same object without entering (WITH name, or none for the allowance the request already entered). Use it to degrade gracefully before entering — e.g. answer “I’ve used today’s budget” instead of being refused. txco://allowance/list pages through the definitions (after, limit).

The refusal

A spent allowance is refused through the same admission marker as a tenant over its rate limit, so every head renders it in its own protocol:

HeadAnswer
web429 Too Many Requests, Retry-After: <seconds to the window's end>, x-txc-deny-reason: allowance_exhausted
LMTP451 temporary failure — the sender’s server retries the mail after the window
othersthe head’s own rendering of an admission denial

The run’s final payload carries the error:

{
  "code": "txco_allowance_exhausted",
  "allowance": "pony-scout",
  "fuel": 2500000,
  "resets_at": "2026-10-08T00:00:00Z",
  "fuel_used": 145,
  "last_stage": "core/0110"
}

Unlike other admission denials, this one comes after the stack ran: the fuel the request burned before entering is real usage and is billed, to the tenant and the allowance.

How it is counted

  • Fuel is charged from the usage path: the request’s own line, resumed continuation segments, and the heartbeats of an attachment the request opened. Every billable usage event under an allowance adds its fuel to that allowance’s current window. With --usage-enabled=false nothing is charged.
  • Charges are summed in memory and written to the KV once a second per allowance, so the counter trails the requests that just finished by about a second.
  • Overshoot. A request checks the window when it enters and is charged when it ends. Requests already running when the window crosses its limit finish (each within its own ceiling), so a busy allowance can end a window somewhat over its fuel. The next request after that is refused.
  • Allowances and their counters live in the tenant’s KV, under chassis- reserved namespaces (_txc.allowance, _txc.allowance.used). On a fleet, point --kvstore at shared storage (redis) so every node counts against the same windows. txco://kv/* cannot read or write them.

Permissions

txco allowance uses the signed admin API (/v1/tenants/{tenant}/allowances[/{name}]): kv:*:read to list and get, kv:*:write to set and delete. Tenant owners have both.

The txco://allowance/* ops are open to every rule in the tenant’s stacks: the tenant’s rules are the tenant, and they only ever see their own tenant’s allowances.

Edit this page · View as markdown