Chio/Docs

Mandates & Capabilities

A capability token is the only thing that lets an agent invoke a tool. There is no ambient authority, no role lookup, no implicit trust. The kernel demands a signed, scoped, time-bounded token on every request and denies the call if any check fails. This page is the operator contract for that surface: which fields the token carries, which grants and constraints narrow it, how delegation chains attenuate scope, and what verification actually checks.

Source

Verified against crates/chio-core-types/src/capability.rs and crates/chio-kernel-core/src/capability_verify.rs. Field names, enum variants, and defaults are taken from those files. If this page disagrees with source, source wins.

CapabilityToken

The on-the-wire token shape:

crates/chio-core-types/src/capability.rs
pub struct CapabilityToken {
    pub id: String,                          // UUIDv7 recommended; used for revocation
    pub issuer: PublicKey,                   // Capability authority or delegating agent
    pub subject: PublicKey,                  // Agent this token is bound to (DPoP sender constraint)
    pub scope: ChioScope,                    // What the token authorizes
    pub issued_at: u64,                      // Unix seconds
    pub expires_at: u64,                     // Unix seconds
    pub delegation_chain: Vec<DelegationLink>,
    pub algorithm: Option<SigningAlgorithm>, // Absent means Ed25519 (legacy default)
    pub signature: Signature,                // Over canonical JSON of all fields above
}

Three properties matter for operators:

  • Signature covers everything except itself. The body type is CapabilityTokenBody and its canonical JSON is the signing input. Tampering with any field (scope, expiry, delegation_chain) invalidates the signature.
  • Subject binding is enforced via DPoP. The token names the agent it is intended for. The kernel checks DPoP proof against this subject when dpop_required is set on a grant.
  • Time bounds are inclusive of issued_at and exclusive of expires_at. A token is valid when issued_at <= now < expires_at.

ChioScope and Grant Variants

ChioScope is a flat container holding three optional grant lists. Empty lists are skipped from canonical JSON, so a tool-only token round-trips byte-identical to its pre-resource-grant ancestors.

crates/chio-core-types/src/capability.rs
pub struct ChioScope {
    pub grants: Vec<ToolGrant>,
    pub resource_grants: Vec<ResourceGrant>,
    pub prompt_grants: Vec<PromptGrant>,
}

ToolGrant

The most heavily used grant. Each ToolGrant authorizes a single tool on a single server, with optional invocation, cost, and constraint attenuation:

crates/chio-core-types/src/capability.rs
pub struct ToolGrant {
    pub server_id: String,
    pub tool_name: String,
    pub operations: Vec<Operation>,
    pub constraints: Vec<Constraint>,
    pub max_invocations: Option<u32>,
    pub max_cost_per_invocation: Option<MonetaryAmount>,
    pub max_total_cost: Option<MonetaryAmount>,
    pub dpop_required: Option<bool>,
}

server_id and tool_name support a literal "*" as a parent wildcard during delegation, but a leaf token must name a concrete tool.

ResourceGrant

crates/chio-core-types/src/capability.rs
pub struct ResourceGrant {
    pub uri_pattern: String,
    pub operations: Vec<Operation>,
}

URI pattern matching is literal-equal, full-match *, or trailing-prefix prefix*. There is no glob engine; if you need richer patterns, narrow at issuance time.

PromptGrant

crates/chio-core-types/src/capability.rs
pub struct PromptGrant {
    pub prompt_name: String,
    pub operations: Vec<Operation>,
}

Operations

The Operation enum is shared across all three grant kinds:

crates/chio-core-types/src/capability.rs
pub enum Operation {
    Invoke,       // Execute the tool
    ReadResult,   // Read a prior invocation result
    Read,         // Read a resource
    Subscribe,    // Subscribe to resource updates
    Get,          // Retrieve a prompt
    Delegate,     // Delegate this grant to another agent
}

A grant with Delegate in its operations list is the precondition for further delegation. Without it, the holder cannot extend the chain.


Constraint Variants

