Chio/Docs
LOGIN · JOIN

ReferenceSpec

Metering Spec

Chio metering 1.0: cost dimensions, budget enforcement, billing-export records, and the query interface the chio-metering crate implements.

Source

This page normatively reflects spec/METERING.md in the chio repository. Status: Normative. Version 1.0, dated 2026-04-14. The keywords MUST, SHOULD, and MAY are normative.

Behavior comes from the crate that implements the spec, crates/economy/chio-metering: src/cost.rs for the dimensions and receipt cost metadata, src/budget.rs for the flat enforcer, src/budget_hierarchy.rs for the tree, src/export.rs for billing records, and src/query.rs for the query interface. Where the crate and the spec disagree, the crate decides and the difference is named where it falls.

chio-metering is a library. It is not on the kernel receipt path: the only workspace crate that depends on it is crates/guards/chio-data-guards, whose WarehouseCostGuard imports CostDimension. Read the types below as a contract a host implements, and the kernel's own monetary blocks in Receipt Format as a separate object.


Synopsis

A cost dimension is an internally tagged enum whose tag key is dimension; the crate defines five variants at crates/economy/chio-metering/src/cost.rs:15-62. The two metering schema identifiers are chio.cost-metadata.v1 and chio.billing-export.v1.

crates/economy/chio-metering/src/cost.rs15-62rust
#[serde(tag = "dimension", rename_all = "snake_case")]
pub enum CostDimension {
    /// Wall-clock compute time in milliseconds.
    ComputeTime {
        /// Duration in milliseconds.
        duration_ms: u64,
    },
    /// Data volume transferred in bytes.
    DataVolume {
        /// Bytes read from upstream.
        bytes_read: u64,
        /// Bytes written to upstream.
        bytes_written: u64,
    },
    /// Monetary cost charged by an upstream API.
    ApiCost {
        /// The cost amount in minor currency units.
        amount: MonetaryAmount,
        /// Provider that charged this cost (e.g. "openai", "anthropic").
        provider: String,
    },
    /// Warehouse query cost recorded by the `WarehouseCostGuard` in
    /// `chio-data-guards`.  Captures the dry-run cost estimate for a
    /// warehouse-class query (BigQuery, Snowflake, Redshift, etc.).
    ///
    /// `estimated_cost_usd` is a decimal string rather than a fixed-width
    /// integer because `rust_decimal` is not part of the Chio workspace;
    /// this follows the precedent set by
    /// `Constraint::MaxTransactionAmountUsd` in `chio-core-types`.
    WarehouseQuery {
        /// Bytes the warehouse reported it will scan to satisfy the query.
        bytes_scanned: u64,
        /// Decimal-string estimate of the monetary cost in USD (e.g.
        /// `"0.25"`).  Parsed against the guard's `max_cost_per_query_usd`
        /// limit; preserved verbatim on the receipt for auditability.
        estimated_cost_usd: String,
    },
    /// Custom cost dimension for extensibility.
    Custom {
        /// Name of the custom dimension.
        name: String,
        /// Numeric value.
        value: u64,
        /// Optional unit label (e.g. "tokens", "requests").
        #[serde(default, skip_serializing_if = "Option::is_none")]
        unit: Option<String>,
    },
}

Cost dimensions

Each receipt MAY carry zero or more cost dimensions. Each serializes with its snake-case variant name under the dimension tag.

The crate carries one dimension the spec text omits

spec/METERING.md section 2.1 tabulates four variants and omits WarehouseQuery. The enum in crates/economy/chio-metering/src/cost.rs defines five. The enum decides what deserializes, so the variant table follows it.

CostDimension

VariantFieldsDescription
ComputeTimeduration_ms: u64Wall-clock compute time in milliseconds
DataVolumebytes_read: u64, bytes_written: u64Data volume transferred in bytes
ApiCostamount: MonetaryAmount, provider: stringMonetary cost charged by an upstream API
WarehouseQuerybytes_scanned: u64, estimated_cost_usd: stringDry-run cost estimate for a warehouse-class query (BigQuery, Snowflake, Redshift, and similar)
Customname: string, value: u64, unit: string?Extensibility dimension with a numeric value

MonetaryAmount

FieldTypeDescription
unitsu64Cost in minor currency units (e.g., cents for USD)
currencystringISO 4217 currency code (e.g., "USD", "EUR")

