Chio/Docs

LearnAnatomy of a Governed Call

Capabilities

This page explains capability-token fields, scope, delegation, subject binding, and revocation for governed calls.

What Capabilities Are

A signed capability token grants scoped authority to an agent. The agent presents the token when requesting a governed call. The autonomous-commerce page explains capabilities as spending authorizations; the Trust Model page explains its trust assumptions. This page describes the token fields, bindings, and validation rules.

A capability token answers four questions:

  1. Who issued it? The issuer public key identifies the Capability Authority or delegating agent.
  2. Who is it for? The subject public key binds the token to a specific agent.
  3. What does it allow? The scope declares which tools, resources, and prompts the bearer can use.
  4. When does it expire? issued_at and expires_at timestamps bound the token's validity window.

CapabilityToken Structure

The CapabilityToken struct contains fourteen fields. The signature covers the canonical JSON (per RFC 8785) of all other fields.

CapabilityToken
pub struct CapabilityToken {
    /// Versioned wire schema id. Tokens that omit it default to
    /// "chio.capability.v1".
    pub schema: String,
    /// Unique token ID (UUIDv7 recommended, used for revocation).
    pub id: String,
    /// Capability Authority (or delegating agent) that issued this token.
    pub issuer: PublicKey,
    /// Agent this capability is bound to (DPoP sender constraint).
    pub subject: PublicKey,
    /// What this token authorizes.
    pub scope: ChioScope,
    /// Unix timestamp (seconds) when the token was issued.
    pub issued_at: u64,
    /// Unix timestamp (seconds) when the token expires.
    pub expires_at: u64,
    /// Ordered list of delegation links from the root CA to this token.
    pub delegation_chain: Vec<DelegationLink>,
    /// Signing algorithm. Absent means Ed25519 (the default); other values
    /// select P-256, P-384, or the post-quantum hybrid.
    pub algorithm: Option<SigningAlgorithm>,
    /// Typed caveats that further restrict how the token may be used.
    pub caveats: Vec<Caveat>,
    /// High-level attenuation request exposed on attenuated tokens.
    pub scope_attenuations: Option<Vec<Attenuation>>,
    /// Wire witness proving a child scope attenuates its parent.
    pub attenuation_proof: Option<AttenuationProof>,
    /// Fixed-point sub-agent budget share in basis points. Values above
    /// 10000 are rejected by validation.
    pub budget_share_bps: Option<u16>,
    /// Signature over canonical JSON of all fields above.
    pub signature: Signature,
}

Four fields control delegation and attenuation. scope_attenuations records the high-level narrowing requested when a child token is minted; attenuation_proof is the wire witness the kernel checks to confirm that the child scope is a subset of its parent; budget_share_bps pins a sub-agent's share of a parent budget in basis points (values above 10000 are rejected); and caveats holds typed side conditions that narrow use further. The schema field is the versioned wire identifier, defaulting to chio.capability.v1.

Signing algorithms

Ed25519 is the default signing algorithm, and Ed25519 tokens omit the algorithm field on the wire. The field also selects P-256, P-384, or the post-quantum hybrid (ML-DSA-65) when a deployment needs one of them. Canonical JSON serialization follows RFC 8785, ensuring deterministic byte ordering across implementations.

ChioScope

The scope field is a ChioScope containing three grant vectors: one for tools, one for resources, and one for prompts. All canonical JSON serialization (used for signing) follows RFC 8785.

ChioScope
pub struct ChioScope {
    /// Individual tool grants.
    pub grants: Vec<ToolGrant>,
    /// Individual resource grants.
    pub resource_grants: Vec<ResourceGrant>,
    /// Individual prompt grants.
    pub prompt_grants: Vec<PromptGrant>,
}

The most common grant type is ToolGrant, which authorizes invocations of a specific tool on a specific server.

ToolGrant

Each ToolGrant specifies the server, tool, allowed operations, and optional constraints on invocation count, cost, and proof-of-possession:

ToolGrant
pub struct ToolGrant {
    /// Which tool server (by server_id from the manifest).
    pub server_id: String,
    /// Which tool on that server.
    pub tool_name: String,
    /// Allowed operations.
    pub operations: Vec<Operation>,
    /// Parameter constraints that narrow the tool's input space.
    pub constraints: Vec<Constraint>,
    /// Maximum number of invocations allowed under this grant.
    pub max_invocations: Option<u32>,
    /// Maximum monetary cost per single invocation.
    pub max_cost_per_invocation: Option<MonetaryAmount>,
    /// Maximum aggregate monetary cost across all invocations.
    pub max_total_cost: Option<MonetaryAmount>,
    /// If true, the kernel requires a valid DPoP proof for every invocation.
    pub dpop_required: Option<bool>,
}

The MonetaryAmount type carries a units field (minor units, e.g., cents for USD) and a currency field (ISO 4217 code). No floats, no rounding.


Time-Bounded Tokens

Every capability token has an issued_at and expires_at timestamp, both Unix seconds. There are no permanent grants. The kernel rejects any token where the current time falls outside this window.

Short-lived tokens limit the effect of a compromise. A stolen token expires on its own; revocation can invalidate it sooner. The shorter interval limits its use.

Subject Binding

