Chio/Docs
LOGIN · JOIN

Agent Passport

Weigh another operator's agent on its issuer-signed history, then issue it one local grant bound to the key its credentials name.

Guide chio@5f63e6e

On this page

Another operator's agent arrives at your door with a passport: credentials that issuers signed about its history. You verify each signature offline, judge the claims under your own policy, and issue a local grant only after the agent proves it holds the key those credentials name.

The passport grants nothing by itself; authority still comes from a capability your kernel issues. It lets you weigh an agent from another organization without access to its operator's systems. Here you play all three parties on one machine: issuer, holder, and receiving operator.

Start
Agent PassportChoose a case.
issuing operatorsreceiving operatorsignssignsacceptedacceptedaσ1σ2c1c2Ksubjectdid:chio:…issuerissuercredentialcredentialreceiving kernelissuing operatorsreceiving operatoracceptedacceptedaσ1σ2c1c2Ksubjectdid:chio:…issuerissuercredentialcredentialreceiving kernel

Each issuer signs its own credential about the same subject

A second issuer appends one credential with its own issuer, evidence, and proof. The receiver checks each Ed25519 proof against the key inside that issuer’s did:chio and reports both issuers, with issuer_count 2. Agent Passport guide.

One Agent Passport holding two issuer-signed credentials about one did:chio subject, checked by the receiving operator’s kernel. The second issuer is illustrative; the example bundle carries one. Lifecycle status, the projected formats, and the grant the receiver issues are left out.Agent Passports

1. Issue a passport from the agent's receipt history

chio passport create reads the subject's receipts from a receipt database, scores them, and writes a passport holding one credential signed with your issuer seed. Validity defaults to 30 days.

Before you begin

You need a receipt database the subject already appears in, and the subject's Ed25519 public key as bare lowercase hex, taken from .metadata.attribution.subject_key on a receipt the agent produced. The issuer seed file holds 64 hexadecimal characters, 32 bytes, on one line. The issuer's did:chio is derived from it, and that is the value a relying party lists in its issuer_allowlist.

passport-identity · createtranscript
$ chio --receipt-db ./receipts.db passport create \
    --agent fixer \
    --signing-seed-file ./issuer.seed \
    --output ./passport.json \
    --validity-days 30
wrote passport to ./passport.json
subject:          did:chio:4daeca6fd9edd85aaa143e9059f9395d457152127e2162896afe650a4391f851
credential_count: 1
merkle_roots:     0
enterprise_provenance: 0
valid_until:      2026-10-05T06:16:56Z
exit 0

You should see the subject's DID, one credential, no Merkle roots yet, and a valid_until 30 days out. The jq line takes the subject key from the receipt store, so the passport describes an agent the store has seen.

2. Verify every credential offline

A passport (schema chio.agent-passport.v1) is an unsigned bundle of reputation credentials, each signed by its own issuer. Verification needs no kernel, storage, or network, and it checks every credential:

  • its Ed25519 signature against the issuer's public key;
  • its subject against the passport's subject;
  • its expiry;
  • that the passport's valid_until ends no later than any credential it contains.
passport-identity · verifytranscript
$ chio passport verify --input ./passport.json
passport verified
subject:          did:chio:4daeca6fd9edd85aaa143e9059f9395d457152127e2162896afe650a4391f851
passport_id:      b42ec4bb375f69d131be95bf1f05dd6018200314accd9ddfc03679e00d42cdf5
issuer:           did:chio:4cb5abf6ad79fbf5abbccafcc269d85cd2651ed4b885b5869f241aedf0a5ba29
issuer_count:     1
credential_count: 1
merkle_roots:     0
enterprise_provenance: 0
valid_until:      2026-10-05T06:16:56Z
exit 0

You should see a passport_id, the content hash of the bundle, which is why verify prints it and create cannot. The issuer is the seed's derived DID. A second issuer appends its own credential and verify reports issuer_count 2; one credential about a different subject refuses the whole passport with PassportSubjectMismatch before any policy runs.

3. Judge the claims under your verifier policy

Signatures say who attested; your PassportVerifierPolicy says whether that is enough. Write the thresholds you require, then evaluate the passport against them. Output in this step and the next three is illustrative.

verifier-policy.yamlyaml
issuer_allowlist:
  - did:chio:4cb5abf6ad79fbf5abbccafcc269d85cd2651ed4b885b5869f241aedf0a5ba29
