LearnAnatomy of a Governed Call
Receipts
The kernel signs a receipt for each mediated decision, recording the request, decision, guard evidence, and financial data.
What receipts record
A receipt is a signed JSON document for an agent's tool request. It records the request, the kernel's decision, and the guard evidence. For priced calls, it also records attribution and cost. See Autonomous Commerce for how receipts participate in billing. The kernel produces one receipt per decision, including denials. Receipts answer four questions:
- Who: which agent, using which capability token, on which tool server
- What: which tool, with what parameters
- When: Unix timestamp of the decision
- Result: the kernel's decision and the evidence from each guard that evaluated the request
Receipt coverage
allow, deny, cancelled, or incomplete. If the kernel mediated a tool call, the log contains a receipt. The same signed format also records trace observations and advisory evaluations without a kernel decision.ChioReceipt fields
The ChioReceipt struct contains all fields necessary for independent verification:
pub struct ChioReceipt {
/// Content-addressed receipt ID derived from the canonical receipt body.
pub id: String,
/// Unix timestamp (seconds) when the receipt was created.
pub timestamp: u64,
/// ID of the capability token that was exercised (or presented).
pub capability_id: String,
/// Tool server that handled the invocation.
pub tool_server: String,
/// Tool that was invoked (or attempted).
pub tool_name: String,
/// The action that was evaluated.
pub action: ToolCallAction,
/// The Kernel's decision. Present only for mediated decisions.
pub decision: Option<Decision>,
/// Signed receipt semantic kind.
pub receipt_kind: ReceiptKind,
/// Signed runtime boundary class.
pub boundary_class: BoundaryClass,
/// Signed observation outcome for trace and advisory records.
pub observation_outcome: Option<ObservationOutcome>,
/// Signed tool-origin classification.
pub tool_origin: ToolOrigin,
/// Signed redaction mode.
pub redaction_mode: RedactionMode,
/// Signed actor attribution chain.
pub actor_chain: Vec<ActorRef>,
/// SHA-256 hash of the evaluated content for this receipt.
pub content_hash: String,
/// SHA-256 hash of the policy that was applied.
pub policy_hash: String,
/// Per-guard evidence collected during evaluation.
pub evidence: Vec<GuardEvidence>,
/// Optional receipt metadata for stream/accounting details.
pub metadata: Option<serde_json::Value>,
/// Strength of kernel mediation that produced this receipt.
pub trust_level: TrustLevel,
/// Tenant identifier for multi-tenant deployments (None in single-tenant).
pub tenant_id: Option<String>,
/// BBS projection version bound into the id when BBS material is present.
pub bbs_projection_version: Option<String>,
/// The Kernel's public key (for verification without out-of-band lookup).
pub kernel_key: PublicKey,
/// Optional BBS material for selective disclosure over this receipt.
pub bbs_signature: Option<BbsReceiptSignature>,
/// Signing algorithm. Informational only; verification dispatches off the
/// self-describing signature encoding.
pub algorithm: Option<SigningAlgorithm>,
/// Signature over canonical JSON of the receipt body.
pub signature: Signature,
}The id is content-addressed: it is a hash of the canonical receipt body, not a random UUID, so the same body always yields the same id and any edit changes it. The decision is optional: it is present for mediated decisions and absent on the trace and advisory receipt kinds described next. See the Receipt Format reference for the complete field-by-field shape and wire semantics.
Receipt kinds and boundary class
Six signed fields identify the record type and the portion of the call Chio observed or enforced. A verifier can assess the record's boundary from the signed fields.
| Field | Variants | Meaning |
|---|---|---|
receipt_kind | MediatedDecision, TraceObservation, AdvisoryEvaluation | Whether the record is an authorization decision or an observation the kernel logged without mediating |
boundary_class | Prevent, DetectOnly, AdvisoryOnly, CannotSee | What Chio could enforce on this call: full prevention down to no visibility at all |
observation_outcome | Observed, Evaluated, Dropped | For non-mediated records: whether the observation was seen, evaluated, or dropped |
tool_origin | CallerExecuted, HostExecutedProviderReported, HostExecutedUnmediated | Where the tool effect ran relative to Chio |
redaction_mode | None, Summary, Redacted | How much of the signed detail was redacted before storage or export |
actor_chain | Vec<ActorRef> | The signed attribution chain of actors behind the request |
The two BBS fields, bbs_projection_version and bbs_signature, carry optional BBS+ material for selective disclosure. A holder can present selected receipt fields without revealing the rest of the receipt.
Decision variants
The Decision enum captures four possible outcomes. On the wire the variants serialize with lowercase tags: allow, deny, cancelled, and incomplete. The deny variant carries both reason and guard (the name of the blocking guard).
#[serde(tag = "verdict", rename_all = "snake_case")]
pub enum Decision {
/// The tool call was allowed and executed.
Allow,
/// The tool call was denied.
Deny {
/// Human-readable reason for the denial.
reason: String,
/// The guard or validation step that triggered the denial.
guard: String,
},
/// The tool call was interrupted by explicit cancellation.
Cancelled {
/// Human-readable reason for the cancellation.
reason: String,
},
/// The tool call did not reach a complete terminal result.
Incomplete {
/// Human-readable reason for the incomplete terminal state.
reason: String,
},
}Each Deny records the guard that rejected the call, allowing a verifier to identify the rule.
ToolCallAction fields
The action field captures the parameters that were passed to the tool, along with a self-verifying hash:
pub struct ToolCallAction {
/// The parameters that were passed to the tool (or attempted).
pub parameters: serde_json::Value,
/// SHA-256 hash of the canonical JSON of parameters.
pub parameter_hash: String,
}The parameter_hash is computed over the canonical JSON (RFC 8785) of the parameters. This allows verification that the parameters in the receipt have not been tampered with, independent of the receipt signature.
Guard evidence
Each receipt includes an evidence array documenting what each guard reported during evaluation:
pub struct GuardEvidence {
/// Name of the guard (e.g. "forbidden-path").
pub guard_name: String,
/// Whether the guard passed (true) or denied (false).
pub verdict: bool,
/// Optional details about the guard's decision.
pub details: Option<String>,
}The pipeline stops at the first denial, so the evidence array omits unevaluated guards. A denial receipt may contain fewer entries than the total number of enabled guards. An allow receipt includes evidence from every guard that was evaluated against the request.
[
{"guard_name": "forbidden-path", "verdict": true, "details": null},
{"guard_name": "path-allowlist", "verdict": true, "details": null},
{"guard_name": "shell-command", "verdict": true, "details": null},
{"guard_name": "egress-allowlist", "verdict": true, "details": null},
{"guard_name": "mcp-tool", "verdict": true, "details": null},
{"guard_name": "secret-leak", "verdict": true, "details": "no secrets detected"},
{"guard_name": "patch-integrity", "verdict": true, "details": null},
{"guard_name": "velocity", "verdict": true, "details": null}
]Content hashing
The content_hash field is a SHA-256 hash of the evaluated content: typically the tool's result for allow decisions, or the request body for deny decisions. This creates a tamper-evident link between the receipt and the actual data that was processed.
# Content hash is computed as:
content_hash = SHA-256(tool_result_bytes)
# Example: tool returned {"ok": true}
content_hash = "sha256:a1b2c3d4..." # SHA-256 of the JSON bytesPolicy hashing
The policy_hash field is a SHA-256 hash of the policy document (HushSpec) that was applied when evaluating the request. This binds the receipt to a specific policy version, enabling auditors to verify that the correct policy was in effect at the time of the decision.
Policy pinning for compliance
Signing
Receipts are signed with the kernel's ed25519 private key. The signature covers the canonical JSON (RFC 8785) serialization of all fields except the signature itself, known as the ChioReceiptBody.
impl ChioReceipt {
/// Sign a receipt body with the Kernel's keypair.
pub fn sign(body: ChioReceiptBody, keypair: &Keypair) -> Result<Self> {
let (signature, _bytes) = keypair.sign_canonical(&body)?;
Ok(Self {
id: body.id,
timestamp: body.timestamp,
// ...all body fields...
signature,
})
}
}The signing process:
- Construct the
ChioReceiptBody(all fields except signature) - Serialize the body to canonical JSON per RFC 8785 (deterministic key ordering, no insignificant whitespace)
- Sign the canonical bytes with the kernel's ed25519 private key
- Attach the resulting signature to produce the final
ChioReceipt
Canonical JSON defines signed bytes
Receipt verification
Third parties can verify receipts independently without contacting the kernel. The receipt is self-contained: it includes the kernel's public key.
impl ChioReceipt {
/// Verify the receipt signature against the embedded kernel key.
pub fn verify_signature(&self) -> Result<bool> {
let body = self.body();
self.kernel_key.verify_canonical(&body, &self.signature)
}
}Verification steps:
- Extract the
ChioReceiptBodyfrom the receipt (all fields except signature) - Serialize the body to canonical JSON (RFC 8785)
- Verify the ed25519 signature using the embedded
kernel_key - Optionally verify the
kernel_keyagainst a trusted key registry to confirm the receipt was produced by a recognized kernel
You can also independently verify the parameter_hash inside the action field by recomputing the SHA-256 of the canonical JSON of the parameters:
impl ToolCallAction {
/// Verify that parameter_hash matches the canonical hash of parameters.
pub fn verify_hash(&self) -> Result<bool> {
let canonical = canonical_json_bytes(&self.parameters)?;
let expected = sha256_hex(&canonical);
Ok(self.parameter_hash == expected)
}
}Receipt example
Here is a complete receipt as it appears in the receipt log. This example shows an allowed file read operation:
{
"id": "rcpt-001",
"timestamp": 1710000000,
"capability_id": "cap-001",
"tool_server": "srv-files",
"tool_name": "file_read",
"action": {
"parameters": {
"path": "/app/src/main.rs"
},
"parameter_hash": "sha256:e3b0c44298fc1c14..."
},
"decision": {
"verdict": "allow"
},
"receipt_kind": "mediated_decision",
"boundary_class": "prevent",
"tool_origin": "caller_executed",
"redaction_mode": "none",
"content_hash": "sha256:d7e8f9a0b1c2d3e4...",
"policy_hash": "abc123def456",
"evidence": [
{"guard_name": "forbidden-path", "verdict": true, "details": null},
{"guard_name": "secret-leak", "verdict": true, "details": "no secrets detected"}
],
"metadata": {
"sandbox": {
"enforced": true
}
},
"trust_level": "mediated",
"kernel_key": "9c7b3f2e8a1c4d5b6f0a9e8d7c6b5a4f...",
"signature": "e5f6a7b8c9d0e1f2a3b4c5d6e7f8091a..."
}And a denied request:
{
"id": "rcpt-002",
"timestamp": 1710000005,
"capability_id": "cap-001",
"tool_server": "srv-files",
"tool_name": "file_read",
"action": {
"parameters": {
"path": "/etc/passwd"
},
"parameter_hash": "sha256:f4a5b6c7d8e9f0a1..."
},
"decision": {
"verdict": "deny",
"reason": "path /etc/passwd is forbidden",
"guard": "forbidden-path"
},
"receipt_kind": "mediated_decision",
"boundary_class": "prevent",
"tool_origin": "caller_executed",
"redaction_mode": "none",
"content_hash": "sha256:0000000000000000...",
"policy_hash": "abc123def456",
"evidence": [
{"guard_name": "forbidden-path", "verdict": false, "details": "path /etc/passwd matches pattern /etc/passwd"}
],
"trust_level": "mediated",
"kernel_key": "9c7b3f2e8a1c4d5b6f0a9e8d7c6b5a4f...",
"signature": "a1b2c3d4e5f6071829303a4b5c6d7e8f..."
}Financial metadata
When a tool call exercises a monetary grant, the receipt's metadata field includes a FinancialReceiptMetadata record under the "financial" key. The full field set is:
| Field | Type | Purpose |
|---|---|---|
grant_index | u32 | Index of the matched grant within the capability token |
cost_charged | u64 | Cost actually charged (minor units) |
currency | String | ISO 4217 currency code of the charge |
budget_remaining | u64 | Remaining budget after this invocation |
budget_total | u64 | Total grant budget |
delegation_depth | u32 | Depth in the delegation chain (0 = root) |
root_budget_holder | String | Identity of the root budget holder |
payment_reference | Option<String> | External payment reference for settlement |
settlement_status | SettlementStatus | One of NotApplicable, Pending, Settled, Failed |
cost_breakdown | Option<Value> | Optional itemized cost breakdown reported by the tool |
oracle_evidence | Option<OracleConversionEvidence> | Cross-currency conversion evidence, when applicable |
attempted_cost | Option<u64> | Cost that would have been charged (present on denials) |
The SettlementStatus enum has four variants: NotApplicable (free-tier grant), Pending (awaiting settlement), Settled (completed), and Failed (e.g., cost overrun).
{
"metadata": {
"financial": {
"grant_index": 0,
"cost_charged": 150,
"currency": "USD",
"budget_remaining": 850,
"budget_total": 1000,
"delegation_depth": 1,
"root_budget_holder": "agent-root-001",
"payment_reference": "ref-abc123",
"settlement_status": "pending",
"cost_breakdown": {"compute": 100, "io": 50},
"oracle_evidence": null,
"attempted_cost": null
}
}
}On denial receipts caused by budget exhaustion, attempted_cost records the cost that would have been charged while cost_charged is zero. For cross-currency invocations, the oracle_evidence field holds an OracleConversionEvidence record: the base and quote currencies, the integer rate_numerator/rate_denominator pair, the oracle source and feed_address, an updated_at timestamp with its max_age_seconds/cache_age_seconds freshness bounds, and the converted and original cost in units. The record is itself a signed oracle attestation: when the quote is signed it carries its own oracle_public_key and signature, so a verifier can check the rate against the oracle's key without trusting the kernel. See Economics for the full struct.
FinancialReceiptMetadata is the settled summary. Alongside it the same module defines the budget-hold lineage records that back it ( FinancialBudgetHoldAuthorityMetadata, FinancialBudgetAuthorizeReceiptMetadata, and FinancialBudgetTerminalReceiptMetadata ) which capture the authorize, lease, and terminal steps of the budget hold that check_and_increment_budget opens and later reconciles. Economics walks that lifecycle.
Storage
Receipts are stored in an append-only SQLite log. The storage layer accepts inserts and rejects updates and deletes. The log is Merkle-committed in batched checkpoints, and inclusion proofs let any individual receipt be verified without replaying the entire log.
Protect the receipt log
Querying
The chio receipt list command queries the receipt log with filtering support. Every read requires an explicit tenant boundary: pass --tenant <id> to scope the listing to one tenant, or --admin-all to read across all tenants as an administrative operation. With neither, the command fails closed. Output is JSON Lines: one receipt per line.
# List all receipts for a specific tool server
$ chio receipt list --admin-all --tool-server srv-files
# Filter by tool name and outcome
$ chio receipt list --admin-all --tool-name file_read --outcome allow
# Time range: --since / --until take Unix seconds
$ chio receipt list --admin-all --since 1735689600 --until 1735776000
# Filter by cost (minor currency units)
$ chio receipt list --admin-all --min-cost 100 --max-cost 500
# Page through results
$ chio receipt list --admin-all --limit 20 --cursor 1042
# Combine filters
$ chio receipt list \
--admin-all \
--tool-server srv-files \
--tool-name file_read \
--outcome deny \
--since 1735689600| Filter | Flag | Description |
|---|---|---|
| Read boundary | --tenant / --admin-all | Required: scope to one tenant, or read across all tenants (mutually exclusive) |
| Capability | --capability | Filter by capability token identifier |
| Tool server | --tool-server | Filter by tool server identifier |
| Tool name | --tool-name | Filter by tool name |
| Outcome | --outcome | Filter by decision: allow, deny, cancelled, or incomplete |
| Start time | --since | Receipts with timestamp ≥ this Unix-seconds value |
| End time | --until | Receipts with timestamp ≤ this Unix-seconds value |
| Min cost | --min-cost | Minimum cost charged, minor units (financial receipts) |
| Max cost | --max-cost | Maximum cost charged, minor units (financial receipts) |
| Page size | --limit | Maximum receipts per page (default 50) |
| Cursor | --cursor | Pagination cursor: sequence value to start after |
SIEM export
Receipts can be exported to external security information and event management (SIEM) systems for centralized monitoring and alerting. chio-siem ships exporters for seven backends: Splunk HEC, Elasticsearch, CEF, Datadog, Sumo Logic, OCSF, and a generic webhook. Each implements a common Exporter trait and is configured with a Rust config struct. SIEM exporters are not configured through policy YAML. Each exporter requires TLS and rejects a plain http:// endpoint.
Splunk HEC
The Splunk HTTP Event Collector exporter streams receipt batches as newline-separated JSON event envelopes, each wrapping the full ChioReceipt under the event key.
pub struct SplunkConfig {
/// Splunk HEC endpoint URL (e.g. "https://splunk.example.com:8088").
pub endpoint: String,
/// HEC authentication token.
pub hec_token: String,
/// Splunk sourcetype for all exported events. Default: "chio:receipt".
pub sourcetype: String,
/// Optional index. Omit to use the HEC token's default index.
pub index: Option<String>,
/// Optional host field sent with each event envelope.
pub host: Option<String>,
/// HTTP request timeout. Default: 30 seconds.
pub timeout: Duration,
/// Typed HTTP egress contract enforced on every dispatch and redirect.
pub egress_contract: Option<HttpEgressContract>,
}Elasticsearch
The Elasticsearch exporter posts receipt batches through the /_bulk API, using receipt.id as the document _id so retries are idempotent.
pub struct ElasticConfig {
/// Elasticsearch endpoint URL (e.g. "https://es.example.com:9200").
pub endpoint: String,
/// Target index for all exported receipts. Default: "chio-receipts".
pub index_name: String,
/// Authentication method and credentials.
pub auth: ElasticAuthConfig,
/// HTTP request timeout. Default: 30 seconds.
pub timeout: Duration,
/// Typed HTTP egress contract enforced on every dispatch and redirect.
pub egress_contract: Option<HttpEgressContract>,
}
pub enum ElasticAuthConfig {
/// Sends `Authorization: ApiKey <key>`.
ApiKey(String),
/// HTTP Basic auth; the password is zeroized on drop.
Basic { username: String, password: Zeroizing<String> },
}How receipts are used
Security teams inspect guard evidence and request history. Finance teams reconcile charges against budgets. Compliance teams compare the policy hash with the policy in force for a decision. Reputation systems can derive denial, spending, and cancellation rates for agents and tool servers. These uses share the signed receipt format.
For the budget lifecycle, see Economics which explains how a priced receipt reconciles against a budget. Session-Aware Guards explains how to use deny-rate and spending signals from receipt records.
Receipt DAG: child request records
Nested operations within a parent tool call produce ChildRequestReceipt records. These link back to the parent via parent_request_id, forming a DAG of parent/child receipt chains across nested operations. Each child records its operation kind (e.g., CreateMessage) and terminal state: Completed, Cancelled, or Incomplete. A denied request still reaches a terminal Completed state; the terminal state tracks lifecycle completion, not the authorization verdict.
pub struct ChildRequestReceipt {
pub id: String,
pub timestamp: u64,
pub session_id: SessionId,
pub parent_request_id: RequestId,
pub request_id: RequestId,
pub operation_kind: OperationKind,
pub terminal_state: OperationTerminalState,
pub outcome_hash: String,
pub policy_hash: String,
pub metadata: Option<serde_json::Value>,
pub kernel_key: PublicKey,
pub signature: Signature,
}Child request receipts are signed and verified identically to standard receipts, maintaining the same non-repudiation guarantees across nested operations.