The subject field binds a token to a specific agent via its Ed25519 public key. A token issued to agent A cannot be used by agent B. The kernel verifies the subject matches the requesting agent's identity before processing any guards. DPoP is the per-invocation replay-resistance hardening on top of this subject binding.

DPoP (Demonstration of Proof of Possession)

Subject binding alone proves that a token was intended for an agent. DPoP proves the agent currently holds the corresponding private key. When dpop_required is set on a grant, the kernel requires a fresh, signed DPoP proof with each invocation.

This sender constraint limits replay of an intercepted token. An attacker who lacks the agent's private key cannot produce the required signed DPoP proof.

DPoP is per-grant

DPoP is configured at the grant level, not the token level. A single token can have some grants that require DPoP and others that do not. Use DPoP for high-value operations like writes and payments.

Delegation Chains

An agent holding a token can delegate a child token to another agent. The child token must attenuate, or narrow, the parent's permissions.

Each link names the delegator, delegatee, and applied constraints. When a sub-agent spends under a delegated capability, the receipt carries the root_budget_holder and delegation depth, which traces delegated authority and cost to the original grant. reconciliation settles against.

A delegated token must have:

  • A scope that is a subset of the parent token's scope
  • An expiration that is no later than the parent's expiration
  • A max_invocations value that is no greater than the parent's
  • A max_cost_per_invocation value that is no greater than the parent's
  • A max_total_cost value that is no greater than the parent's

Lean 4 theorem family P1, Capability Monotonicity, proves these attenuation properties over the claim-registry bounded model: child scope is a subset, child expiry is no later, and child max_invocations is no greater, child max_cost_per_invocation and max_total_cost are each no greater. The theorem does not cover the full Rust runtime.

The delegation_chain field records the ordered list of delegation links from the root CA to the current token. The kernel walks this chain to verify that every link is a valid attenuation of its parent.

delegated-capability.yaml
# Root token: issued by CA, grants read_file + write_file
capability:
  id: cap_root_a1b2
  issuer: "3f9a1c7b2e8d..."       # CA authority key
  subject: "a4d8e2f10b9c..."      # orchestrator key
  scope:
    grants:
      - server_id: srv-files
        tool_name: read_file
        operations: [invoke]
        max_invocations: 100
      - server_id: srv-files
        tool_name: write_file
        operations: [invoke]
        max_invocations: 50
  issued_at: 1744536000
  expires_at: 1744539600
  delegation_chain: []
  signature: "e5f6a7b8c9d0..."

# Delegated token: orchestrator → research-agent
# Attenuated: only read_file, fewer invocations, shorter window
capability:
  id: cap_child_c3d4
  issuer: "a4d8e2f10b9c..."      # orchestrator key
  subject: "b7c8d9e0f1a2..."     # research-agent key
  scope:
    grants:
      - server_id: srv-files
        tool_name: read_file
        operations: [invoke]
        max_invocations: 25
  issued_at: 1744536000
  expires_at: 1744537800
  delegation_chain:
    - capability_id: cap_root_a1b2
      delegatee: "b7c8d9e0f1a2..."   # research-agent key
      attenuations: []
      timestamp: 1744536000
      signature: "c1d2e3f4a5b6..."
  signature: "f9e8d7c6b5a4..."

In this example, the orchestrator narrows its authority before passing it to the research agent. The child token cannot invoke write_file (removed from scope), has a lower invocation cap (25 vs. 100), and expires 30 minutes sooner. The kernel verifies all of this by walking the delegation chain.


Revocation

The Capability Authority can revoke any token at any time by its id. Revocation cascades through the entire delegation subtree: revoking a parent token invalidates its descendants. Lean 4 theorem family P2, Revocation Completeness, proves that revoked tokens and revoked presented ancestors cannot pass over its bounded model. It does not prove the full runtime.

The kernel checks the revocation list during token validation, before any guards run. A revoked token is treated the same as an expired token: the request is denied immediately.

Revocation requires a new token

The revocation flow does not reinstate a revoked token. Issue a new token if the agent needs access again.

Example Capability Token

A complete capability token granting a research agent read-only filesystem access with a per-invocation cost cap:

capability-token.yaml
capability:
  id: cap_7f3a9b2c-e91d-4a5f-b8c1-d6e7f8a9b0c1
  issuer: "9c7b3f1a2e8d..."
  subject: "a4d8e2f10b9c..."
  scope:
    grants:
      - server_id: srv-files
        tool_name: read_file
        operations: [invoke]
        constraints:
          - param: path
            pattern: "./workspace/**"
        max_invocations: 50
        max_cost_per_invocation:
          units: 10
          currency: USD
        max_total_cost:
          units: 200
          currency: USD
        dpop_required: false
      - server_id: srv-files
        tool_name: list_directory
        operations: [invoke]
        max_invocations: 100
    resource_grants: []
    prompt_grants: []
  issued_at: 1744536000
  expires_at: 1744539600
  delegation_chain: []
  signature: "e5f6a7b8c9d0..."

This token allows the agent to call read_file (up to 50 times, constrained to paths under ./workspace/, max $0.10 per call, max $2.00 total) and list_directory (up to 100 times). It expires one hour after issuance.


Next Steps

  • Autonomous Commerce: capabilities as spending authorizations
  • Guards: how the guard pipeline enforces policy on every invocation
  • Receipts: signed records of kernel decisions
  • Economics: budgets, metering, and settlement