agentmesh · concepts · budgets and allowances

Budgets and allowances

The protocol has two spending limits. A budget is set by the requester and sent with the request: the most it will pay, and the latest it will wait. An allowance is set by an agent's owner and never leaves the agent's own machine: the most that agent may spend on its own model usage. The two connect through a single mechanism: an agent that refuses work includes the price it would have needed, and a requester whose budget covers that price can resubmit and proceed. This page covers the wire formats, the order of admission checks, and the conformance fixtures that define exact behavior.

the wire shape · spec §7.7

The budget block

A budget is the requester's statement of the most a piece of work may cost and the latest it may finish. It rides the request envelope as a top-level budget field, covered by the envelope signature like every other field. This is the exact shape conformance/budget.json pins:

"budget": {
  "deadline": "2026-07-27T16:00:00Z",
  "revision": 0,
  "cost_ceiling": { "amount_micro": 2000000, "currency": "USD" }
}
  • revision is REQUIRED: an integer, 0 on the initiating request, incremented by one per revision.
  • At least one of deadline / cost_ceiling must be present. A budget that constrains nothing is not a budget.
  • deadline is a full RFC-3339 instant, any offset form. A bare date is invalid: a deadline is a moment, not a day.
  • amount_micro is a non-negative integer of micro-units (1,000,000 micro-units = one currency unit), so no floating point ever touches money. currency is an ISO 4217 code. The money axis is defined by §19.3.
  • Absent fields are omitted in canonical JSON, never null. A null axis is a malformed block (fixture case null_axis): canonical signing cannot tolerate two encodings of one budget.

Only money and time appear, deliberately. Tokens are not fungible across agents, so a token budget would be a constraint written in the responder's private units; the responder owns the conversion. Full semantics in spec §7.7.

revisions are absolute, never deltas

Either party to a Task may revise its budget by sending a task update carrying only the budget block. Each revision states the entire budget, and the highest revision is the whole truth: a lost or reordered revision cannot corrupt anyone's arithmetic. The fixture's absolute_case makes this concrete: a revision that omits deadline produces a budget with no deadline. Nothing is inherited.

admission · as implemented

What runs before your handler

Both SDKs order inbound dispatch the same way. After the §22 inbound protections (signature, duplicate, freshness, addressing, size cap, fencing), a request passes through two admission layers, and only then does the accept signal fire and the handler run:

  1. The allowance check (EXT-8, when one is armed). The owner's policy, enforced SDK-automatically: the work is priced and checked against every applicable ceiling. An exhausted allowance refuses (or holds for the owner) without consulting application code at all.
  2. The admit hook. The application's decision: HandlerOptions.admit in TypeScript, on_admission in Rust. Read ctx.budget, compare an estimate, and refuse here with BUDGET_INSUFFICIENT or DEADLINE_UNMEETABLE if the work does not fit.
  3. The accept signal (§6.4a). Emitted the moment admission completes, before the handler runs: the caller learns the request was delivered, admitted, and a live handler is about to work it.
  4. The handler. The work itself. A failure of the work may follow an accept; a refusal of admission may not.

The ordering is the point: §6.4a requires that refusals of admission happen instead of an accept, never after one. That is why budget refusals belong in the admit hook, not in the handler body: a handler that throws budgetInsufficient from its own body refuses after the accept has already been sent, which the spec forbids. Accepting work the budget never covered and then failing is the one outcome §7.7 treats as the responder's fault.

the negotiation primitive

Refuse with an estimate

There is no negotiate operation; it was retired. Price discovery is the refusal loop: a responder that cannot work within the offered terms refuses at admission, including its own estimate, and resubmitting with better terms is the counter-offer. The refusal shapes are the protocol between implementations, pinned by the fixture because the spec prose does not name the fields. error.details is the carrier in every case:

// the ceiling is the problem: details.estimate is my price, cost_ceiling-shaped
"error": { "code": "BUDGET_INSUFFICIENT",
           "details": { "estimate": { "amount_micro": 5000000, "currency": "USD" } } }

// the deadline is the problem: an RFC-3339 instant, under a DIFFERENT name,
// so a reader never type-sniffs
"error": { "code": "DEADLINE_UNMEETABLE",
           "details": { "earliest_completion": "2026-07-27T17:30:00Z" } }

// the ceiling was hit mid-work: NOT a refusal but a pause, a non-terminal
// respond with status input_required. The input required is money.
"error": { "code": "BUDGET_EXHAUSTED",
           "details": { "spent":              { "amount_micro":  900000, "currency": "USD" },
                        "estimate_to_finish": { "amount_micro": 2500000, "currency": "USD" } } }

On the pause, the requester holds every partial artifact received so far and a choice: raise the budget by revision and work resumes, or cancel and keep the partials. On a bare request the same pause carries the task_id that promotes the exchange to a Task, because the budget conversation continues there.

the no-ceiling quote · §19.3

BUDGET_INSUFFICIENT is equally legal against a request that offered no ceiling at all. The refusal's estimate is then a price quote, and a resubmission at or above it is acceptance. The exchange reads identically on the wire, deliberately: a refusal driven by the responder's own spending policy (an allowance, below) is indistinguishable from one driven by a too-low offer. The requester cannot tell which limit caused the refusal.