Constraints narrow tool parameters or impose runtime requirements. They serialize via #[serde(tag = "type", content = "value")], so the wire form is {"type": "path_prefix", "value": "/var/log"}. The full enum, taken from source:

VariantPayloadSemantics
PathPrefixStringFile path parameter must start with this prefix
DomainExactStringNetwork domain must match exactly
DomainGlobStringNetwork domain matches a glob
RegexMatchStringParameter must match regex
MaxLengthusizeString parameter length cap
MaxArgsSizeusizeSerialized arg payload byte cap
GovernedIntentRequiredunitRequest must carry a governed transaction intent
RequireApprovalAbove{ threshold_units: u64 }Approval token required above threshold
SellerExactStringCommerce approval bound to this seller
MinimumRuntimeAssuranceRuntimeAssuranceTierFloor on runtime attestation tier
MinimumAutonomyTierGovernedAutonomyTierFloor on bonded autonomy tier
TableAllowlistVec<String>SQL tables this grant may reference
ColumnDenylistVec<String>Forbidden table.column identifiers
MaxRowsReturnedu64Row cap, enforced post-invocation
OperationClassSqlOperationClassread_only / read_write / admin
AudienceAllowlistVec<String>Allowed recipient channels or IDs
ContentReviewTierContentReviewTiernone / basic / strict review demand
MaxTransactionAmountUsdString (decimal)Per-transaction USD cap
RequireDualApprovalboolTwo-party approval required
ModelConstraint{ allowed_model_ids, min_safety_tier }Constrain executing model identity / safety tier
MemoryStoreAllowlistVec<String>Memory stores this grant may write to
MemoryWriteDenyPatternsVec<String>Regex patterns that block writes
Custom(String, String)Extensibility key / value pair

Constraint subset semantics during delegation

A child grant must contain every constraint of its parent. The check is membership equality on the constraint list, not semantic comparison. A child cannot "narrow" a parent PathPrefix("/") by replacing it with PathPrefix("/etc"); the parent constraint must appear unchanged in the child, alongside any further restrictions.

Signing Algorithms

Three algorithms ship today. Backward-compatibility is preserved via the optional algorithm envelope field: when absent, consumers MUST treat the algorithm as Ed25519 so legacy artifacts deserialize unchanged.

crates/chio-core-types/src/capability.rs
pub enum SigningAlgorithm {
    Ed25519,   // Default, non-FIPS. No feature flag required.
    P256,      // ECDSA P-256 / SHA-256. Requires the `fips` feature.
    P384,      // ECDSA P-384 / SHA-384. Requires the `fips` feature.
}

Verification dispatches off the self-describing prefix on PublicKey and Signature, not the envelope field. The algorithm field is informational. Building a chio binary without the fips feature causes P-256 and P-384 verification to return Ok(false) rather than attempt the algorithm.


Delegation Chains

Delegation lets one agent narrow its own capability and hand the attenuated form to another agent. The chain is recorded inline on the leaf token:

crates/chio-core-types/src/capability.rs
pub struct DelegationLink {
    pub capability_id: String,    // ID of the ancestor token at this step
    pub delegator: PublicKey,
    pub delegatee: PublicKey,
    pub attenuations: Vec<Attenuation>,  // How the scope was narrowed
    pub timestamp: u64,
    pub signature: Signature,     // Delegator signs canonical(body)
}

Verification reads the chain root-to-leaf:

  1. The first link's delegator must equal the issuer of the original capability (the root of trust).
  2. Each subsequent link's delegator must equal the previous link's delegatee.
  3. Each link's signature is verified against its delegator key.
  4. The leaf token's subject must equal the final link's delegatee.
  5. The leaf scope must be a subset of every ancestor scope. The subset check uses ChioScope::is_subset_of, which composes per-grant checks over tools, resources, and prompts.

Verification

The portable, IO-free check lives in chio-kernel-core::capability_verify:

crates/chio-kernel-core/src/capability_verify.rs
pub fn verify_capability(
    token: &CapabilityToken,
    trusted_issuers: &[PublicKey],
    clock: &dyn Clock,
) -> Result<VerifiedCapability, CapabilityError>