min_receipt_count: 1
max_attestation_age_days: 30
require_checkpoint_coverage: false
require_active_lifecycle: false
Terminalbash
chio passport evaluate --input passport.json --policy verifier-policy.yaml
Output
passport   b42ec4bb375f…cdf5
accepted   yes
matched    credential 0, issuer did:chio:4cb5ab…ba29

You should see the decision, the matched credential indexes, and the matched issuers. A valid signature can still fail policy: with require_checkpoint_coverage set, a credential whose receipts have no checkpoint roots is rejected, and CredentialPolicyEvaluation records its index, issuer, and reasons while the other credentials stand. To publish your requirements, sign the policy with chio passport policy create; relying parties read it as a SignedPassportVerifierPolicy.

4. Challenge the holder to prove the subject key

Create a challenge that names the issuers you accept and the policy you apply. The holder selects matching credentials and signs a response with the passport subject's key, and you verify the response against the exact challenge.

Terminalbash
chio passport challenge create --verifier https://trust.larkfield.example \
    --policy verifier-policy.yaml --output challenge.json
chio passport challenge respond --input passport.json --challenge challenge.json \
    --holder-seed-file holder.seed --output response.json
chio passport challenge verify --input response.json --challenge challenge.json \
    --verifier-challenge-db challenges.db
Output
challenge  challenge.json, expires in 300 s
response   response.json, signed by did:chio:4daeca…f851
verified   bound to challenge.json, 1 credential disclosed

You should see a response bound to your challenge. A challenge lives 300 seconds by default (--ttl-secs), and the challenge database keeps verification replay-safe. --issuer and --max-credentials limit what the holder discloses; chio passport present applies the same filter offline.

5. Issue one local grant bound to the holder's key

chio trust federated-issue verifies the presentation against the challenge and issues the single capability your policy file defines. Its subject is the key the passport names.

Terminalbash
chio trust federated-issue --presentation-response response.json \
    --challenge challenge.json --capability-policy grant-policy.yaml
Output
grant      cap-01b3…6a20, signed by your kernel
to         did:chio:4daeca…f851, from response.json

You should see a grant to the passport's subject. The holder's key travels in the projected credential's cnf claim, and the same key signs a DPoP proof on every request, so a copied grant alone carries no authority at your door. --delegation-policy sets a signed parent scope and lifetime ceiling; --enterprise-identity adds provider-admin gating.

Possession at the doorChoose what arrives.
Their operatorYour operatoraK′Kdρtheir agenttheir kernelyour kerneltoolTheir operatorYour operatoraK′Kdρagenttheir kernelyour kerneltool

κgrantσpossessiongguardsτtreatyBbudget

The proof binds the grant to this call

Their agent presents the federated grant with a DPoP proof signed by the key the grant names. Your kernel verifies the grant, compares each of the proof's bindings with the request it received, then runs your guards, the treaty check, and your budget. The proof's nonce is consumed at dispatch, and your kernel signs the receipt. Proof of possession.

One federated call at your kernel's door: the grant and DPoP proof their agent presents, the proof's five bindings, and the checks your kernel runs under its own policy, in order. Scope runs inside the grant check. Output checks after dispatch and the durable replay profile are left out.Proof of possession

6. Read the receipt for the agent's first call

Once the agent calls a tool under the grant, list the receipts your kernel signed for it, using the grant id from step 5.

Terminalbash
chio receipt list --capability <grant-id> | jq -c '{id, tool_name, decision}'
Output
{"id":"rcpt-01b3…2f71","tool_name":"read_file","decision":{"verdict":"allow"}}

You should see the admitted call under the grant from step 5. The receipt names the capability your kernel issued: the authority the call ran under, with the passport as the evidence behind issuing it.

Read more in Receipts.

A refused passport names the input it rejected

Most failures stop before a file is written. The two below come from chio passport create.

invalid public key: Cannot decompress Edwards point

chio passport create decompresses --subject-public-key as an Edwards point, so 32 random bytes fail before the receipt window is queried. Take the value from .metadata.attribution.subject_key on a receipt the agent produced.

passport-identity · create-bad-subjecttranscript
$ chio --receipt-db ./receipts.db passport create \
    --subject-public-key 3d067fadc2c47f5a6f125a0b06c36f38e5699277107b585847145601a905146e \
    --signing-seed-file ./issuer.seed \
    --output ./rejected.json
