BuildEconomics
Tool Pricing
Tool servers publish signed prices; the kernel checks each call against the capability budget at dispatch.
Read Budgets & Metering first
Price, Budget, and Charged Cost
Chio keeps pricing and budgeting conceptually separate. A tool server publishes a price. An authority issues a budget. The kernel runs at dispatch time and checks whether the budget covers another invocation at the advertised price. Keep those values separate: they come from different actors and are used at different times.
| Value | Source | Role |
|---|---|---|
| Advertised price | Signed tool manifest | Operator input; the kernel does not enforce it directly |
| Issued budget | Capability grant | Kernel-enforced ceiling |
| Kernel-charged cost | Receipt metadata | What actually got deducted |
| Post-execution usage | Trust-control sidecar | Reconciliation record |
A tool server publishes pricing in a signed manifest. An operator or authority reads that quote. The authority issues a capability whose monetary budget is consistent with the advertised price plus a local safety margin. The kernel enforces the issued budget at invocation time.
Pricing models
Chio supports four pricing models in manifests, spelled Flat, PerInvocation, PerUnit, and Hybrid in the PricingModel enum. Each corresponds to a builder method on NativeTool in Rust. Rust is currently the supported way to author priced tools.
| Model | Rust builder | Behavior |
|---|---|---|
flat | flat_price(units, currency) | One fixed base price; no parameter sensitivity |
per_invocation | per_invocation_price(units, currency) | Each invocation uses the same fixed price; billing unit is "invocation" |
per_unit | per_unit_price(units, currency, billing_unit) | Price scales with a declared unit such as 1k_tokens or MB |
hybrid | hybrid_price(base_units, unit_units, currency, billing_unit) | Fixed base plus a per-unit variable component |
All prices are declared in minor currency units (cents for USD, the smallest denomination for other currencies), matching the MonetaryAmount type used throughout chio.
Manifest Pricing Fields
Every ToolDefinition may carry a pricing object. The four models use the same shape with different populated fields.
{
"name": "greet",
"description": "Returns a personalized greeting",
"input_schema": { "...": "..." },
"pricing": {
"pricing_model": "per_invocation",
"unit_price": { "units": 25, "currency": "USD" },
"billing_unit": "invocation"
}
}For hybrid, a base_price field is also present:
{
"pricing": {
"pricing_model": "hybrid",
"base_price": { "units": 100, "currency": "USD" },
"unit_price": { "units": 5, "currency": "USD" },
"billing_unit": "1k_tokens"
}
}The populated fields differ by model. For flat, the advertised amount lives in base_price; unit_price is absent and no billing_unit is set. For per_invocation, the amount lives in unit_price, billing_unit is "invocation", and base_price is absent. For per_unit, the amount lives in unit_price and billing_unit names the scaling dimension (common values: 1k_tokens, MB, GB, row). The manifest validator enforces this split: it requires base_price for flat, unit_price plus a billing_unit for per_invocation and per_unit, and both amounts plus a billing_unit for hybrid.
Declaring Pricing in Rust
The maintained native example in examples/hello-tool publishes pricing directly from NativeTool. The builder adds pricing directly to a tool definition.
use chio_core::crypto::Keypair;
use chio_mcp_adapter::{NativeChioServiceBuilder, NativeTool};
// $0.25 per call, priced per invocation.
let greet = NativeTool::new(
"greet",
"Returns a personalized greeting",
serde_json::json!({
"type": "object",
"properties": { "name": { "type": "string" } },
"required": ["name"]
}),
)
.read_only()
.per_invocation_price(25, "USD");
// Price by token count; $0.005 per 1k tokens.
let summarize = NativeTool::new(
"summarize",
"Condense input text",
serde_json::json!({
"type": "object",
"properties": { "text": { "type": "string" } },
"required": ["text"]
}),
)
.per_unit_price(5, "USD", "1k_tokens");
// Fixed $1.00 setup fee plus $0.05 per MB.
let archive = NativeTool::new(
"archive",
"Compress and store data",
serde_json::json!({
"type": "object",
"properties": { "data": { "type": "string" } },
"required": ["data"]
}),
)
.hybrid_price(100, 5, "USD", "MB");
// new() takes the server id and the server's public-key hex (a manifest
// metadata field, not a signing key).
let server_kp = Keypair::generate();
let service = NativeChioServiceBuilder::new("srv-hello", server_kp.public_key().to_hex())
.tool(greet)
.tool(summarize)
.tool(archive)
.build()?;
// build() only validates the assembled manifest. Signing is a separate step.
let signed = chio_manifest::sign_manifest(service.manifest(), &server_kp)?;NativeChioServiceBuilder::build() validates the assembled manifest and returns the service; it does not sign. The public-key hex passed to NativeChioServiceBuilder::new(server_id, public_key) is a manifest metadata field, not a signing key. After build(), call chio_manifest::sign_manifest(service.manifest(), &keypair), which re-validates and signs the whole manifest. The signature covers the pricing block, so any post-hoc change to price requires re-signing. Callers can verify the signature before extending trust to the declared prices.
Flat vs. Per-Invocation
flat_price and per_invocation_price can advertise the same per-call amount, but they are not aliases: they emit different manifest shapes and are not interchangeable.
// flat_price -> pricing_model: Flat, amount in base_price, no billing_unit.
tool.flat_price(25, "USD");
// pricing = { pricing_model: "flat", base_price: { units: 25, currency: "USD" } }
// per_invocation_price -> pricing_model: PerInvocation, amount in unit_price,
// billing_unit "invocation".
tool.per_invocation_price(25, "USD");
// pricing = { pricing_model: "per_invocation",
// unit_price: { units: 25, currency: "USD" },
// billing_unit: "invocation" }Reach for flat_price when a tool has no parameter sensitivity and the manifest should record that; use per_invocation_price when the price is a fixed per-call unit. A caller reading the manifest must branch on pricing_model and read the amount from the correct field.
Declaring Pricing Is Rust-Only Today
Priced-tool authoring lives on NativeTool in chio-mcp-adapter, shown above. There is no TypeScript or Python equivalent. The published TypeScript packages are all framework-scoped middleware (@chio-protocol/express, fastify, next, workers, and siblings), and the Python packages (chio-sdk-python, chio-sdk) are clients to the sidecar and to hosted MCP sessions. None of them expose a tool()/pricing.* builder or a ChioToolServer.
A tool server written in another language still advertises pricing the same way: it serves the signed ToolDefinition manifest with the pricing block described above. Authoring that manifest with the native builder is the supported path today. TypeScript and Python callers read the pricing block from a fetched manifest (see Advertising Price to Callers); they do not author it.
Budget Planning from a Quote
Translate the quote into a capability-grant budget. The planning rules depend on the pricing model:
| Model | Per-call cap | Total budget |
|---|---|---|
flat | Flat quote | flat * allowed_invocations + margin |
per_invocation | Quoted unit price | unit_price * allowed_invocations + margin |
per_unit | Conservative per-call estimate from expected unit ceiling | per_call_estimate * allowed_invocations + margin |
hybrid | base + unit * expected_units_per_call | per_call * allowed_invocations + margin |
A worked example with the greet tool above: the manifest advertises per_invocation at 25 USD minor units. The expected workload is 40 calls. A straightforward planning pass:
expected_total = 40 * 25 = 1000
safety_margin = 200
grant_total = 1200
per_call_cap = 25The corresponding capability grant:
use chio_core::capability::{MonetaryAmount, Operation, ToolGrant};
let grant = ToolGrant {
server_id: "srv-hello".to_string(),
tool_name: "greet".to_string(),
operations: vec![Operation::Invoke],
constraints: vec![],
max_invocations: None,
max_cost_per_invocation: Some(MonetaryAmount {
units: 25,
currency: "USD".to_string(),
}),
max_total_cost: Some(MonetaryAmount {
units: 1200,
currency: "USD".to_string(),
}),
dpop_required: Some(true),
};The quote is not the enforcement boundary
Dispatch-Time Validation
At dispatch, the kernel calls try_charge_cost(). This is an atomic operation that checks three things together, in a single transaction on the budget store:
- Invocation count. Does the grant have any
max_invocationsleft? - Per-invocation cap. Is this call's planned cost at or below
max_cost_per_invocation? - Total budget. Does the grant have at least the planned cost remaining under
max_total_cost?
If any check fails the call is denied with a specific reason code. If all three pass, the planned cost is deducted and the call is dispatched. The deduction is visible on the receipt as metadata.financial.cost_charged.
# The planned cost that the kernel deducts:
#
# flat -> flat price
# per_invocation -> unit price
# per_unit -> unit price * expected_units_per_call (from intent)
# hybrid -> base price + unit price * expected_units_per_call
#
# The grant's max_cost_per_invocation should be at least this value.
# Post-execution, observed units may differ from expected. That
# reconciliation happens in the trust-control sidecar, not in the
# signed receipt.Metered Billing and Governed Quotes
Manifest pricing is advisory discovery data. When an operator wants to bind a concrete pre-execution quote into a governed request, Chio supports a typed governed_intent.metered_billing block. It carries:
| Field | Meaning |
|---|---|
settlement_mode | must_prepay, hold_capture, or allow_then_settle |
quote.quote_id | Stable identifier from the metering or billing system |
quote.provider | Billing authority that issued the quote |
quote.billing_unit | Unit name (invocation, 1k_tokens, ...) |
quote.quoted_units | Estimated billable units |
quote.quoted_cost | Estimated monetary amount |
quote.issued_at / expires_at | Quote validity window |
max_billed_units | Explicit upper bound for the governed request |
With metered_billing, the pre-execution quote and settlement configuration live on the governed intent; kernel-charged cost lives in metadata.financial on the receipt; post-execution usage is reconciled through a mutable trust-control sidecar keyed by receipt_id. The signed receipt is immutable. Reports can show the quote, kernel-charged cost, and external metering separately.
Cross-Currency Transactions
Tools may quote in one currency while agents hold budgets in another. Chio handles cross-currency settlement through an OracleConversionEvidence record on the receipt's metadata.financial.oracle_evidence field. A Chainlink feed (or other policy-configured oracle) provides the conversion rate at dispatch time; the kernel records the rate as an integer rate_numerator / rate_denominator pair, along with the feed source, address, and timestamp, so an auditor can reconstruct the math. The Settlement guide's Oracle Price Verification section documents the same record from the resolver side.
{
"metadata": {
"financial": {
"grant_index": 0,
"cost_charged": 250,
"currency": "USD",
"budget_remaining": 950,
"budget_total": 1200,
"delegation_depth": 0,
"root_budget_holder": "did:chio:z6Mk...",
"settlement_status": "settled",
"oracle_evidence": {
"schema": "chio.oracle-conversion-evidence.v1",
"base": "JPY",
"quote": "USD",
"authority": "chainlink",
"rate_numerator": 1096,
"rate_denominator": 100000,
"source": "AggregatorV3Interface.latestRoundData",
"feed_address": "0x...",
"updated_at": 1700000125,
"max_age_seconds": 3600,
"cache_age_seconds": 12,
"converted_cost_units": 250,
"original_cost_units": 22800,
"original_currency": "JPY",
"grant_currency": "USD"
}
}
}
}Use one currency unless you need FX
Changing Prices
A tool's manifest is signed. Changing the price of an existing tool requires:
- Updating the pricing fields on the
ToolDefinition. - Re-signing the manifest with the server's key.
- Publishing the new manifest version (with an updated
versionandpublished_at).
Callers who fetched the previous manifest still see the old price until they re-fetch. When they do re-fetch, they will see the new price before they attempt any delegation or capability issuance. The kernel does not silently adopt new prices: the authority that mints a grant must read the current manifest and issue a budget that is consistent with the current quote. If the price has risen and the grant budget no longer covers a call, the kernel denies at dispatch.
Price increases need operator action
max_cost_per_invocation. This is working as intended. Operators should watch the receipt query API for a spike in budget-cap denies after a price change and re-issue grants with a higher ceiling where appropriate.Advertising Price to Callers
Before an agent (or an authority on its behalf) delegates a capability for a tool, it fetches the signed manifest during tool discovery and inspects the pricing block. Once parsed, the block is plain structured data in any language:
// `manifest` is the signed ToolDefinition you fetched during tool discovery.
const pricing = manifest.pricing;
if (pricing?.pricing_model === "per_invocation") {
const { units, currency } = pricing.unit_price;
const plannedCalls = 40;
const margin = 200; // minor units
const totalBudget = units * plannedCalls + margin;
console.log(`Quoted at ${units} ${currency} per call.`);
console.log(`Planning budget of ${totalBudget} ${currency} for ${plannedCalls} calls.`);
}# `manifest` is the signed ToolDefinition fetched during tool discovery.
pricing = manifest.get("pricing")
if pricing and pricing["pricing_model"] == "per_invocation":
units = pricing["unit_price"]["units"]
currency = pricing["unit_price"]["currency"]
planned_calls = 40
margin = 200
total_budget = units * planned_calls + margin
print(f"Quoted at {units} {currency} per call.")
print(f"Planning budget of {total_budget} {currency} for {planned_calls} calls.")Current Limitations
The policy YAML and HushSpec authoring path does not yet encode monetary default-capability issuance. Operators perform monetary planning in capability-issuance or authority code. Current flow:
- Publish pricing in the manifest.
- Read the manifest in operator or authority code.
- Issue a budgeted capability explicitly.
There is no YAML pricing-to-budget configuration yet. Use the three steps above until that feature ships.
Safety Notes
- Keep pricing currency and grant currency identical until multi-currency support ships.
- Size
max_total_costwith HA overrun headroom in clustered deployments. Per-kernel budget state does not coordinate across replicas. - Require DPoP on spend-bearing grants so quoted authority stays bound to the intended subject.
- Use manifest pricing as operator input. Receipts and the metered sidecar record billed usage.
- When a tool may exceed the advisory quote, set the grant from the worst-case amount you are willing to authorize, not from the optimistic quote.
Related Guides
- Budgets & Metering walks through the budget side of the same transaction.
- Settlement covers how observed costs reconcile against quoted costs after the tool runs.
- Economics is the conceptual overview of how money moves through chio.