It performs three checks and nothing else:

  • Issuer trust. token.issuer must appear in the supplied trusted-issuer set.
  • Signature. token.verify_signature() re-canonicalizes the body and dispatches verification off the signature's self-describing encoding.
  • Time bounds. classify_time_window(now, issued_at, expires_at) returns one of Valid, NotYetValid, Expired.

Revocation, delegation lineage, scope match against a request, and DPoP subject binding are deliberately out of scope here. They live in chio-kernel because they require IO or transport state. The full kernel orchestrates all of them in ChioKernel::evaluate_tool_call_sync.


Revocation

Revocation is a separate signal layered on top of valid-signature tokens. The kernel queries a RevocationStore:

crates/chio-kernel/src/revocation_runtime.rs
pub trait RevocationStore: Send + Sync {
    fn is_revoked(&self, capability_id: &str) -> Result<bool, RevocationStoreError>;
    fn revoke(&self, capability_id: &str) -> Result<bool, RevocationStoreError>;
}

Two implementations ship: an InMemoryRevocationStore suitable for tests and short-lived processes, and a SQLite-backed store in chio-store-sqlite for production. The lookup happens once per evaluation, before the guard pipeline runs. A revoked token denies with a fixed reason regardless of scope.

Distributed revocation

A NATS-subscribed feed implementation lives downstream of the trait. Operators with multiple kernel replicas should propagate revocations through that channel rather than per-replica admin calls; the trait is the contract that keeps the kernel agnostic.

DPoP Subject Binding

DPoP (Demonstration of Proof-of-Possession) binds a single tool invocation to the agent named in token.subject. The agent signs a per-request proof; the kernel verifies that signature against the subject key.

crates/chio-kernel/src/dpop.rs
pub struct DpopProof {
    pub body: DpopProofBody,
    pub signature: Signature,
}

DPoP is opt-in per grant via ToolGrant::dpop_required. The semantics:

  • Some(true): the kernel requires a valid DPoP proof on every invocation.
  • Some(false) or None: DPoP is not enforced.
  • During delegation, a parent that requires DPoP forces every child grant to also require DPoP. A child cannot relax this requirement.

Schema: chio.dpop_proof.v1. The proof is namespaced and replay-protected via a nonce store owned by the kernel; consult crates/chio-kernel/src/dpop.rs for the verification logic.


Worked Example: Sign and Verify

End-to-end flow: build a body, sign with an Ed25519 keypair, then verify against a trusted-issuer set.

rust
use chio_core_types::capability::{
    CapabilityToken, CapabilityTokenBody, ChioScope, ToolGrant, Operation,
    Constraint,
};
use chio_core_types::crypto::Keypair;
use chio_kernel_core::capability_verify::verify_capability;
use chio_kernel_core::clock::SystemClock;

let issuer_kp = Keypair::generate();
let agent_kp = Keypair::generate();

let body = CapabilityTokenBody {
    id: "01HXJ...".into(),
    issuer: issuer_kp.public_key(),
    subject: agent_kp.public_key(),
    scope: ChioScope {
        grants: vec![ToolGrant {
            server_id: "fs".into(),
            tool_name: "read_file".into(),
            operations: vec![Operation::Invoke],
            constraints: vec![Constraint::PathPrefix("/var/log".into())],
            max_invocations: Some(1000),
            max_cost_per_invocation: None,
            max_total_cost: None,
            dpop_required: Some(true),
        }],
        resource_grants: vec![],
        prompt_grants: vec![],
    },
    issued_at: 1_700_000_000,
    expires_at: 1_700_086_400, // +24h
    delegation_chain: vec![],
};

let token = CapabilityToken::sign(body, &issuer_kp).expect("sign");

let trusted = [issuer_kp.public_key()];
let clock = SystemClock;
let verified = verify_capability(&token, &trusted, &clock)
    .expect("token verifies");

assert_eq!(verified.id, token.id);

The result is a VerifiedCapability carrying just enough state for downstream scope-match and revocation checks. Adapters that drop the original token after verification still hold the captured scope.


Next Steps

Mandates & Capabilities · Chio Docs