error[CHIO-TRUST-KEY-MALFORMED]: invalid public key: the value is not a point on the Ed25519 curve
  help: pass the agent's Ed25519 public key as 64 hex characters, as chio receipt list prints it in subject_key
  code: urn:chio:error:trust:key-malformed
  docs: https://chio.computer/docs/reference/errors#error-trust-key-malformed
exit 1
no receipts found for subject in the selected window

The key is a valid point with no receipts in the window. Widen --since, or point --receipt-db at the store that holds the agent's history.

Passport, credential, and policy reference

Credentials are W3C Verifiable Credentials, issued through OID4VCI-compatible endpoints at /v1/passport/issuance/*. A passport supports offline verification, verifier-policy evaluation, filtered disclosure, challenge-bound holder presentations, replay-safe challenge state, and multi-issuer composition. The Rust APIs are chio-credentials and chio-did; every command is in chio passport.

Passport and verification fields

AgentPassportrust
pub struct AgentPassport {
    pub schema: String,                // "chio.agent-passport.v1"
    pub subject: String,               // did:chio identifier of the agent
    pub credentials: Vec<ReputationCredential>,
    pub merkle_roots: Vec<String>,     // receipt log checkpoint roots
    pub enterprise_identity_provenance: Vec<EnterpriseIdentityProvenance>,
    pub issued_at: String,             // RFC 3339
    pub valid_until: String,           // RFC 3339
    pub trust_tier: Option<TrustTier>, // derived tier, optional
}

The CLI's verify output renders PassportVerification:

PassportVerificationrust
pub struct PassportVerification {
    pub passport_id: String,
    pub subject: String,
    pub issuer: Option<String>,
    pub issuers: Vec<String>,
    pub issuer_count: usize,
    pub credential_count: usize,
    pub merkle_root_count: usize,
    pub enterprise_identity_provenance: Vec<EnterpriseIdentityProvenance>,
    pub passport_lifecycle: Option<PassportLifecycleResolution>,
    pub verified_at: u64,
    pub valid_until: String,
}

did:chio identifiers

Every agent and operator is identified by a did:chio identifier whose method-specific part is the lowercase hex Ed25519 public key. The key is in the identifier, so resolution needs no registry and anyone can verify signatures without a resolver.

did:chiotext
did:chio:7b0f6f631f6e66207140ead0b6b2e9418916d2c4b3c7448ba5f7ed27f5c8d038
chio_didrust
use chio_did::DidChio;
use chio_core::PublicKey;
use std::str::FromStr;

// Create from a public key (rejects non-Ed25519 keys, so it returns a Result)
let did = DidChio::from_public_key(public_key)?;
println!("{}", did.as_str());
// did:chio:7b0f6f63...

// Parse from a string
let did = DidChio::from_str(
    "did:chio:7b0f6f631f6e66207140ead0b6b2e9418916d2c4b3c7448ba5f7ed27f5c8d038"
)?;

// Resolve to a DID Document (self-certifying, no network call)
let doc = did.resolve();

The resolved DID document carries the verification method (the Ed25519 key in multibase) and, optionally, service endpoints for receipt log access and passport status. chio did resolve runs the same resolution with no network call, which also tells you whether an identifier is a valid Ed25519 point. It accepts --did or --public-key; --receipt-log-url and --passport-status-url add the service entries.

A capability token stores its subject as a PublicKey; a passport stores a did:chio. To bind one to the other, derive the DID with DidChio::from_public_key(token.subject)? and compare it with the verified passport's subject. Matching identity is a separate step from checking the issuer and from deciding whether the claims satisfy your policy.

passport-identity · did-resolvetranscript
$ chio did resolve \
    --did did:chio:7b0f6f631f6e66207140ead0b6b2e9418916d2c4b3c7448ba5f7ed27f5c8d038
{
  "@context": "https://www.w3.org/ns/did/v1",
  "id": "did:chio:7b0f6f631f6e66207140ead0b6b2e9418916d2c4b3c7448ba5f7ed27f5c8d038",
  "verificationMethod": [
    {
      "id": "did:chio:7b0f6f631f6e66207140ead0b6b2e9418916d2c4b3c7448ba5f7ed27f5c8d038#key-1",
      "type": "Ed25519VerificationKey2020",
      "controller": "did:chio:7b0f6f631f6e66207140ead0b6b2e9418916d2c4b3c7448ba5f7ed27f5c8d038",
      "publicKeyMultibase": "z6MknjdqrkCzEUsqeH3AD14W14XZqKmu894CLReWFLpj97BV"
    }
  ],
  "authentication": [
    "did:chio:7b0f6f631f6e66207140ead0b6b2e9418916d2c4b3c7448ba5f7ed27f5c8d038#key-1"
  ],
  "assertionMethod": [
    "did:chio:7b0f6f631f6e66207140ead0b6b2e9418916d2c4b3c7448ba5f7ed27f5c8d038#key-1"
  ]
}
exit 0

Credential wire format

Each credential follows the W3C Verifiable Credentials data model with Chio extensions:

ReputationCredentialjson
{
  "@context": [
    "https://www.w3.org/2018/credentials/v1",
    "https://chio.world/credentials/v1"
  ],
  "type": ["VerifiableCredential", "ChioReputationAttestation"],
  "issuer": "did:chio:...",
  "issuanceDate": "2026-04-01T00:00:00Z",
  "expirationDate": "2026-07-01T00:00:00Z",
  "credentialSubject": {
    "id": "did:chio:...",
    "metrics": { "...": "LocalReputationScorecard" }
  },
  "evidence": { "...": "ChioCredentialEvidence" },
  "proof": { "...": "CredentialProof" }
}

ChioCredentialEvidence and CredentialProof carry rename_all = "camelCase" (crates/trust/chio-credentials/src/artifact.rs:245-259, :361-370), so the evidence keys are receiptCount, receiptIds, checkpointRoots, lineageRecords, and uncheckpointedReceipts. A credential from step 1 reads:

passport-identity · credentialtranscript
$ jq '.credentials[0] | {issuer, issuanceDate, expirationDate, evidence, proof}' ./passport.json
{
  "issuer": "did:chio:4cb5abf6ad79fbf5abbccafcc269d85cd2651ed4b885b5869f241aedf0a5ba29",
  "issuanceDate": "2026-09-05T06:16:56Z",
  "expirationDate": "2026-10-05T06:16:56Z",
  "evidence": {
    "query": {
      "until": 1788589016
    },
    "receiptCount": 1,
    "receiptIds": [
      "38d92b9b698aa09b3c9c5195dfb19d2ec530c15669aa5f74d56f92a82008ec0b"
    ],
    "checkpointRoots": [],
    "lineageRecords": 1,
    "uncheckpointedReceipts": 1
  },
  "proof": {
    "type": "Ed25519Signature2020",
    "created": "2026-09-05T06:16:56Z",
    "proofPurpose": "assertionMethod",
    "verificationMethod": "did:chio:4cb5abf6ad79fbf5abbccafcc269d85cd2651ed4b885b5869f241aedf0a5ba29#key-1",
    "proofValue": "f9625bcfc650ea984f856054de534df5b80b67e93765ce5b374ad869cb3351f31f064de0aaba9dc5f2d5e015f0132ab9d72247385c482eebf40007f40a625e09"
  }
}
exit 0

proofValue is a bare 128-character lowercase hex signature under the Ed25519Signature2020 proof type: the issuer writes signature.to_hex() (crates/trust/chio-credentials/src/challenge.rs:179) and the verifier reads it with Signature::from_hex (:218). Each checkpointRoots entry is a Merkle root rendered through Hash's Display, 0x followed by 64 hex characters (crates/core/chio-core-types/src/hashing.rs:95-99, applied at crates/products/chio-cli/src/passport.rs:629-633); the list above is empty because the store held no checkpoints.

metrics holds a LocalReputationScorecard: eight metric categories plus subject_key, computed_at, composite_score, and effective_weight_sum, computed from the subject's receipts in the attestation window and kept snake_case on the wire (crates/trust/chio-reputation/src/model.rs:265-279).

Reputation metrics

MetricMeasuresPolicy threshold
reliability
Success rate and consistency of tool invocations
min_reliability
least_privilege
Whether the agent requests only the permissions it needs
min_least_privilege
delegation_hygiene
Quality of delegation: attenuation and expiry
min_delegation_hygiene
boundary_pressure
Frequency of denied requests; lower is better
max_boundary_pressure
resource_stewardship
Use of budgets and economic resources
none
specialization
Consistency of tool usage patterns
none
history_depth
Length and completeness of operational history
min_history_days
incident_correlation
Association with security incidents or policy violations
none

Verifier policy fields

PassportVerifierPolicyrust
pub struct PassportVerifierPolicy {
    pub issuer_allowlist: BTreeSet<String>,       // only accept from these issuers
    pub min_composite_score: Option<f64>,          // 0.0 - 1.0
    pub min_reliability: Option<f64>,              // 0.0 - 1.0
    pub min_least_privilege: Option<f64>,           // 0.0 - 1.0
    pub min_delegation_hygiene: Option<f64>,        // 0.0 - 1.0
    pub max_boundary_pressure: Option<f64>,         // 0.0 - 1.0 (lower is better)
    pub min_receipt_count: Option<usize>,           // minimum receipts in evidence
    pub min_lineage_records: Option<usize>,         // minimum lineage depth
    pub min_history_days: Option<u64>,              // minimum operational history
    pub max_attestation_age_days: Option<u32>,      // reject stale attestations
    pub require_checkpoint_coverage: bool,          // all receipts must be checkpointed
    pub require_receipt_log_urls: bool,             // evidence must include log URLs
    pub require_enterprise_identity_provenance: bool,
    pub require_active_lifecycle: bool,             // passport must be in active state
}

Trust tiers

A TrustTier is a coarse class built from an operator-supplied compliance score and a behavioral-anomaly flag. Only chio passport generate sets it, in the optional AgentPassport.trust_tier. chio passport create leaves it unset, and chio passport evaluate returns a PassportPolicyEvaluation with the decision, matched credential indexes, and matched issuers, and no tier.

TierCompliance scoreBehavioral anomaly
Unverified
below 300
any
Attested
300 to 699
any
Verified
700 to 899
any
Verified
900 and above
active
Premier
900 and above
none active

Treat Unverified as untrusted and require step-up evidence. --compliance-score takes 0..=1000 and defaults to 1000, which yields Premier, so a freshly provisioned agent does not start as Unverified; --behavioral-anomaly caps the tier below Premier.

passport-identity · generatetranscript
$ chio passport generate \
    --agent did:chio:7b0f6f631f6e66207140ead0b6b2e9418916d2c4b3c7448ba5f7ed27f5c8d038 \
    --compliance-score 950 \
    --validity-days 30 \
    --output ./generated.json
passport generated
subject:          did:chio:7b0f6f631f6e66207140ead0b6b2e9418916d2c4b3c7448ba5f7ed27f5c8d038
compliance_score: 950
behavioral_anomaly: false
trust_tier:       premier
valid_until:      1791181017
output:           ./generated.json
exit 0

generate reads no receipts or lineage; it stamps the tier from its flags, so it bootstraps a tier-bearing record that a relying party reads directly. Reputation earned from an auditable history comes from chio passport create. The tier prints kebab-case (premier) because TrustTier serializes with rename_all = "kebab-case" (crates/trust/chio-credentials/src/trust_tier.rs:38-39); the table names the Rust variants.

Passport lifecycle

StateMeaningReported by
Active
Current and valid
stored record
Stale
Not refreshed recently
resolved status only
Superseded
Replaced; superseded_by names the replacement
stored record
Revoked
Revoked, with revoked_at and an optional reason
stored record
NotFound
No lifecycle record for this passport id
resolved status only

Lifecycle records publish with optional resolve URLs and cache TTLs, so verifiers can check status without asking the issuer directly: chio passport status publish --resolve-url with --cache-ttl-secs. A policy with require_active_lifecycle accepts only Active.

Enterprise identity provenance

An issuer can record how the agent's identity was verified through its organization's identity provider. The partner then weighs the issuer, the provenance, and the metrics together, without access to the issuer's systems.

EnterpriseIdentityProvenancerust
pub struct EnterpriseIdentityProvenance {
    pub provider_id: String,            // identity provider identifier
    pub provider_kind: String,          // e.g., "azure-ad", "okta"
    pub federation_method: EnterpriseFederationMethod,
    pub principal: String,              // verified principal name
    pub subject_key: String,            // bound agent public key
    pub tenant_id: Option<String>,
    pub organization_id: Option<String>,
    pub groups: Vec<String>,
    pub roles: Vec<String>,
    // ...
}

An agent with credentials from several issuers presents them as one cross-issuer portfolio evaluated under one verifier policy; chio-credentials carries the cross-issuer trust packs and portfolio evaluation.

Next: pin the partner whose agents you admit