Chio/Docs

LearnStart Here

Quickstart

Sign your first receipt with three commands, put your coding agent under the same policy, and hand an auditor evidence that verifies without Chio.

346 chio commands under 31 top-level commands: api (1), arena (3), attest (10), bind (0), cert (3), certify (14), check (0), commerce (1), conformance (2), did (1), doctor (0), evidence (5), federation (9), finding (17), guard (17), init (0), lineage (3), mcp (4), passport (33), pheromone (51), policy (1), proof (10), receipt (11), replay (1), reputation (2), run (0), runtime (27), settle (1), start (0), trust (87), workflow (1)chio api: 1 commands beneath itapi1chio arena: 3 commands beneath itarena3chio attest: 10 commands beneath itattest10chio bind: a single commandbindleafchio cert: 3 commands beneath itcert3chio certify: 14 commands beneath itcertify14chio check: a single commandcheckleafchio commerce: 1 commands beneath itcommerce1chio conformance: 2 commands beneath itconformance2chio did: 1 commands beneath itdid1chio doctor: a single commanddoctorleafchio evidence: 5 commands beneath itevidence5chio federation: 9 commands beneath itfederation9chio finding: 17 commands beneath itfinding17chio guard: 17 commands beneath itguard17chio init: a single commandinitleafchio lineage: 3 commands beneath itlineage3chio mcp: 4 commands beneath itmcp4chio passport: 33 commands beneath itpassport33chio pheromone: 51 commands beneath itpheromone51chio policy: 1 commands beneath itpolicy1chio proof: 10 commands beneath itproof10chio receipt: 11 commands beneath itreceipt11chio replay: 1 commands beneath itreplay1chio reputation: 2 commands beneath itreputation2chio run: a single commandrunleafchio runtime: 27 commands beneath itruntime27chio settle: 1 commands beneath itsettle1chio start: a single commandstartleafchio trust: 87 commands beneath ittrust87chio workflow: 1 commands beneath itworkflow1

Prerequisites

  • A Rust toolchain and Cargo. The workspace pins its toolchain in rust-toolchain.toml, so rustup fetches the Rust 1.94.1 the build asks for.
  • git, to clone the repository, and jq, which the receipt filters below pipe into.
  • A working directory you own, with no group or world write bit on it or on any directory above it. Chio refuses to open its state directory otherwise.

1. Install the CLI

Chio is a single binary, built from the repository with Cargo:

bash
$ git clone https://github.com/bb-connor/arc.git
$ cd arc
$ cargo build --release -p chio-cli
$ export PATH="$PWD/target/release:$PATH"
$ cd ..

The build writes one self-contained binary at target/release/chio, and the export puts it on your PATH for this shell. Copy that file into a directory your PATH already covers to keep it there. Compiling the dependency graph is the long step.

quickstart · versiontranscript
$ chio --version
chio-cli 0.1.0
exit 0

The commands step back out of the source tree before the version check, because the scaffold in section 2 is its own Cargo project and wants a directory of its own. Sections 2 through 5 need the CLI and the Rust toolchain you just used. Section 6 adds your coding agent. Release archives, the Homebrew formula, the container image, and the language SDKs are on Installation.

2. Sign your first receipt

chio init scaffolds a project: a policy, a small tool server, and a demo client that calls it.

quickstart · inittranscript
$ chio init my-agent
created Chio scaffold at ~/chio/my-agent

Next steps:
  cd ~/chio/my-agent
  cargo build
  CHIO_BIN=chio cargo run --quiet --bin demo
exit 0

Every command below runs inside that directory; each transcript names the directory it ran in.

quickstart · lstranscript
$ ls
Cargo.toml
README.md
policy.yaml
src
exit 0in my-agent

The closing hint spells the binary out through CHIO_BIN, which the demo reads when it shells out. With that variable unset it looks for a binary named chio along PATH, so the plain form in section 4 works from the install you just did.

The starter policy.yaml grants one tool, hello_world on the server hello, and enables the forbidden-path and shell-command guards. chio check evaluates a single call against it. Two databases matter: --receipt-db is the signed record you keep, and --session-db is the admission ledger for the run, which a one-shot dry run can put in a scratch directory.

quickstart · check-allowtranscript
$ chio --receipt-db .chio/receipts.db --session-db "$(mktemp -d)/admission.db" check \
    --policy ./policy.yaml --server hello --tool hello_world --params '{"name":"Chio"}'