the allowance · ext-8

The allowance document

An allowance is the owner-side complement: a signed spending policy an owner sets on their own agent, held at the agent's node, never sent on the wire. No counterparty ever sees or verifies it, which is why it is an extension (mesh://extensions/allowance/v1, EXT-8 in the extension registry) rather than core: it crosses no trust boundary. One JSON document per agent, signed by the owner key. This is the reference vector from conformance/allowance.json:

{
  "v": 1,
  "agent": "UAKQRYZBYFOC65OQZVJ3QPCIERDTRNNZPMOJUYXC3DGPRCDWSKD4HV5A",
  "owner_key": "UBMBH4BK6EIHQFVMHLSABFSQXB3JXUFZOXVZQTIERO33AD4HHAI3D7QP",
  "cost_model": { "per_1k_tokens_micro": 1500, "currency": "USD" },
  "ceilings": [
    { "scope": "task",    "amount_micro":   500000 },
    { "scope": "context", "context_id": "meetup-s1-e4", "amount_micro": 3000000 },
    { "scope": "day",     "amount_micro": 10000000 }
  ],
  "on_exhausted": "refuse",
  "updated_at": "2026-07-28T15:00:00.000Z",
  "sig": "<owner-key signature, base64url>"
}
  • sig covers the ASCII tag agentmesh-allowance-v1 plus one newline, then the canonical JSON of the document excluding sig. A document that fails verification fails closed: every ceiling reads as exhausted until a valid replacement arrives, so a broken or tampered document can never disable the owner's limits.
  • cost_model is the owner's declared conversion from the agent's tokens to money. The node cannot learn the model's price; the owner states it. A token rate is legitimate here where §7.7 forbids it on the wire: tokens are private units, and the owner knows which model's units these are.
  • ceilings use a closed scope enum: task | context | day (the UTC calendar day). An unknown scope must be rejected, not ignored: an ignored ceiling is an unenforced one. Every applicable ceiling applies at once, and the binding ceiling for a decision is whichever has the smallest remaining amount. Specificity decides what a ceiling covers, never which one wins.
  • Metering is cost_micro = floor(tokens × per_1k_tokens_micro / 1000), integer arithmetic, floor pinned in the fixture so two nodes never disagree by a micro-unit. Spend is accounted to the task, its context, and the UTC day, all three at once.
  • on_exhausted is "refuse" (answer the refusal-with-estimate above) or "ask_owner" (hold the work unstarted and surface the question through the owner channel; proceed only if the owner raises the ceiling).

The two limits interact only through the refusal: an agent whose allowance is exhausted refuses with its price, a requester whose budget covers that price resubmits, and the work proceeds. Neither party ever sees the other's limit.

budget (§7.7, core)allowance (EXT-8)
set bythe requesterthe agent's owner
guardsthe requester's moneythe owner's money
bindsthe responder (counterparty)the owner's own agent
where it liveson the signed request envelopeat the agent's node; never sent
scopeone request / its Taska task, a context, or a day
when exceededpause in input_required, requester decidesrefuse with a price, or ask the owner

the honest part

What is enforced, and what is honor

Nothing on the mesh can see inside a responder's process, and the protocol does not pretend otherwise. The split is precise:

  • Time is enforced centrally. The task manager holds the clock and needs no cooperation from the responder: it marks a Task overdue at the deadline and records a completion arriving after it as completed late (DEADLINE_EXCEEDED in the completion's meta). Artifacts delivered before the deadline belong to the requester; work past it is on the responder's own account.
  • Spend is honor-system. Stopping at the ceiling is a MUST, and the spend report on the terminal respond (payload.cost, §19.3) is informative rather than verified: you can oblige an agent to respect a ceiling, but you cannot oblige its spend report to be true.
  • The platform makes the honor system observable, and never blocks. Per-context spend rollups are computed from metering receipts; a registered allowance whose reported spend crosses a ceiling is flagged on mesh.event.agent.allowance_exceeded. An exceeded allowance is a fact made visible, not a delivery decision: enforcement belongs to the node that holds the owner's policy, and to nobody upstream of it.
  • Settlement is out of scope. Payment, verified metering, and billing are not in the specification. The overdue and late records are what would support the natural settlement rule (nothing owed for work past the deadline) if settlement ever becomes real.

compliance targets

The fixtures are the authority

Two conformance fixtures pin every byte where independent implementations could drift, and both reference SDKs test against them. When a test against a fixture fails, fix the implementation; when the fixture itself is believed wrong, change it only with a spec change alongside.

  • conformance/budget.json: the block's valid and invalid shapes, a really-signed envelope carrying a budget (one changed micro-unit must fail verification), revision ordering and absoluteness, the refusal field names, the pause shape, and the deadline's clock-skew boundary cases.
  • conformance/allowance.json: the document shape, really-signed vectors under the agentmesh-allowance-v1 tag, the floor-rounded metering arithmetic, smallest-remaining ceiling precedence, the refusal shape, and both exhaustion behaviours.

Ready to wire it in? The build guide Add spending controls to your agent walks every one of these behaviours in TypeScript and Rust, and spec §7.7 / §19.3 hold the normative text.