Chio/Docs
LOGIN · JOIN

EconomySpend Checks

Reconciliation

Quoted, observed, and charged cost are three separate numbers, and reconciliation is what makes a receipt carry all three.

A quote is an estimate, a meter is a measurement, and a rail capture is money. Chio records the three separately and reconciles them into one receipt, so a reader can tell which number came from where. The rail side of that cycle is the PaymentAdapter trait in chio-kernel; the ledger side is chio-credit; the stall detector is chio-settle; the finance export is chio-metering.


The reconcile cycle

Five steps produce records that later steps can verify:

  1. Authorize. Before the call executes, the kernel resolves a MeteredBillingQuote: a quoted unit count and a quoted monetary cost. Under MustPrepay or HoldCapture, the kernel calls the adapter's authorize with the worst-case amount and gets back a PaymentAuthorization whose state is either Held or PrepaidFinal.
  2. Execute. The governed call runs. Cost metering observes compute time, data bytes transferred, and any per-call upstream API cost.
  3. Capture the observed cost. A held authorization settles at the observed amount: capture when that amount is above zero, release when it is zero. An authorization that was already PrepaidFinal takes neither call.
  4. Realize the budget. The kernel reconciles the hold at actual_cost.min(charge.cost_charged), so an underspend returns the difference to the grant and an overspend cannot silently exceed the authorized charge.
  5. Emit the reconciled receipt. The receipt carries the rail's payment_reference, a final SettlementStatus, and the metered usage evidence.

Three conditions break the underspend path and close the hold at the authorized charge instead: cross_currency_failed when a reported cost is in another currency and no oracle rate resolved, payment_already_settled when the authorization was prepaid and final, and cost_overrun when the reported cost exceeds the authorized charge. A cross-currency failure or an overrun also sets settlement_status to Failed.


Three cost layers

Every reconciled receipt resolves three cost values. They are recorded separately so each one can be audited on its own:

LayerSourceFinal whenCarried on the receipt as
QuotedMeteredBillingQuote.quoted_costAt intent signingMeteredBillingReceiptMetadata.quote
ObservedCost meter, post-executionWhen the tool returns or errorsMeteredUsageEvidenceReceiptMetadata.observed_units
ChargedRail capture amountWhen the adapter returns a settled resultFinancialReceiptMetadata.payment_reference and settlement_status

Quoted and observed values describe expected and measured cost. Charged records rail-side money movement. The three metadata blocks live under two reserved receipt keys: governed_transaction carries the quote and the usage evidence, financial carries the charge.

The metadata blocks under governed_transaction are camelCase on the wire and the block itself is not, because GovernedTransactionReceiptMetadata carries no rename_all while MeteredBillingReceiptMetadata, MeteredBillingQuote, and MeteredUsageEvidenceReceiptMetadata all do. FinancialReceiptMetadata is snake_case throughout.


The authoritative-spend contract

A receipt that says money moved is a different claim from a receipt that proves it. The predicate is_authoritative_spend_receipt decides which one a given receipt is: it demands a mediated Allow, a budget hold whose terminal mutation was a reconcile, a signed execution nonce (chio.execution_nonce.v1) that binds the exact call, and an admitted kernel key. The frozen receipt profile it pins is chio.mediated_spend.v1, and BudgetGuaranteeLevel records how strong the store behind that hold actually was.

Authoritative Spend holds the full conjunction and the guarantee-level taxonomy. This page is the same contract seen from the rail and the ledger, not a second one, and Budgets & Metering teaches the lifecycle against a capability token's budget.


The exposure ledger

Across many in-flight settlements, an operator needs one view of total open exposure. chio-credit provides it as chio.credit.exposure-ledger.v1, with one position record per currency:

crates/economy/chio-credit/src/lib.rsrust
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct ExposureLedgerCurrencyPosition {
    pub currency: String,
    pub governed_max_exposure_units: u64,
    pub reserved_units: u64,
    pub settled_units: u64,
    pub pending_units: u64,
    pub failed_units: u64,
    pub provisional_loss_units: u64,
    pub recovered_units: u64,
    pub quoted_premium_units: u64,
    pub active_quoted_premium_units: u64,
}

