Chio/Docs
LOGIN · JOIN

In-Process Library

Link the kernel into a Rust binary, load its signing seed, and own its stores, lifecycle, and drain in one process.

Deployment chio@5f63e6e

On this page

A Rust service can call the kernel as a library. One host process links chio-kernel, holds the signing key and the durable stores, and checks and receipts each governed call with no network hop between the application and the kernel.

That host joins the trust boundary: whatever can read its memory can sign receipts. Run in process when the team that operates the binary also owns the signing key, and run the sidecar when it does not.

Start

The whole host process holds the key

Deployment shapesChoose a shape.
Embedded in Rustchio-kernel crateRust host processRust callany other route: the host closes itaapplicationKchio-kerneldtool serverfresourceShared servicechio api protectchio sidecarHTTP :9090aapplicationKchio api protectdreverse proxyfupstream APIMCP bridgechio mcp servechio processMCP stdioaMCP clientKchio mcp servedMCP serverfresourcesEmbedded in Rustchio-kernel crateRust host processRustother route: host closesaappKkerneldtoolfresourceShared servicechio api protectchio sidecar:9090aappKkerneldproxyfupstreamMCP bridgechio mcp servechio processstdioaclientKkerneldserverfresource

Embedded: the whole host process holds the key

The host links chio-kernel and calls ChioKernel directly, with no network hop. It holds the signing key and owns the stores, so its whole process is part of the trust boundary. Choose it when host code is trusted, the latency budget is tight, and the runtime serves one tenant. In-process integration.

admitted route
Rust call to the kernel to a registered tool server
signing key
seed in host memory, loaded from a secret backend
stores
owned and opened by the host process
trust boundary
the whole host process; host code must be trusted
In Rust · types.rs:11811 lines
/// Optional SQLite database path for durable receipt persistence.
#[arg(long, global = true)]
pub(crate) receipt_db: Option<PathBuf>,
// …
/// Optional file path for a persistent capability-authority seed.
#[arg(long, global = true)]
pub(crate) authority_seed_file: Option<PathBuf>,
// …
/// Optional SQLite database path for durable admission, trust authority, and remote MCP session state.
#[arg(long, global = true)]
pub(crate) session_db: Option<PathBuf>,
Three shapes from the deployment guides. The outlined process holds the kernel, its key, and its stores; the host process, sidecar, and client are examples, and container, network, and sandbox settings decide the isolation a deployment achieves.Architecture

In process fits when three things hold at once. The binary that links the kernel is built, signed, and operated by the team that owns the signing key, and third-party agent code stays out of it. The latency budget is tight enough that a localhost HTTP hop matters for a measured workload. The runtime serves one tenant, with one signing key, one policy, and one process lifecycle.

Choose the sidecar when the host loads untrusted code (third-party agent runtimes, scripted plugins, model-generated code), since a compromised host can read the key and forge receipts. Choose it as well when one process enforces different policies or signing identities per tenant, when policy rolls without a host restart, or when the kernel scales and upgrades apart from the host. In process, account for dependencies, privileged debugging, and crash dumps: each can read process memory.

1. Add the kernel crates at a reviewed revision

The in-process API is the chio-kernel crate, whose library target is chio_kernel, and its chio-core dependency, published as chio-core-types. Pin both to the exact release your node runs, so the receipts the host signs match the verifier your auditors use.

Cargo.tomltoml
[dependencies]
chio-kernel = "=0.2.0"
chio-core   = { package = "chio-core-types", version = "=0.2.0" }
tokio       = { version = "1", features = ["rt-multi-thread", "macros"] }
tracing     = "0.1"

The kernel is the trusted computing base, so review each bump as a deliberate change. The delegation feature is on by default and pq is off; the feature reference lists all four.

2. Load the signing seed from a secret backend

The kernel signs every receipt with an Ed25519 keypair built from raw seed bytes, and the keypair stays on the heap inside the host process for as long as the kernel lives. Load the seed from a managed secret backend at startup. A seed in a checked-in file or a build-time constant lets anyone who reads it produce receipts that verify against the kernel's public key.

The kernel takes ownership of the keypair through chio_core::crypto::Keypair, so the host keeps no copy of the raw seed bytes. Keep the seed out of logs and out of any file the process can read after init, and zeroize it on shutdown if it survives across reloads. Secrets & Keys lists the supported backends and the rotation workflow.

3. Build the kernel with durable stores required