The Custom variant allows operators to track domain-specific metrics (e.g., token counts, request counts) without modifying the protocol.

The WarehouseQuery variant records the dry-run cost estimate a warehouse cost guard reads off a tool call before the query runs. It serializes under the dimension tag "warehouse_query". estimated_cost_usd is a decimal string rather than a fixed-width integer: the guard parses it against its max_cost_per_query_usd limit and preserves the value verbatim on the receipt for auditability. This follows the precedent set by Constraint::MaxTransactionAmountUsd in the capability scope types.

CostMetadata

Per-receipt cost metadata, serialized as JSON. The doc comment on the struct places it in the receipt's metadata map under the cost key.

cost is not one of the kernel's reserved metadata keys, and no crate in the workspace writes it. The kernel reserves admission_operation, attribution, budget_authority, channel, chio_receipt_signing_nonce, delivery_contract, financial, finding_delivery, finding_recovery, governed_transaction, and original_metadata, declared in crates/core/chio-core-types/src/receipt/metadata.rs, crates/core/chio-core-types/src/receipt/signing.rs, and crates/kernel/chio-kernel/src/admission_operation.rs. The kernel writes its own typed block under each of those, merges them last, and rejects a pre-existing collision from caller or hook metadata. Placing cost is therefore the host's job, and what it writes there is ordinary caller metadata: the kernel signs it and does not author it.

FieldTypeRequiredDescription
schemastringYesMUST be "chio.cost-metadata.v1"
receipt_idstringYesReceipt ID this cost belongs to
timestampu64YesUnix timestamp (seconds) of the receipt
session_idstringNoSession that produced this receipt
agent_idstringYesAgent that made the invocation
tool_serverstringYesTool server that handled the invocation
tool_namestringYesTool that was invoked
dimensionsCostDimension[]YesIndividual cost measurements
total_monetary_costMonetaryAmountNoAggregate monetary cost across ApiCost dimensions

Total monetary cost computation

The total_monetary_cost field is computed by summing all ApiCost dimension amounts. Implementations MUST use saturating addition to prevent overflow.

When multiple ApiCost dimensions use different currencies, only amounts in the first currency encountered are summed. Cross-currency amounts require oracle conversion and are excluded from the automatic total. Implementations SHOULD document this behavior to operators.

When no ApiCost dimensions are present, total_monetary_cost MUST be null.


Budget enforcement

Budget enforcement tracks cumulative spending and rejects invocations that would exceed configured limits. Enforcement is fail-closed: any error during budget evaluation MUST deny the request.

BudgetPolicy

FieldTypeRequiredDescription
max_totalMonetaryAmountYesMaximum total spending across all dimensions
max_per_sessionMonetaryAmountNoPer-session spending limit
max_per_agentMonetaryAmountNoPer-agent spending limit
max_per_toolmap[string, MonetaryAmount]NoPer-tool limits; key format "server:tool"
currencystringYesCurrency for budget enforcement

Enforcement semantics

Budget checks evaluate in the following order. The first violation encountered MUST be returned:

  • Total budget: total_spent + cost > max_total.units
  • Per-session budget: session_spent + cost > max_per_session.units
  • Per-agent budget: agent_spent + cost > max_per_agent.units
  • Per-tool budget: tool_spent + cost > max_per_tool[key].units

All arithmetic MUST use saturating addition. Overflow MUST saturate to u64::MAX rather than wrapping.

A cost of zero MUST always pass budget checks regardless of current spending levels.

BudgetViolation

When a budget check fails, the enforcer MUST return a typed violation:

VariantFieldsDescription
Totallimit_units, current_units, requested_units, currencyTotal budget exceeded
Sessionsession_id, limit_units, current_units, requested_units, currencyPer-session budget exceeded
Agentagent_id, limit_units, current_units, requested_units, currencyPer-agent budget exceeded
Tooltool_key, limit_units, current_units, requested_units, currencyPer-tool budget exceeded

Recording

After a tool invocation succeeds and the receipt is signed, the enforcer records the cost against all applicable counters (total, session, agent, tool). Recording uses saturating addition.

The record operation does not enforce limits; it only updates tracking counters. Budget enforcement happens exclusively in the check operation before invocation.

