In-Process Library
Link the kernel into a Rust binary, load its signing seed, and own its stores, lifecycle, and drain in one process.
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.
StartThe whole host process holds the key
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>,
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.
[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.
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 oneChioKernelinArc<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
.awaita 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.
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);chio new pricing --example pricing-service
cd pricing/rust
cargo run --lockedIn another terminal, ask for a refund on order-77 with no capability.
curl -si -H 'Content-Type: application/json' \
-d '{"order":"order-77"}' \
http://127.0.0.1:8000/v1/refundHTTP/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
Try pq, PqRequired, or PIDFile.
Crate features
| Feature | Default | Effect |
|---|---|---|
delegation | on | Recursive- 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- | 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.
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.