ChioKernel::new takes a KernelConfig: the signing keypair, the trusted capability-authority public keys, the policy hash, safety settings, and two resource budgets. Leave both durability gates at false, so a dispatch succeeds only after durable persistence.

src/main.rs · build_kernelrust
use chio_core::crypto::Keypair;
use chio_kernel::kernel::{
    ChioKernel, HotPathDeadlineConfig, KernelConfig, MemoryBudgetConfig,
};

fn build_kernel(seed_hex: &str, policy_hash: String) -> ChioKernel {
    // Load the signing seed from the host's secret backend.
    // `Keypair::from_seed_hex` rejects malformed input.
    let keypair = Keypair::from_seed_hex(seed_hex)
        .expect("signing seed must be a 64-char hex Ed25519 seed");

    let config = KernelConfig {
        keypair,
        ca_public_keys: Vec::new(),       // populate from your CA roster
        max_delegation_depth: 4,
        policy_hash,
        allow_sampling: false,
        allow_sampling_tool_use: false,
        allow_elicitation: false,
        max_stream_duration_secs: 300,
        max_stream_total_bytes: 256 * 1024 * 1024,
        require_web3_evidence: false,
        allow_ephemeral_receipt_log: false,     // require durable receipts
        allow_ephemeral_revocation_store: false, // require durable revocation
        checkpoint_batch_size: 100,
        retention_config: None,
        memory_budget: MemoryBudgetConfig::defaults(),
        deadlines: HotPathDeadlineConfig::default(),
    };

    ChioKernel::new(config)
}

memory_budget sets bounded-structure caps and an RSS soft ceiling, and deadlines sets wall-clock budgets for the guard pipeline, dispatch, and receipt append. The crate exports DEFAULT_MAX_STREAM_DURATION_SECS, DEFAULT_MAX_STREAM_TOTAL_BYTES, and DEFAULT_CHECKPOINT_BATCH_SIZE as starting values; size stream limits and checkpoint batches for your host from there.

ChioKernel::new runs in a synchronous context. The async signing task spawns on the first signing call, when a Tokio runtime must be active, so you can build the kernel in a plain constructor and pass it into the runtime, or build it inside #[tokio::main].

4. Register providers, then share one kernel across tasks

Register tool servers, resource providers, and prompt providers before serving. The agent then connects through open_session, the kernel issues initial capabilities, and tool calls flow through the guard pipeline; every allow or deny decision produces a signed receipt. The kernel is built for the multi-threaded Tokio runtime.

  • Send + Sync. Wrap one ChioKernel in Arc<ChioKernel> and call it from any thread or task.
  • Async signing path. The signing task is mpsc-backed and lives on the Tokio runtime from the first sign call on. Keep the runtime alive for the kernel's whole lifetime.
  • Bounded backpressure. Callers .await a bounded channel in place of the keypair, so concurrent tool calls never serialize on a single mutex.
  • Single-thread runtimes. A kernel built inside tokio::runtime::Builder::new_current_thread() works for CLI-style hosts. The signing task runs on that runtime, so keep other threads from blocking it.

5. Drain every session before the process exits

On SIGTERM or SIGINT, stop accepting new sessions, call begin_draining_session and then close_session for every active session, drop the kernel, and return from main. The drain flushes pending receipts to the configured store, drains the signing task, and emits a final checkpoint when the receipt count crosses a checkpoint_batch_size boundary. A process that exits first can lose receipts still buffered in the signing task.

A host that owns long-running work follows the same order. It installs durable admission and receipt stores before issuance and bounds active jobs with execution permits. SIGTERM or Ctrl-C stops new admission, requests cooperative cancellation, and joins the workers through final persistence.

A forced stop is an error that calls for inspecting the retained request and any partial document. Give SIGHUP no rebuild handler: the kernel has no live reload, and configuration changes go through the replacement procedure below.

6. Run the pricing host and watch it refuse

The Rust variant of the pricing-service example links the kernel into the axum process that serves your quotes. A chio-tower layer sits in front of every route, signs with a node key the project's kernel certifies, and writes each decision into the project's history. It assumes the quickstart project from Quickstart part 2.

rust/src/main.rs · in mainrust
let project = Computer::open("quickstart").await?;
let node_key = project.node_key("pricing-http").await?;
let policy_hash = project.policy().hash().await?;
let layer = ChioLayer::builder(node_key, policy_hash)
    .receipt_store(project.history().receipt_store())
    .revocation_store(project.revocation_store())
    .build()?;