verdict:    ALLOW
tool:       hello_world
server:     hello
receipt_id: 448440b65fa15559a364d24512cbe4f08631befe2c3d2ab471dad7204b8b69c8
policy:     69e943b96e9ce64d0264bb56bf8930e77bd4e68adf68fcf1395790dae03e6b55
source:     0d35135e8b19230b7aa42b0cc5982579450f5dc4a0280822e8b339e7786d2296
mode:       preflight
fixture:    false
exit 0allowin my-agent

That is the first receipt. The id is the SHA-256 over the receipt's canonical JSON body, so the record is content-addressed: 64 lowercase hex characters, every time. The policy and source hashes pin which policy text produced the verdict.

Refusals are signed on the same terms. Ask for a tool the capability never granted:

quickstart · check-denytranscript
$ chio --receipt-db .chio/receipts.db --session-db "$(mktemp -d)/admission.db" check \
    --policy ./policy.yaml --server hello --tool drop_tables --params '{}'
verdict:    DENY
tool:       drop_tables
server:     hello
reason:     requested tool drop_tables on server hello is not in capability scope
receipt_id: 28b6e2576ca2ccbd031e769d8d2bd504317161115a600f27771a7f932307a284
policy:     69e943b96e9ce64d0264bb56bf8930e77bd4e68adf68fcf1395790dae03e6b55
source:     0d35135e8b19230b7aa42b0cc5982579450f5dc4a0280822e8b339e7786d2296
mode:       preflight
fixture:    false
WARN chio_kernel::kernel::evaluation::async_evaluation_core message=capability rejected request_id=check-001 reason=requested tool drop_tables on server hello is not in capability scope
exit 2denyin my-agent

Exit codes for scripting

chio check returns success on an allow, exits 2 on a deny, and exits 3 when the verdict is pending human approval, which it treats as a soft deny (crates/products/chio-cli/src/cli/runtime.rs). The two transcripts above show the first two codes. Branch on the exit status: a clean deny is policy working, not a crash.

3. Read the record

Both decisions are in the receipt database. chio receipt list prints one JSON receipt per line, so a jq filter turns the log into a ledger:

quickstart · receipt-listtranscript
$ chio --receipt-db .chio/receipts.db receipt list --admin-all \
    | jq -r '[.decision.verdict, .tool_server, .tool_name, .id] | @tsv'
allow	hello	hello_world	448440b65fa15559a364d24512cbe4f08631befe2c3d2ab471dad7204b8b69c8
deny	hello	drop_tables	28b6e2576ca2ccbd031e769d8d2bd504317161115a600f27771a7f932307a284
exit 0in my-agent

A local read is scoped explicitly: --admin-all reads across every tenant, and --tenant <id> reads one. Omitting both is an error, so a read boundary is never implied.

To ask why a single call went the way it did, use receipt explain. Every id is a hash of a timestamped body, so yours differ from the ones printed here. Let the query name the receipt and the two commands compose:

quickstart · receipt-explaintranscript
$ DENY=$(chio --receipt-db .chio/receipts.db receipt list --admin-all \
    | jq -r 'select(.decision.verdict == "deny") | .id')
$ chio --receipt-db .chio/receipts.db receipt explain "$DENY" --admin-all
receipt: 28b6e2576ca2ccbd031e769d8d2bd504317161115a600f27771a7f932307a284
schema: chio.receipt.v1
identity: 28b6e2576ca2ccbd031e769d8d2bd504317161115a600f27771a7f932307a284
decision: deny
reason: requested tool drop_tables on server hello is not in capability scope
guard: kernel
policy_hash: 69e943b96e9ce64d0264bb56bf8930e77bd4e68adf68fcf1395790dae03e6b55
scope_diff: requested scope vs granted scope is not embedded in this receipt
parents: 0
repair_hint: inspect the guard and policy_hash, then mint or narrow a matching capability
exit 0in my-agent

The log holds one deny at this point, so $DENY is a single id. Narrow the filter the same way once there are more of them, by tool name or by server.

reason is the line to read. This call never reached a guard: the capability did not carry the tool, so the kernel refused it on scope. Section 5 produces the other kind, a call the capability allows and a guard stops.

4. Run a real mediated call

chio check decides without dispatching. The scaffold's demo runs the whole loop: it starts chio mcp serve over the bundled tool server, calls hello_world through the edge, and prints what came back. The demo also prints the handshake and the tool listing, which the filter drops.