Three accessors read the enforcer without changing it. total_spent() returns the running total in policy currency units, remaining() returns max_total.units minus that total under saturating subtraction, so it floors at zero rather than wrapping, and policy() borrows the active BudgetPolicy. The per-session, per-agent, and per-tool maps stay private; a caller reads them only through a check violation. Defined at crates/economy/chio-metering/src/budget.rs:191, budget.rs:197, and budget.rs:203.


Budget hierarchy

BudgetEnforcer holds one flat policy. Above it sits a second model the specification text does not cover: a tree of nodes, one per organizational scope, where every ancestor caps its descendants. crates/economy/chio-metering/src/lib.rs declares the module at line 20 and re-exports its public types at lines 27 to 30, so a caller reaches them as chio_metering::BudgetTree and siblings.

The tree owns shape and limits, never storage. A caller reads current spend from its own store into a SpendSnapshot and passes it in for each decision.

TypeFields or variantsBehavior
BudgetNodeIdString, transparentNode identifier, conventionally scope/name. The tree validates uniqueness and acyclic parents; it does not validate the string shape.
BudgetWindowDaily, Monthly, Rolling { seconds }Tagged under kind. duration_seconds() returns 86400, 30 days, or the given seconds; bucket_start(ts) returns ts - (ts % duration), and 0 when the duration is 0.
BudgetLimitsmax_spend_units, currency, max_tokens, max_requests, max_warehouse_bytesEvery field is optional, so a node may cap one dimension and leave the rest open. currency is required once max_spend_units is set.
BudgetNodeid, parent, limits, window, enabledOne scope. A node with no parent is a root; enabled defaults to true and a disabled node denies every draft charged through it.
AggregateSpendspend_units, currency, tokens, requests, warehouse_bytesThe four capped dimensions plus the currency that denominates the first. Used for both the draft and the recorded spend.
PerWindowSpendwindow_start, currentOne node's spend inside one window bucket. The bucket start is compared against the node's own window, so a stale row denies rather than under-counting.
SpendSnapshotevaluated_at, per_nodeThe caller-supplied read of the store. A snapshot with no evaluated_at denies as an expired window rather than defaulting to now.
BudgetTreeinsert, validate, ancestors, descendants, evaluate, serialize, deserializeThe node map. insert rejects a duplicate id, a missing parent, and a parent chain that would close a cycle. serialize emits nodes sorted by id.
BudgetDecisionAllow, Deny { reason }Tagged under decision. There is no third outcome and no warning state.
BudgetDenyReasonNodeDisabled, DimensionExceeded, CurrencyMismatch, ArithmeticOverflow, WindowExpired, UnknownNodeTagged under reason. DimensionExceeded names the dimension as one of spend, tokens, requests, or warehouse_bytes, and formats the cap and the projected value as decimal strings.
BudgetErrorInvalidLimits, Cycle, MissingParent, Duplicate, InvalidSerializationConstruction and deserialization failures. Distinct from a deny: a tree that cannot be built never evaluates.

Evaluation rule

BudgetTree::evaluate(id, draft, current) takes the leaf the charge belongs to, the draft spend, and the snapshot. A leaf absent from the tree denies at once with UnknownNode. Otherwise the walk runs from that leaf to the root, and at each node in turn:

  1. A disabled node records NodeDisabled.
  2. A snapshot with no evaluated_at, or a row whose window_start differs from the node's bucket start for that instant, records WindowExpired. A node with no row is read as zero spend in the current bucket.
  3. A monetary cap with no node currency, or with a draft or snapshot currency that differs from it, records CurrencyMismatch. A zero amount carrying no currency is accepted against the node currency.
  4. A checked addition that overflows records ArithmeticOverflow for that dimension. The hierarchy checks addition rather than saturating it, so an overflow denies where the flat enforcer would clamp.
  5. A projection over a cap records DimensionExceeded, for spend, tokens, requests, and warehouse bytes in that order.

Each node overwrites the recorded reason, so the reason returned is the one from the node closest to the root and an operator reads the widest policy boundary first. A walk that records nothing returns Allow. Defined at crates/economy/chio-metering/src/budget_hierarchy.rs:599, over the ancestor chain built at budget_hierarchy.rs:548.


Billing export

Billing export transforms Chio cost metadata into flat, denormalized records suitable for ingestion by external billing systems.

BillingRecord