let app = Router::new()
    .route("/v1/quote", post(quote))
    .route("/v1/discount", post(discount))
    .route("/v1/refund", post(refund))
    .layer(layer);
Terminalbash
chio new pricing --example pricing-service
cd pricing/rust
cargo run --locked

In another terminal, ask for a refund on order-77 with no capability.

Terminal 2bash
curl -si -H 'Content-Type: application/json' \
  -d '{"order":"order-77"}' \
  http://127.0.0.1:8000/v1/refund
Output
HTTP/1.1 403 Forbidden
content-type: application/json
x-chio-receipt-id: rcpt-01d4…6b20

{"error":"chio_access_denied","message":"side-effect route requires a capability token","receipt_id":"rcpt-01d4…6b20"}

You should see a 403 and a receipt id. The refund handler never ran, and the signed refusal is already in the project's history: chio receipt list --admin-all --outcome deny shows it under the pricing-http node key. The same request carrying the outside buyer's quote grant is refused too, because that grant names quote and not refund.

The Rust systems tour covers the ownership and persistence mechanisms behind an embedded host.

A configuration change replaces the kernel

The pricing host keeps policy and provider registries fixed after startup, and every receipt records the policy hash in force. To roll its configuration, stop admission, drain the owned work, construct a replacement kernel, and reopen admission. Keep the previous policy bytes so receipts signed under that policy stay interpretable.

A host that manages several kernels keeps each instance's signing identity, authority pins, stores, and lifecycle explicit. Instances in one address space share that process's memory trust boundary, so run separate supervised processes when the identities need isolation from each other. A separately deployed sidecar rolls the kernel without redeploying the application, under its own drain and admission procedure.

Startup and shutdown order explain the common failures

The kernel refuses its seed

Keypair::from_seed_hex expects a 64-character hex Ed25519 seed. Check the value the secret backend returns, then restart the host.

The first signing call has no runtime

The signing task spawns on the first sign call and lives on the Tokio runtime. Build or move the kernel into the runtime before the first governed call, and keep that runtime up until the kernel is dropped.

Receipts are missing after a stop

The process exited, or was killed, before its drain finished. Inspect the retained requests and partial documents, then put the drain ahead of exit in the shutdown path and give the supervisor a stop timeout that covers it.

Crate features, signing floors, and host conventions

Crate features

FeatureDefaultEffect
delegation
on
Recursive-delegation integration: the verifier consults the installed RevocationView snapshot on every delegated dispatch and denies a capability when any link's capability_id is in the revoked set.
pq
off
Turns on pq in chio-core-types and chio-core so the boot path can build a hybrid ML-DSA-65 signing backend.
otel
off
OpenTelemetry semantic conventions for kernel spans, paired with the host's tracing-otel exporter.
tokio-console-smoke
off, test-only
Pulls in tokio/tracing for the console-smoke test target.

Hybrid post-quantum signing

Build chio-kernel with the pq feature to sign receipts and capability tokens with a hybrid classical-plus-ML-DSA-65 backend. A HybridSigningConfig sets the deployment's floor: KernelCryptoFloor::AllowClassical (the default) accepts classical-only Ed25519 envelopes, AllowHybrid accepts hybrid envelopes, and PqRequired rejects classical-only envelopes and requires hybrid signing on each signed envelope. Both non-classical floors need a 32-byte ML-DSA-65 keygen seed.

src/main.rs · signing floorrust
use chio_kernel::{HybridSigningConfig, KernelCryptoFloor};

let hybrid = HybridSigningConfig {
    crypto_floor: KernelCryptoFloor::AllowHybrid,
    // 32-byte ML-DSA-65 keygen seed, loaded from the secret backend.
    pq_signing_seed: Some(pq_seed),
};

After the kernel self-quote gate accepts the non-classical floor, ChioKernel::with_hybrid_signing_backend joins the Ed25519 keypair and a HybridBackend built from the rolled ML-DSA-65 key into a Box<dyn SigningBackend>. The hybrid config is an input separate from KernelConfig, so enabling it leaves the KernelConfig wire bytes unchanged.

Log flush on shutdown

The kernel emits structured tracing spans. Install a subscriber that flushes on shutdown: drop the guard of tracing_appender::non_blocking explicitly, or use the synchronous tracing_subscriber::fmt. A subscriber that buffers without a flush can lose the last allow and deny decisions made during the drain.

PID files

Under systemd, write a PID file at /run/<service>.pid and name it in the unit's PIDFile= directive. Containers need no PID file, since the orchestrator tracks PID 1.

Next: put the seed in a secret backend