quickstart · demotranscript
$ cargo build --quiet
$ cargo run --quiet --bin demo | sed -n '/^tool_call:/,/^governed greeting:/p'
tool_call:
{
  "id": 3,
  "jsonrpc": "2.0",
  "result": {
    "content": [
      {
        "text": "Hello, Chio! This call was mediated by Chio.",
        "type": "text"
      }
    ],
    "isError": false,
    "structuredContent": {
      "greeting": "Hello, Chio! This call was mediated by Chio."
    }
  }
}

governed greeting: Hello, Chio! This call was mediated by Chio.
exit 0in my-agent

The tool ran, the caller got its answer, and the record grew by one row. The last of the three rows is the call the kernel dispatched:

quickstart · receipt-list-mediatedtranscript
$ chio --receipt-db .chio/receipts.db receipt list --admin-all \
    | jq -r '[.decision.verdict, .tool_server, .tool_name, .id] | @tsv'
allow	hello	hello_world	448440b65fa15559a364d24512cbe4f08631befe2c3d2ab471dad7204b8b69c8
deny	hello	drop_tables	28b6e2576ca2ccbd031e769d8d2bd504317161115a600f27771a7f932307a284
allow	hello	hello_world	b8937de6bcee88a767d59acfd2e457e6d67eb4984f173bea3324bd47a88e456f
exit 0in my-agent

5. Write the policy yourself

A policy has two layers that answer different questions. Capabilities say what an agent may reach. Guards say what may never happen, whatever the capability allows. Open policy.yaml and, below the kernel block, widen the capability to a second server while tightening a guard:

policy.yaml (below the kernel block)yaml
guards:
  forbidden_path:
    enabled: true
    additional_patterns:
      - "**/deploy/**"
  shell_command:
    enabled: true

capabilities:
  default:
    tools:
      - server: "hello"
        tool: "hello_world"
        operations: [invoke]
        ttl: 300
      - server: "docs"
        tool: "write_file"
        operations: [invoke]
        ttl: 300

The new grant is live on the next call, and so is the new pattern:

quickstart · check-docs-allowtranscript
$ chio --receipt-db .chio/receipts.db --session-db "$(mktemp -d)/admission.db" check \
    --policy ./policy.yaml --server docs --tool write_file \
    --params '{"path":"./notes.md","content":"draft"}'
verdict:    ALLOW
tool:       write_file
server:     docs
receipt_id: bea823a56307417abb5a28338b5f1583aa78c4c368ade6ce27f829ad27b43a53
policy:     2fcc2ec667d3d0c7c40b46e03df20c257e79f9f8dec17c5a3057e094f2948cec
source:     e79960baa280fa1851dc208fc325eb3fd8e88ec874d50d3b364a64f6ecff8a7b
mode:       preflight
fixture:    false
exit 0allowin my-agent

The capability grants write_file on docs for every path. The guard still refuses one:

quickstart · check-docs-denytranscript
$ chio --receipt-db .chio/receipts.db --session-db "$(mktemp -d)/admission.db" check \
    --policy ./policy.yaml --server docs --tool write_file \
    --params '{"path":"./deploy/prod.yaml","content":"draft"}'
verdict:    DENY
tool:       write_file
server:     docs
reason:     guard denied the request: guard "guard-pipeline" denied the request
receipt_id: 57bde8363cda6ff352db9451a55cc6d6f051b541dacab6c127cb995347395ee2
policy:     2fcc2ec667d3d0c7c40b46e03df20c257e79f9f8dec17c5a3057e094f2948cec
source:     e79960baa280fa1851dc208fc325eb3fd8e88ec874d50d3b364a64f6ecff8a7b
mode:       preflight
fixture:    false
WARN chio_kernel::kernel::evaluation::async_evaluation_core message=guard denied request_id=check-001 reason=guard denied the request: guard \"guard-pipeline\" denied the request
exit 2denyin my-agent

Both hash lines moved when the file did. Compare the policy and source values here against the ones in section 2: every receipt from here on cites the new pair, so a reader of the log can tell which text was in force for which decision.

6. Put your coding agent under the same policy