The report is a projection over receipts, not a running balance. For each matching receipt the builder adds the governed max_amount into governed_max_exposure_units and files the receipt's financial amount into exactly one of settled_units, pending_units, or failed_units according to its SettlementStatus. A NotApplicable status contributes nothing. quoted_premium_units and active_quoted_premium_units come from underwriting decisions rather than receipts.

reserved_units and provisional_loss_units are not separate lifecycle stages either. Both are gated on one boolean on the entry:

crates/platform/chio-store-sqlite/src/receipt_store/support/claim_log/metadata.rsrust
pub(crate) fn settlement_reconciliation_action_required(
    settlement_status: SettlementStatus,
    reconciliation_state: SettlementReconciliationState,
) -> bool {
    matches!(
        settlement_status,
        SettlementStatus::Pending | SettlementStatus::Failed
    ) && !matches!(
        reconciliation_state,
        SettlementReconciliationState::Reconciled | SettlementReconciliationState::Ignored
    )
}

A receipt whose settlement is Pending or Failed, and whose SettlementReconciliationState is still Open or RetryScheduled, carries action_required. That flag is what fills reserve_required_amount on the entry, and a Failed receipt that still needs action also fills provisional_loss_amount. So reserved_units reads as exposure an operator has not closed out, and provisional_loss_units as the part of it that already failed.

ExposureLedgerQuery refuses a query with no anchor. Its validate requires at least one of --capability, --agent-subject, or --tool-server, refuses a --tool-name without a --tool-server, and refuses a --since later than --until. Receipts default to 100 rows and cap at 200; decisions default to 50 and cap at 200.

recovered_units is never filled from receipts

build_exposure_ledger_receipt_entry sets recovered_amount: None on every entry it builds, so a report assembled from receipts alone leaves recovered_units at zero. The field exists on the position and the report sums it, but a recovery has to reach the ledger through the credit loss lifecycle rather than through a receipt.

The settlement watchdog

Settlements stall. Counterparties go offline, rails delay, chains reorganize. The watchdog detects the stall and classifies it, and leaves the recovery transaction to the operator. Jobs carry the schema chio.settlement-automation-job.v1.

crates/economy/chio-settle/src/automation.rsrust
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum SettlementWatchdogKind {
    EscrowTimeout,
    FinalityObservation,
    BondExpiry,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(deny_unknown_fields)]
pub struct SettlementWatchdogJob {
    pub schema: String,
    pub job_id: String,
    pub kind: SettlementWatchdogKind,
    pub trigger_kind: SettlementAutomationTriggerKind,
    pub chain_id: String,
    pub replay_window_secs: u64,
    pub cron_expression: String,
    pub state_fingerprint: String,
    pub operator_override_required: bool,
    pub reference_id: String,
}

Two builders ship. build_settlement_watchdog_job takes a Web3SettlementDispatchArtifact and emits a FinalityObservation job whose state_fingerprint is a SHA-256 over the dispatch id, chain, escrow, token and beneficiary addresses, operator key hash, settlement path, and amount. build_bond_watchdog_job takes a chain, a vault id, and an expiry, and emits a BondExpiry job fingerprinted over those three. Both refuse an empty cron expression and a zero replay window, and both set operator_override_required: true and trigger_kind: Cron. The third kind, EscrowTimeout, is part of the enum and has no builder in this module.

assess_watchdog_execution compares a job against an execution record and returns one of five refusals, each with its own message: the execution names a different job, it completed before it fired, its observed state fingerprint drifted, its delay exceeded the replay window without the outcome DelayedButSafe, or it suppressed a duplicate without the outcome DuplicateSuppressed. A sixth refusal covers the override:

crates/economy/chio-settle/src/automation.rsrust
if job.operator_override_required && !execution.operator_override_used {
    return Err(SettlementError::Verification(
        "watchdog execution must retain operator override control".to_string(),
    ));
}

A watchdog does not move money

The job detects the stall and classifies the recovery action. The seven SettlementRecoveryAction values are WaitForConfirmations, WaitForDisputeWindow, RetrySubmission, ResubmitAfterReorg, ExecuteRefund, ManualReview, and ExpireBond. The operator's signer is what broadcasts the recovery transaction.

The loss lifecycle

