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.
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.
StartEach 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.
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 Passports1. 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.
$ chio --receipt-db ./receipts.db passport create \
--agent fixer \
--signing-seed-file ./issuer.seed \
--output ./passport.json \
--validity-days 30wrote passport to ./passport.json subject: did:chio:4daeca6fd9edd85aaa143e9059f9395d457152127e2162896afe650a4391f851 credential_count: 1 merkle_roots: 0 enterprise_provenance: 0 valid_until: 2026-10-05T06:16:56Z
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_untilends no later than any credential it contains.
$ chio passport verify --input ./passport.jsonpassport 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
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.
issuer_allowlist:
- did:chio:4cb5abf6ad79fbf5abbccafcc269d85cd2651ed4b885b5869f241aedf0a5ba29
min_receipt_count: 1
max_attestation_age_days: 30
require_checkpoint_coverage: false
require_active_lifecycle: falsechio passport evaluate --input passport.json --policy verifier-policy.yamlpassport 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.
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.dbchallenge 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.
chio trust federated-issue --presentation-response response.json \
--challenge challenge.json --capability-policy grant-policy.yamlgrant 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.
κ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.
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.
chio receipt list --capability <grant-id> | jq -c '{id, tool_name, decision}'{"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.
$ chio --receipt-db ./receipts.db passport create \
--subject-public-key 3d067fadc2c47f5a6f125a0b06c36f38e5699277107b585847145601a905146e \
--signing-seed-file ./issuer.seed \
--output ./rejected.jsonerror[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
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
Search for issuer_allowlist, proofValue, Premier, or Superseded.
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
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:
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:chio:7b0f6f631f6e66207140ead0b6b2e9418916d2c4b3c7448ba5f7ed27f5c8d038use 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.
$ 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"
]
}Credential wire format
Each credential follows the W3C Verifiable Credentials data model with Chio extensions:
{
"@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:
$ 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"
}
}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
| Metric | Measures | Policy threshold |
|---|---|---|
reliability | Success rate and consistency of tool invocations | min_reliability |
least_privilege | Whether the agent requests only the permissions it needs | min_ |
delegation_ | Quality of delegation: attenuation and expiry | min_ |
boundary_ | Frequency of denied requests; lower is better | max_ |
resource_ | Use of budgets and economic resources | none |
specialization | Consistency of tool usage patterns | none |
history_depth | Length and completeness of operational history | min_ |
incident_ | Association with security incidents or policy violations | none |
Verifier policy fields
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.
| Tier | Compliance score | Behavioral 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.
$ chio passport generate \
--agent did:chio:7b0f6f631f6e66207140ead0b6b2e9418916d2c4b3c7448ba5f7ed27f5c8d038 \
--compliance-score 950 \
--validity-days 30 \
--output ./generated.jsonpassport generated subject: did:chio:7b0f6f631f6e66207140ead0b6b2e9418916d2c4b3c7448ba5f7ed27f5c8d038 compliance_score: 950 behavioral_anomaly: false trust_tier: premier valid_until: 1791181017 output: ./generated.json
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
| State | Meaning | Reported 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.
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.