FieldTypeDescription
schemastringMUST be "chio.billing-export.v1"
receipt_idstringReceipt ID
timestampu64Unix timestamp (seconds)
timestamp_isostringISO 8601 timestamp in UTC (e.g., "2023-11-14T22:13:20Z")
session_idstringSession ID (nullable)
agent_idstringAgent that triggered the cost
tool_serverstringTool server
tool_namestringTool name
compute_time_msu64Total compute time in milliseconds
data_bytesu64Total data transferred in bytes
cost_unitsu64Monetary cost in minor units (nullable)
currencystringISO 4217 currency code (nullable)
providerstringUpstream provider (nullable)

All timestamps MUST use ISO 8601 format with UTC timezone and Z suffix. Implementations MUST fall back to "unix:<timestamp>" when the Unix timestamp cannot be converted to a calendar date.

BillingExport

A batch of billing records.

FieldTypeDescription
schemastringMUST be "chio.billing-export.v1"
exported_atu64Unix timestamp when the export was created
record_countu64Total number of records in this export
total_costMonetaryAmountAggregate cost (null if mixed currencies)
recordsBillingRecord[]The billing records

When records contain costs in multiple currencies, total_cost MUST be null rather than summing incompatible amounts.

One function builds the batch: create_billing_export(records, exported_at) at crates/economy/chio-metering/src/export.rs:70. It flattens each CostMetadata into one BillingRecord, takes provider from the first ApiCost dimension it finds, sums total_monetary_cost with saturating addition while the currency holds, and sets the export's total_cost to null as soon as a second currency appears.

Export formats

Implementations MUST support JSON export. Implementations SHOULD support CSV export. CSV output MUST use the same field names as the JSON schema with one record per row and a header line.

The crate meets the MUST and leaves the SHOULD to its caller. BillingRecord and BillingExport derive Serialize, which gives JSON through serde_json. There is no CSV writer in crates/economy/chio-metering/src/export.rs, and the crate takes no CSV dependency in its manifest. A host that wants the CSV form derives it from the record fields.


Query interface

The query interface supports cost aggregation across multiple dimensions.

crates/economy/chio-metering/src/lib.rs re-exports CostQuery, CostQueryResult, and CostSummary at line 33, and stops there. GroupBy, CostGroup, execute_cost_query, and MAX_COST_QUERY_LIMIT are public on the module, so a caller reaches them at chio_metering::query:: and not at the crate root.

CostQuery

All filter fields are optional. When omitted, the filter matches all records. Multiple filters are ANDed together.

FieldTypeDescription
session_idstringFilter by session ID
agent_idstringFilter by agent ID
tool_serverstringFilter by tool server
tool_namestringFilter by tool name
sinceu64Start of time range, inclusive (Unix seconds)
untilu64End of time range, exclusive (Unix seconds)
currencystringOnly include costs in this currency
limitusizeMaximum detailed records to return
group_byGroupByAggregation dimension

GroupBy

ValueDescription
noneNo grouping; return individual receipt costs
sessionGroup by session ID
agentGroup by agent ID
toolGroup by tool key ("server:tool_name")

CostQueryResult

FieldTypeDescription
summaryCostSummaryAggregate statistics across all matching records
groupsCostGroup[]Grouped rows (empty when group_by is none)
truncatedboolWhether the result was truncated due to limit

CostSummary

FieldTypeDescription
receipt_countu64Total matching receipts
total_compute_time_msu64Aggregate compute time
total_data_bytesu64Aggregate data volume
total_monetary_costMonetaryAmountAggregate cost (null if mixed currencies)
distinct_agentsu64Number of distinct agents
distinct_toolsu64Number of distinct tools

CostGroup

FieldTypeDescription
keystringGroup key (session ID, agent ID, or "server:tool")
receipt_countu64Receipts in this group
total_compute_time_msu64Compute time for this group
total_data_bytesu64Data volume for this group
total_monetary_costMonetaryAmountCost for this group (null if mixed currencies)

Limits

The maximum number of records returned by a single query MUST NOT exceed MAX_COST_QUERY_LIMIT, which the crate sets to 500 at crates/economy/chio-metering/src/query.rs:12. When the matching set exceeds the limit, the result MUST set truncated to true and return only the first limit records. An operator-provided limit is capped at that constant.


  • Pricing · what a call costs before metering measures it
  • Reconciliation · adjusting a kernel budget hold to realized cost, which this crate does not do
  • Economics · budgets and metering as a concept, without the field shapes
  • Receipt Format · the kernel-written monetary blocks on a signed receipt