Some settlements never recover. The depositor disappears, the provider takes a chargeback, the chain stays reorganized. Two paired fields on the position carry that: provisional_loss_units and recovered_units.

  1. The receipt settles Failed and its reconciliation state stays open, so the entry carries action_required and its amount lands in provisional_loss_units.
  2. If a bond covers the loss, the operator impairs it. prepare_bond_impair in chio-settle builds the prepared call against the ChioBondVault contract; the operator signs and broadcasts it.
  3. If a credit facility covers the residual instead (see Credit Facilities), the facility absorbs it and the operator documents the drawdown.
  4. Marking the receipt's reconciliation state Reconciled or Ignored clears action_required, which removes the amount from both reserved_units and provisional_loss_units on the next report. The receipt keeps its Failed status; the ledger stops asking for attention.

Billing export

Reconciled cost feeds the billing export in chio-metering under the schema chio.billing-export.v1. Each export is a flat, denormalized record per receipt:

crates/economy/chio-metering/src/export.rsrust
pub struct BillingRecord {
    pub schema: String,
    pub receipt_id: String,
    pub timestamp: u64,
    pub timestamp_iso: String,
    pub session_id: Option<String>,
    pub agent_id: String,
    pub tool_server: String,
    pub tool_name: String,
    pub compute_time_ms: u64,
    pub data_bytes: u64,
    pub cost_units: Option<u64>,
    pub currency: Option<String>,
    pub provider: Option<String>,
}

pub struct BillingExport {
    pub schema: String,
    pub exported_at: u64,
    pub record_count: u64,
    pub total_cost: Option<MonetaryAmount>,
    pub records: Vec<BillingRecord>,
}

create_billing_export takes a slice of CostMetadata and an export timestamp, so the caller decides which receipts enter the export. It rolls up total_cost only while every record it has seen shares one currency; the first record in a second currency sets a mixed flag and the export omits the total rather than printing a misleading sum. provider is the first upstream API provider on the record's cost dimensions, the field the source documents with "openai" and "anthropic", not the payment rail. The operator workflow is at Export Billing Records.


Operator workflow

  • Live monitoring. Run the exposure ledger against an anchor and watch pending_units against governed_max_exposure_units, and reserved_units as the backlog of receipts nobody has closed out.
  • Periodic close. Build the billing export at the billing cadence from the receipts that period covers.
  • Watchdog review. Inspect watchdog executions whose outcome is not Executed. Anything in ManualOverrideRequired is the action queue.
  • Loss documentation. When a failed settlement becomes a confirmed loss, drive the bond impair if one applies, then move the receipt's reconciliation state off Open so the ledger stops reporting it as open exposure.
  • Facility review. Cross-check reconciled volume against the configured facility ceiling and rebalance if utilization stays high.

Worked example: a fifteen-cent underspend

An agent holds a capability bound to an AcpPaymentAdapter rail with settlement_mode = hold_capture. That adapter ships in chio-kernel, reports rail_id() as "acp" and rail_mode() as ReversibleHold, and confirms every monetary transition against an external facilitator. The capability quotes 100 cents per call. This call hits a cheap cache path and costs 85.

The values below are illustrative, in real field names. The two shipped alternatives behave differently in step 2: X402PaymentAdapter and SimPaymentAdapter both return PrepaidFinal, which takes the payment_already_settled branch and skips capture entirely. Chio ships no card-network adapter; for one of those you write your own against the trait, as Settlement Rails describes.

Step 1: quote

The intent is signed with quotedCost of 100 cents and settlementMode of hold_capture.

Step 2: authorize

The kernel builds a PaymentAuthorizeRequest whose reference is the durable operation id, or the request id when the call has no durable journal, and whose amount_units is 100, and calls authorize. The adapter posts it to the facilitator, checks that the response echoes the digest of the exact request, and returns PaymentAuthorization with state Held and metadata carrying {"adapter": "acp", "mode": "shared_payment_token_hold"}. The kernel refuses the authorization if that state disagrees with the rail mode already persisted in the payment journal.

Step 3: execute

The tool runs. The meter records observed_units of 85 with an evidence id from the facilitator.

Step 4: capture

Actual cost is above zero, so the kernel calls capture(authorization_id, 85, "USD", request_id). The adapter binds the operation to the authorization id, the amount, the currency, and the reference, and the facilitator confirms it. There is no second call for the unused 15 cents: capturing below the held amount is what unwinds the rest of the hold. Had the observed cost been zero, the kernel would have called release instead.

Step 5: realize the budget

The kernel reconciles the hold at min(85, 100), which is 85. The 15-cent difference returns to the grant, so the next call sees the headroom.

