BuildEconomics
Tool Pricing
Tool servers publish signed prices; the kernel checks each call against the capability budget at dispatch.
Read Budgets & Metering first
Prerequisites
- A native tool server built with
chio-mcp-adapter. Pricing is declared on the tool at build time and signed into the manifest; there is no runtime price API. - A keypair to sign the manifest with. The public-key hex you pass to the builder has to be that same key, or
sign_manifestrefuses. - An issuing authority that reads the manifest and mints a budgeted capability. Publishing a price does not create a budget.
- Every amount on this page is in minor currency units: 100 units is one US dollar (
crates/core/chio-core-types/src/capability/scope.rs:80-91).
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, which is where a priced tool is authored.
| 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.
pub fn build_service(
public_key_hex: String,
) -> Result<NativeChioService, chio_manifest::ManifestError> {
NativeChioServiceBuilder::new("srv-hello", public_key_hex)
.server_name("Hello Tool Server")
.server_version("0.1.0")
.server_description("A tiny native Chio service that exposes a tool, resource, prompt, and priced manifest")
.tool(
NativeTool::new(
"greet",
"Returns a personalized greeting",
serde_json::json!({
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "The name to greet"
}
},
"required": ["name"]
}),
)
.output_schema(serde_json::json!({
"type": "object",
"properties": {
"greeting": { "type": "string" }
}
}))
.read_only()
.per_invocation_price(25, "USD")
.latency_hint(chio_manifest::LatencyHint::Instant),
|arguments| {
let name = greeting_name(&arguments)?;
Ok(serde_json::json!({
"greeting": format!("Hello, {name}! This greeting was served by a native Chio service.")
}))
},
)
.static_resource(The price rides on the tool, not on the server: .per_invocation_price(25, "USD") declares 25 minor units, which is $0.25 a call. Note the shape of .tool(...): it takes the tool definition and its handler, so a priced tool and the code that serves it are declared together (crates/protocol/chio-mcp-adapter/src/native.rs:265). The other three models are the same call with a different builder method:
use chio_mcp_adapter::native::NativeTool;
// Price by token count; $0.05 per 1k tokens.
let summarize = NativeTool::new("summarize", "Condense input text", schema.clone())
.per_unit_price(5, "USD", "1k_tokens");
// Fixed $1.00 base plus $0.05 per MB.
let archive = NativeTool::new("archive", "Compress and store data", schema.clone())
.hybrid_price(100, 5, "USD", "MB");
// One fixed price with no parameter sensitivity.
let ping = NativeTool::new("ping", "Liveness probe", schema)
.flat_price(1, "USD");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, and sign_manifest refuses when it does not equal the signing key (crates/platform/chio-manifest/src/lib.rs:266,293-303). 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.
Where Pricing Is Authored
Priced-tool authoring lives on NativeTool in chio-mcp-adapter, shown above, and nowhere else. No other SDK carries a pricing builder: a search for the word across the .NET, Swift, JVM, Kubernetes, Lambda and guard SDKs returns nothing, and the TypeScript, Python and Go SDKs carry pricing only on the read side. @chio-protocol/sdk exports the PricingModel and ToolPricing types for verifying a fetched manifest (sdks/typescript/chio-ts/src/invariants/manifest.ts:10-17), the Python chio-sdk package keeps them as private validation sets (sdks/python/chio-py/src/chio/invariants/manifest.py:16,36), and the Go SDK does the same (sdks/go/chio-go/invariants/manifest.go:18-23).
A tool server written in another language still advertises pricing the same way: it serves the signed ToolManifest with the pricing block described above, authored with the native builder. Callers in every language read that 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::scope::{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?
The dispatch path calls authorize_budget_hold(crates/kernel/chio-kernel/src/kernel/validation.rs:1650), whose default implementation delegates to try_charge_cost_with_ids_and_authority(crates/kernel/chio-kernel/src/budget_store.rs:910-933). 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 kernel never reads manifest pricing
ToolPricing is discovery data. Its only consumers in the workspace are the native builder, the manifest crate, and the manifest types (crates/protocol/chio-mcp-adapter/src/native.rs, crates/platform/chio-manifest/src/lib.rs, validation.rs, crates/core/chio-core-types/src/manifest.rs); nothing in the kernel or the economy crates reads it. The cost the kernel authorizes comes from the tool server's reported ToolInvocationCost(crates/kernel/chio-kernel/src/runtime.rs:386-394) or from metered.quote.quoted_cost on a governed intent (crates/kernel/chio-kernel/src/kernel/validation.rs:2698-2713). Translating a published price into a planned cost is the issuing authority's job, which is what makes the manifest advisory and the grant enforcing.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.
{
"grant_index": 0,
"cost_charged": 250,
"currency": "USD",
"budget_remaining": 950,
"budget_total": 1200,
"delegation_depth": 0,
"settlement_status": "settled",
"oracle_evidence": {
"schema": "chio.oracle-conversion-evidence.v1",
"base": "JPY",
"quote": "USD",
"authority": "chio_link_runtime_v1",
"rate_numerator": 1096,
"rate_denominator": 100000,
"source": "chainlink",
"feed_address": "0x...",
"updated_at": 1700000125,
"max_age_seconds": 3600,
"cache_age_seconds": 12,
"converted_cost_units": 250,
"original_cost_units": 228,
"original_currency": "JPY",
"grant_currency": "USD"
}
}The two string fields are not free text. authority is always the constant chio_link_runtime_v1, and a receipt carrying anything else is rejected (crates/economy/chio-web3/src/anchors.rs:25,174-179). source is the backend label: chainlink or pyth, each of which gains a :degraded suffix when resolution relaxes its ceilings (crates/economy/chio-link/src/lib.rs:140-150,877). A Solidity method name such as AggregatorV3Interface.latestRoundData belongs in prose about how the rate was read, not in this field. The block is abridged: a real receipt also carries root_budget_holder, a bare 64-character lowercase hex issuer key.
Check the arithmetic yourself, because the kernel does. The conversion rule is original_cost_units x rate_numerator x quote_minor_units, divided by base_minor_units x rate_denominator, rounded up (crates/economy/chio-web3/src/anchors.rs:283-309), with the minor-unit table at :311-322: 100 for USD, EUR and GBP, 1 for JPY, 1,000,000 for USDC and USDT. Here that is 228 x 1096 x 100 / (1 x 100000) = 249.888, rounded up to 250. A converted_cost_units that does not equal the recomputed value is refused with oracle conversion evidence converted_cost_units must equal (anchors.rs:210-215), so an FX receipt whose numbers do not reconcile never verifies.
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:
// `signedManifest` is what verifySignedManifest accepted. Pricing hangs off
// each tool, not off the manifest: a top-level "pricing" key fails the
// structure check outright.
const tool = signedManifest.manifest.tools.find((entry) => entry.name === "greet");
const pricing = tool?.pricing;
if (pricing?.pricing_model === "per_invocation" && pricing.unit_price) {
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.`);
}# `signed_manifest` is what the invariants verifier accepted; a parsed
# manifest is a plain dict. Pricing hangs off each tool.
tool = next(t for t in signed_manifest["manifest"]["tools"] if t["name"] == "greet")
pricing = tool.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.")The nesting matters more than it looks. MANIFEST_FIELD_SET excludes pricing while TOOL_FIELD_SET includes it (sdks/typescript/chio-ts/src/invariants/manifest.ts:71-90), so a manifest carrying a top-level pricing key comes back with structure_valid: false rather than merely reading as empty.
Where the Budget Is Set
A tool publishes its price; an issuer grants a budget. The policy authoring path, YAML and HushSpec alike, governs what an agent may call rather than what the call costs, so the monetary step lives in capability-issuance or authority code:
- Publish pricing in the manifest.
- Read the manifest in operator or authority code.
- Issue a budgeted capability explicitly.
Keeping the two apart means a price change never silently widens a grant: the budget an agent holds is the one an issuer signed for it.
Verify the result
A published price is only real once it lands on a receipt as a charged cost. The shortest way to see that end to end without standing up a priced server is chio mcp governed-sim, which drives one governed prepaid call through an ephemeral kernel and writes the signed bundle:
$ chio --session-db ./s.sqlite mcp governed-sim \
--governed-mustprepay --payment-adapter sim --out ./bundle.json
$ jq '.metadata.financial' ./bundle.json{
"budget_remaining": 900,
"budget_total": 1000,
"cost_breakdown": {
"payment": {
"adapter_metadata": {
"adapter": "sim",
"commerce": null,
"governed": {
"approvalTokenId": "governed-sim-approval-1",
"intentHash": "f57e0f00b78fe5267f81f92696a0fe9a32339cb02d57b81c9e15f59996f3062d",
"intentId": "governed-sim-intent-1",
"purpose": "no-key CI smoke",
"serverId": "governed-sim-srv",
"toolName": "compute"
},
"mode": "prepaid_no_broadcast"
},
"authorization_id": "sim-268afdc9129342029708eb795c87da08",
"preauthorized_units": 100,
"recorded_units": 100
}
},
"cost_charged": 100,
"currency": "USD",
"delegation_depth": 0,
"grant_index": 0,
"payment_reference": "sim-268afdc9129342029708eb795c87da08",
"root_budget_holder": "0473a15a01ca05a11b0558cd9fd1edd8bddd26f3f598f1c168aaec97dd7f031b",
"settlement_status": "settled"
}crates/core/chio-core-types/src/receipt/economics.rs:77-106at fe56570Read cost_charged against the price you published. If they disagree, the tool server is reporting a cost that its manifest does not justify, and the receipt is the record that says so.
Failures and recovery
The price rose past a grant's cap
Existing grants start denying at dispatch once the per-call cost exceeds max_cost_per_invocation. That is the design: a price change never silently widens a budget somebody already signed for. Re-issue the affected grants with a ceiling that covers the new price, and watch for a spike in monetary denies after any price change.
The call is unpayable
A governed intent that mandates prepayment with no adapter configured is refused before the tool runs, and the refusal is durable rather than merely returned:
$ chio --session-db ./s2.sqlite mcp governed-sim \
--governed-mustprepay --payment-adapter none --out ./bundle-deny.jsoncrates/kernel/chio-kernel/src/kernel/responses/deny_responses.rs:31-90at fe56570The FX receipt does not reconcile
A cross-currency receipt whose converted_cost_units does not equal the recomputed value is refused at verification, not at dispatch, so the symptom is a proof that will not verify rather than a call that will not run. Recompute the conversion by hand before trusting a hand-authored fixture, and check the minor-unit column: a figure that looks 100 times too small is almost always a dollar amount written into a cents field.
The manifest will not sign
sign_manifest re-validates before signing, so a pricing block missing its required field never reaches a caller: Flat needs base_price, PerInvocation and PerUnit need unit_price and billing_unit, and Hybrid needs all three (crates/platform/chio-manifest/src/validation.rs:87-100). The builder methods set these for you; a hand-written manifest is where the omission happens.
Safety Notes
- Keep pricing currency and grant currency identical unless the capability genuinely needs a conversion. Cross-currency resolution works, and it is one more thing that can fail closed: with no oracle configured the kernel denies rather than guessing a rate (
crates/kernel/chio-kernel/src/kernel/validation.rs:2437-2465). - Size
max_total_costwith HA overrun headroom in clustered deployments. Replicas do coordinate: the bound ismax_cost_per_invocation x node_count, the window in which each node may approve one invocation at the cap before the merge propagates (crates/kernel/chio-kernel/src/budget_store.rs:636-641). - 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.