Coding agents reach their file, shell, and git tools over MCP. chio mcp serve wraps any stdio MCP server subprocess, so every call the agent makes goes through the same kernel and lands in the same log. The bundled code-agent preset is a policy written for exactly this: it grants reads, writes, and edits on the fs server, one shell tool, and the read and commit side of git, then puts .env, .git/, .ssh/, and .pem and .key files behind the forbidden-path guard, and refuses git push --force. Claude Code registers the wrapped server in one line:

bash
$ claude mcp add fs -- chio --receipt-db .chio/receipts.db --session-db .chio/session.db \
    mcp serve --preset code-agent --server-id fs \
    -- npx -y @modelcontextprotocol/server-filesystem .

Local scope keys the registration to this project inside ~/.claude.json, so the wrapped server is reachable from this directory and nowhere else.

The server id must stay fs. The code-agent preset grants capabilities to the fs, shell, and git server ids only, and the policy is fail-closed, so any other id is denied on every call. Confirm the edge is live:

bash
$ claude mcp get fs

Now use the agent exactly as before. Its calls land in the receipt database the CLI has been writing to, under the server id you registered, so the filter from section 3 reads them once it names that server and the path each call asked for:

bash
$ chio --receipt-db .chio/receipts.db receipt list --admin-all --tool-server fs \
    | jq -r '[.decision.verdict, .tool_name, .action.parameters.path] | @tsv'

The filesystem server resolves every path before it asks, so the receipt records an absolute one and the guard matches on that.

7. Prove the agent's standing

An Agent Passport bundles the agent's did:chio identity with a reputation credential computed from its own receipts, signed by an issuer key you hold. Build one from the log you just made:

quickstart · passport-createtranscript
$ AGENT=$(chio --receipt-db .chio/receipts.db receipt list --admin-all \
    | jq -r 'select(.decision.verdict == "allow") | .metadata.attribution.subject_key' | tail -1)
$ chio --receipt-db .chio/receipts.db passport create \
    --subject-public-key "$AGENT" \
    --signing-seed-file ./issuer.seed \
    --output passport.json
wrote passport to passport.json
subject:          did:chio:aa3d113530678f6e69d413fce90ba72fb291a426bea566b34c2c7adac55612f5
credential_count: 1
merkle_roots:     0
enterprise_provenance: 0
valid_until:      2026-10-04T13:52:03Z
exit 0in my-agent

A receipt carries the subject of the capability the call used, and a run that is given no persistent authority seed mints its own capability, so the subject changes from run to run. The credential therefore covers the receipts one run wrote, and the tail -1 above picks the most recent allowed one. The seed file is written on first use and reused after that, so the issuer identity stays yours.

A relying party never has to trust your word for any of that. It writes its own bar and evaluates the passport against it, with no shared server. A passport exists only where its subject has at least one receipt, so a bar of one is the floor every passport clears:

verifier-policy.yamlyaml
minReceiptCount: 1
minLineageRecords: 1
quickstart · passport-evaluatetranscript
$ chio passport evaluate --input passport.json --policy verifier-policy.yaml
passport evaluated
subject:             did:chio:aa3d113530678f6e69d413fce90ba72fb291a426bea566b34c2c7adac55612f5
passport_id:         f42fb309c80a3baf92fed8c805756d2ff05d661cf2f04714d100e9305a6b905d
issuer:              did:chio:14a57fedcd137e619f69be7791f34cca4b6273872120b0e44cf0279c637018af
issuer_count:        1
accepted:            true
matched_credentials: 1
matched_issuers:     did:chio:14a57fedcd137e619f69be7791f34cca4b6273872120b0e44cf0279c637018af
credential_count:    1
enterprise_provenance: 0
valid_until:         2026-10-04T13:52:03Z
exit 0in my-agent

Raise the bar past what the agent has earned and the same command refuses it, naming the shortfall:

strict-policy.yamlyaml
minReceiptCount: 500
minLineageRecords: 1
quickstart · passport-evaluate-stricttranscript
$ chio passport evaluate --input passport.json --policy strict-policy.yaml
passport evaluated
subject:             did:chio:aa3d113530678f6e69d413fce90ba72fb291a426bea566b34c2c7adac55612f5
passport_id:         f42fb309c80a3baf92fed8c805756d2ff05d661cf2f04714d100e9305a6b905d
issuer:              did:chio:14a57fedcd137e619f69be7791f34cca4b6273872120b0e44cf0279c637018af
issuer_count:        1
accepted:            false
matched_credentials: 0
credential_count:    1
enterprise_provenance: 0
valid_until:         2026-10-04T13:52:03Z
rejections:
  credential 0 (did:chio:14a57fedcd137e619f69be7791f34cca4b6273872120b0e44cf0279c637018af):
    - receipt_count 1 is below required minimum 500