Step 6: reconciled receipt

The rail result maps onto the receipt through ReceiptSettlement::from_payment_result: payment_reference is the result's transaction_id, and settlement_status is the rail status mapped by to_receipt_status. That mapping matters: Settled, Released, and Refunded all become Settled, while Authorized, Captured, and Pending all become Pending. A facilitator that answers captured rather than settled leaves the receipt pending and the ledger entry asking for action.

json
{
  "governed_transaction": {
    "intent_id": "intent-cache-lookup-001",
    "intent_hash": "[64 hex, GovernedTransactionIntent::binding_hash]",
    "purpose": "cached enrichment lookup",
    "server_id": "enrichment-provider",
    "tool_name": "lookup",
    "metered_billing": {
      "settlementMode": "hold_capture",
      "quote": {
        "quoteId": "quote-cache-001",
        "provider": "enrichment-provider",
        "billingUnit": "lookup",
        "quotedUnits": 1,
        "quotedCost": { "units": 100, "currency": "USD" },
        "issuedAt": 1747776000
      },
      "usageEvidence": {
        "evidenceKind": "acp",
        "evidenceId": "[facilitator usage record id]",
        "observedUnits": 85
      }
    }
  },
  "financial": {
    "grant_index": 0,
    "cost_charged": 85,
    "currency": "USD",
    "budget_remaining": 9915,
    "budget_total": 10000,
    "delegation_depth": 0,
    "root_budget_holder": "did:chio:9b4f...",
    "payment_reference": "[facilitator transaction id]",
    "settlement_status": "settled"
  }
}

If this receipt enters a billing export, it becomes a BillingRecord with cost_units of 85 and currency of "USD". The 15 cents were never charged and never appear.


In the procurement tour

This is station 6 of the Procurement Tour: the buyer kernel reconciles 1000 quoted evidence rows against 850 observed rows and returns the 15-cent difference to the grant.

Picks up from the prior station. The signed bilateral receipt is in hand, and the cost meter has emitted observedUnits of 850 with an evidence id from the SOC 2 review tool.

The quote pinned at station 3 carries billingUnit of evidence-row, quotedUnits of 1000, and quotedCost of 100 cents, under settlementMode of hold_capture. The kernel was holding 100 cents against did:chio:9b4f...'s grant. The record below is an illustrative reconciliation view for this example, in real field names; it is not a signed wire schema:

json
{
  "receipt_id": "[64 hex]",
  "buyer": "did:chio:9b4f...",
  "provider": "did:chio:c87a...",
  "tool": "soc2-review",
  "billing_unit": "evidence-row",
  "quoted": {
    "units": 1000,
    "amount": { "units": 100, "currency": "USD" }
  },
  "observed": {
    "units": 850,
    "amount": { "units": 85, "currency": "USD" },
    "evidence_kind": "vanguard-soc2-review",
    "evidence_id": "vng_soc2_run_8821"
  },
  "charged": {
    "amount": { "units": 85, "currency": "USD" }
  },
  "delta": {
    "amount": { "units": 15, "currency": "USD" },
    "direction": "credit_to_grant"
  },
  "ledger_transition": {
    "currency": "USD",
    "before": { "pending_units": 100, "settled_units": 0, "grant_balance_units": 9900 },
    "after": { "pending_units": 0, "settled_units": 85, "grant_balance_units": 9915 }
  },
  "settlement_status": "pending"
}

Every monetary field is a MonetaryAmount, which is deny_unknown_fields over exactly units and currency. Minor units are the only denomination it carries, so a sub-cent per-row rate has no representation here; the tour prices 1000 rows at 100 cents and lets the unit count do the dividing.

Continue at the next station, where the buyer kernel selects the configured Web3 rail and builds the dispatch record.

See also

  • Settlement Rails and On-Chain Settlement for the rail-side mechanics this page assumes: the rail kinds, the adapters that ship, and the chain-side dispatch.
  • Audit APIs for how an auditor reads the receipts this cycle produces.
  • Credit Facilities for how the exposure ledger interacts with sized credit lines and how losses cascade through bonds and facilities.
  • Claims for the dispute path when an observed cost is contested.
  • Tool Pricing for how quoted cost is constructed before the cycle starts.
  • Export Billing Records for the operator-facing export workflow.