Budgets & Metering
This guide walks through the practical side of chio's economic model: how to set budget limits on capability tokens, declare pricing on tool servers, read financial data from receipts, handle cost overruns, and monitor budget consumption over time. Enforcement happens in try_charge_cost(), which atomically checks all three budget tiers (max_invocations, max_cost_per_invocation, max_total_cost) in a single transaction.
Read Economics first
MonetaryAmount, the three-tier budget model, and the pre-charge/dispatch/reconcile cycle.Setting Budget Limits
Budget limits are set on individual grants within a capability token. Each grant targets a specific tool on a specific server and can carry any combination of the three budget fields: max_cost_per_invocation, max_total_cost, and max_invocations.
Invocation Limit Only
The simplest budget: limit how many times a tool can be called with no monetary cap. This is useful for free-tier tools where you want to prevent runaway loops.
grants:
- server_id: srv-search
tool_name: web_search
operations: [invoke]
max_invocations: 100
# No monetary limits, tool is freeMonetary Cap Only
Set an aggregate spending limit with no per-call cap. The agent can make expensive calls as long as the total stays under budget.
grants:
- server_id: srv-ai-inference
tool_name: generate_text
operations: [invoke]
max_total_cost:
units: 5000 # $50.00
currency: USDFull Three-Tier Budget
Combine all three limits for maximum control. This is the recommended configuration for production deployments.
grants:
- server_id: srv-ai-inference
tool_name: generate_text
operations: [invoke]
max_cost_per_invocation:
units: 100 # $1.00 per call
currency: USD
max_total_cost:
units: 5000 # $50.00 aggregate
currency: USD
max_invocations: 500Multiple Grants with Different Budgets
A single capability token can contain multiple grants, each with its own independent budget. This lets you give an agent access to several tools with different spending profiles.
grants:
- server_id: srv-ai-inference
tool_name: generate_text
operations: [invoke]
max_cost_per_invocation:
units: 100
currency: USD
max_total_cost:
units: 5000
currency: USD
max_invocations: 500
- server_id: srv-search
tool_name: web_search
operations: [invoke]
max_invocations: 200
# Free tool, no monetary limits
- server_id: srv-storage
tool_name: store_document
operations: [invoke]
max_cost_per_invocation:
units: 10 # $0.10 per store
currency: USD
max_total_cost:
units: 500 # $5.00 aggregate
currency: USDConfiguring Tool Pricing
Tool pricing is declared on the tool server side using NativeTool pricing helpers (per_invocation_price, flat_price, per_unit_price, hybrid_price). The kernel reads this pricing metadata from the tool manifest during economic checks.
Per-Invocation Pricing
Charge a fixed amount for each call. The billing unit is automatically set to "invocation".
NativeTool::new("greet", "Return a greeting", schema)
.per_invocation_price(25, "USD")
// Each call costs $0.25Flat Pricing
A single fixed price with no billing unit. Use this for tools with a constant cost regardless of input size.
NativeTool::new("lookup", "Look up a record", schema)
.flat_price(500, "USD")
// Each call costs $5.00Per-Unit Pricing
Scale cost by a custom billing unit. The tool reports the number of units consumed in the ToolInvocationCost.
NativeTool::new("tokenize", "Tokenize text", schema)
.per_unit_price(2, "USD", "1k_tokens")
// $0.02 per 1,000 tokensHybrid Pricing
Combine a base price with a per-unit price. The base price is charged on every invocation; the unit price scales with usage.
NativeTool::new("search", "Search documents", schema)
.hybrid_price(25, 10, "USD", "document")
// $0.25 base + $0.10 per document returnedPricing is in the manifest
Reading Budget Status from Receipts
Every receipt for a tool call that exercises a monetary grant includes FinancialReceiptMetadata in the metadata.financial field. The full field set includes grant_index, cost_charged, currency, budget_remaining, budget_total, delegation_depth, root_budget_holder, settlement_status, cost_breakdown, oracle_evidence, and attempted_cost. The key fields for monitoring budget consumption are:
| Field | What it tells you |
|---|---|
cost_charged | How much this invocation actually cost (after reconciliation) |
budget_remaining | How much budget is left after this invocation |
budget_total | The original total budget for this grant |
cost_breakdown | Itemized cost categories (compute, I/O, etc.) |
settlement_status | Whether the charge is pending, settled, or failed |
Use the CLI to query receipts with financial data:
# Show receipts with cost information for a specific tool
$ chio --receipt-db ./receipts.sqlite receipt list \
--tool-server srv-ai-inference --tool-name generate_text
# Filter by minimum cost (in minor currency units) to find expensive calls
$ chio --receipt-db ./receipts.sqlite receipt list \
--min-cost 100 --tool-server srv-ai-inference
# Look up one receipt by id (list emits JSON Lines; filter with jq)
$ chio --receipt-db ./receipts.sqlite receipt list --limit 500 \
| jq 'select(.id == "rcpt-econ-001")'A receipt for a successful invocation shows cost_charged with the reconciled amount and budget_remaining reflecting the post-invocation state:
{
"metadata": {
"financial": {
"grant_index": 0,
"cost_charged": 75,
"currency": "USD",
"budget_remaining": 4925,
"budget_total": 5000,
"delegation_depth": 0,
"root_budget_holder": "agent-main-001",
"settlement_status": "pending",
"cost_breakdown": {
"compute": 60,
"io": 15
}
}
}
}Handling Cost Overruns
A cost overrun occurs when a tool reports an actual cost greater than the pre-charged amount (max_cost_per_invocation). This should not happen with correctly implemented tools, but the kernel handles it defensively. In an HA deployment the worst-case overrun bound is max_cost_per_invocation.units * active_node_count, reflecting the maximum cost that can escape the atomic pre-charge window if nodes race.
When an overrun is detected during reconciliation:
- The
settlement_statusis set toFailed - The receipt records both
cost_charged(what was pre-charged) and the actual cost reported by the tool - The budget state is not adjusted beyond the pre-charge: the kernel does not retroactively debit more than what was reserved
{
"metadata": {
"financial": {
"grant_index": 0,
"cost_charged": 100,
"currency": "USD",
"budget_remaining": 900,
"budget_total": 1000,
"settlement_status": "failed",
"cost_breakdown": {
"compute": 180,
"io": 40
}
}
}
}Overruns indicate a tool bug
max_cost_per_invocation. If you see settlement_status: "failed" in receipts, investigate the tool server. The tool's declared pricing may be incorrect, or the tool may not be respecting its own cost bounds.To debug overruns, query for failed settlements:
# Find all receipts with failed settlement
$ chio --receipt-db ./receipts.sqlite receipt list --tool-server srv-ai-inference \
| jq 'select(.metadata.financial.settlement_status == "failed")'
# Compare the cost_charged vs actual cost_breakdown totals
# cost_charged = pre-charged amount (max_cost_per_invocation)
# sum(cost_breakdown) = actual cost reported by the toolCross-Currency Setup
When a grant's budget currency differs from a tool's pricing currency, the kernel resolves the exchange rate through an oracle feed via chio-link. The conversion evidence is embedded in every cross-currency receipt for auditability.
To enable cross-currency enforcement, configure oracle feeds in your kernel configuration:
oracle:
feeds:
- pair: USD/USDC
source: chio-link://oracle/fx/usd-usdc
refresh_interval_seconds: 60
margin_bps: 50 # 0.50% margin for volatility
- pair: USD/EUR
source: chio-link://oracle/fx/usd-eur
refresh_interval_seconds: 300
margin_bps: 100 # 1.00% margin
- pair: USD/BTC
source: chio-link://oracle/fx/usd-btc
refresh_interval_seconds: 30
margin_bps: 200 # 2.00% margin for crypto volatilityThe margin_bps field adds a buffer to the exchange rate to account for price movement between the pre-charge and reconciliation phases. One basis point equals 0.01%.
When a cross-currency invocation occurs, the receipt includes OracleConversionEvidence:
{
"metadata": {
"financial": {
"grant_index": 0,
"cost_charged": 150,
"currency": "USD",
"budget_remaining": 850,
"budget_total": 1000,
"settlement_status": "pending",
"oracle_evidence": {
"from_currency": "USDC",
"to_currency": "USD",
"rate_numerator": 100,
"rate_denominator": 100,
"margin_bps": 50,
"oracle_source": "chio-link://oracle/fx/usd-usdc",
"rate_timestamp": 1710000090
}
}
}
}Monitoring Budget Consumption
Budget consumption can be tracked over time by querying the receipt log. Each receipt records the budget_remaining at the time of invocation, giving you a running view of spend.
# chio receipt list emits one JSON receipt per line (JSON Lines).
# Use jq's slurp mode (-s) where aggregation across lines is needed.
# Total cost charged for a specific capability token
$ chio --receipt-db ./receipts.sqlite receipt list --capability cap-budget-001 \
| jq -s '[.[] | .metadata.financial.cost_charged] | add'
# Budget consumption over time (timestamp + remaining)
$ chio --receipt-db ./receipts.sqlite receipt list --capability cap-budget-001 \
| jq '{ts: .timestamp, remaining: .metadata.financial.budget_remaining}'
# Group costs by tool name
$ chio --receipt-db ./receipts.sqlite receipt list --tool-server srv-ai-inference \
| jq -s 'group_by(.tool_name) | map({tool: .[0].tool_name, total: [.[].metadata.financial.cost_charged] | add})'SIEM integration for dashboards
Common Patterns
Free Tier with Invocation Limits
For tools that have no per-call cost but should not be called unboundedly. The invocation limit prevents runaway loops without requiring any monetary accounting.
grants:
- server_id: srv-search
tool_name: web_search
operations: [invoke]
max_invocations: 100
# No max_cost_per_invocation
# No max_total_costPay-Per-Use with Monetary Caps
For metered tools where cost varies per call. The per-invocation cap prevents a single expensive call from consuming the entire budget, while the total cap limits aggregate spending.
grants:
- server_id: srv-ai-inference
tool_name: generate_text
operations: [invoke]
max_cost_per_invocation:
units: 200 # $2.00 max per call
currency: USD
max_total_cost:
units: 10000 # $100.00 total budget
currency: USDDelegated Budgets
A parent agent with a $100 budget delegates $10 to a child agent. The child's spending counts against the parent's total, but the child can never exceed its own $10 limit. Cost attenuations for delegation use ReduceCostPerInvocation, ReduceTotalCost, and ReduceMaxInvocations.
# Parent token: $100 total budget
grants:
- server_id: srv-ai-inference
tool_name: generate_text
operations: [invoke]
max_total_cost:
units: 10000 # $100.00
currency: USD
max_invocations: 1000# Child token (attenuated from parent): $10 budget
# Created via ReduceTotalCost + ReduceMaxInvocations
# (optionally ReduceCostPerInvocation)
grants:
- server_id: srv-ai-inference
tool_name: generate_text
operations: [invoke]
max_total_cost:
units: 1000 # $10.00 (reduced from parent's $100)
currency: USD
max_invocations: 100 # (reduced from parent's 1000)The delegation chain preserves the root_budget_holder field in receipts, so you can always trace spending back to the original budget owner. The delegation_depth field indicates how deep in the chain the invocation occurred.
Budget Exhaustion
When a budget is exhausted, whether by invocation count or monetary cap, the kernel denies further calls and produces a receipt with financial metadata that records the denied attempt.
The denial receipt includes attempted_cost: the cost that would have been charged if the budget were sufficient. This distinguishes budget exhaustion from other denial reasons (guard failures, expired tokens, etc.).
{
"id": "rcpt-deny-budget-001",
"timestamp": 1710001000,
"capability_id": "cap-budget-001",
"tool_server": "srv-ai-inference",
"tool_name": "generate_text",
"action": {
"parameters": {
"prompt": "Write a summary",
"max_tokens": 1000
},
"parameter_hash": "sha256:a1b2c3..."
},
"decision": {
"verdict": "deny",
"reason": "budget exhausted: max_total_cost exceeded (950/1000 USD charged, 100 USD required)",
"guard": "budget"
},
"content_hash": "sha256:0000000000000000...",
"policy_hash": "abc123def456",
"evidence": [
{"guard_name": "budget", "verdict": false, "details": "max_total_cost would be exceeded: 950 + 100 > 1000 USD"}
],
"metadata": {
"financial": {
"grant_index": 0,
"cost_charged": 0,
"currency": "USD",
"budget_remaining": 50,
"budget_total": 1000,
"delegation_depth": 0,
"root_budget_holder": "agent-main-001",
"settlement_status": "not_applicable",
"attempted_cost": 100
}
},
"kernel_key": "ed25519:pub:9c7b3f...",
"signature": "ed25519:d4e5f6a7..."
}Budget exhaustion is final
To detect budget exhaustion programmatically, check for denial receipts where the guard is "budget" and attempted_cost is present:
# Find all budget exhaustion denials
$ chio --receipt-db ./receipts.sqlite receipt list --outcome deny \
| jq 'select(.decision.guard == "budget" and .metadata.financial.attempted_cost != null)'Next Steps
- Economics · the conceptual model behind budgets, including cross-currency enforcement and attenuation
- Receipts · full receipt structure, including financial metadata fields
- Native Tool Server · how to declare tool pricing in native server manifests