exit 0in my-agent

The command reports the evaluation and returns success either way, so a script branches on accepted rather than on the exit status.

8. Take the evidence offline

An evidence package is the receipt log plus its lineage and checkpoint state, written to a directory with a hash manifest. It travels to auditors, counterparties, and incident reviews without the database or any Chio service:

quickstart · evidence-exporttranscript
$ chio --receipt-db .chio/receipts.db evidence export --admin-all --output ./evidence
$ ls evidence
README.txt
capability-lineage.ndjson
checkpoint-consistency-proofs.ndjson
checkpoint-equivocations.ndjson
checkpoint-publications.ndjson
checkpoint-witnesses.ndjson
checkpoints.ndjson
child-receipts.ndjson
inclusion-proofs.ndjson
manifest.json
query.json
receipts.ndjson
retention.json
exit 0in my-agent

manifest.json holds a SHA-256 and a byte count for every other file in the directory, and query.json records the read boundary the export ran under. A file this run produced nothing for is still written, empty, so a recipient can tell an empty set from a missing one.


Verify the result

Verification reads only that directory. The counts are what the recipient can now assert on their own:

quickstart · evidence-verifytranscript
$ chio evidence verify --input ./evidence
evidence package verified
tool_receipts:          5
child_receipts:         0
checkpoints:            0
checkpoint_publications: 0
checkpoint_witnesses:   0
checkpoint_consistency_proofs: 0
checkpoint_equivocations: 0
capability_lineage:     5
inclusion_proofs:       0
uncheckpointed_receipts: 5
authorized_receipts:     3
trace_observations:      0
advisory_evaluations:    0
verified_files:         12
child_receipt_scope:    FullQueryWindow
transparency_preview_logs: 0
publication_state:      transparency_preview
exit 0in my-agent

The success condition is the first line and the exit status: evidence package verified with a zero exit. The receipt count is the walkthrough's own: the two decisions from section 2, the mediated call from section 4, and the two from section 5. Nothing here was checkpointed, so the checkpoint rows are zero and every receipt is uncheckpointed. verified_files is the manifest's entry count, which is every file in the directory except the manifest itself.

Failures and recovery

Change one byte in a copy of the package and verification fails closed, naming the file that moved:

quickstart · evidence-tampertranscript
$ cp -R evidence evidence-tampered
$ printf '\n' >> evidence-tampered/receipts.ndjson
$ chio evidence verify --input ./evidence-tampered
error [urn:chio:error:attest:provenance-missing]: evidence package file hash mismatch for receipts.ndjson
context: {"domain":"attest","severity":"error","stability":"unstable","string_code":"CHIO-ATTEST-PROVENANCE-MISSING"}
suggested fix: Regenerate the evidence bundle and include provenance before submitting the operation.
exit 1in my-agent

The error goes to stderr and the command exits non-zero, so a pipeline stops on it. Export again from the receipt database to get a package whose manifest matches.

  • command not found: chio. The export in section 1 lasts for one shell. Copy target/release/chio into a directory your PATH already covers.
  • private directory ancestry must not be group or world writable unless sticky. Chio refuses a state directory whose parent any other user can write. Take the group and world write bits off the project directory and off .chio, then run the command again.
  • A command exited 2. That is a deny, not a failure. Read reason in the output, or run receipt explain on the id it printed.
  • The demo in section 4 stops before it prints tool_call:. It starts chio mcp serve as a subprocess and reads CHIO_BIN, or else looks for a binary named chio along PATH, so the edge has to be reachable from the shell that runs cargo run.

Limits and next steps

Everything above ran against local files: one receipt database, one policy, one issuer key on disk. The package it produced carries no checkpoints and no inclusion proofs, because nothing published the log to a transparency service. Durable revocation, a shared receipt store, and checkpoint coverage are deployment decisions, and the starter policy opts out of the first of them by keeping revocation state in memory.

  • Policy Schema: every guard and every field you can put in policy.yaml
  • Capabilities: scoped, time-bounded, delegatable authority, and how it narrows
  • Receipts: what a receipt binds, and how signature verification works
  • Architecture: the kernel, the guard pipeline, and the trust boundary around them
  • Verify receipts offline: take the evidence package apart yourself