Chio/Docs
LOGIN · JOIN

Portable Kernel

Use the same authority model on servers, browsers, and phones, with execution and operational state supplied by each host.

Concept chio@5f63e6e

On this page

An agentic operating system can use the same authority model across the devices where people work and the hosts where operations run. Chio’s portable Rust core gives server, browser, and mobile applications shared capability evaluation and receipt primitives. Each platform supplies the surrounding execution and state services its role requires.

Each host retains its own execution authority. A check that runs in a browser tab can tell the page that a request would be refused. It cannot tell a tool to run, because the browser does not control the tool, its credentials, or the stores the hosted kernel consults.

One decision core, different operating boundaries

The chio-kernel-core crate is a no_std + alloc Rust library. It provides synchronous capability evaluation and canonical receipt operations. Clock and entropy enter through explicit interfaces. Transport, tool dispatch, databases, and platform key custody surround that core, and each target supplies them differently.

The core does not own a database or network connection. Richer evaluation entry points can consult explicit caller-owned state, including a mutable delegated-budget registry. That is different from the hosted runtime’s monetary stores, live revocation lookups, and approval workflow, which exist only where the native runtime runs.

The workspace’s dependency graph keeps that boundary. The core’s only workspace dependency is chio-core-types, the shared types; it has no dependency on a runtime, store, or protocol edge. Seven workspace crates build on it, among them the runtime kernel and the SQLite store.

Crate layersSelect a crate to trace its dependencies.
api-protectclicontrol-planemcp-edgeopenai-adapterstore-sqlitepolicyguardskernelsettlecorefederationkernel-coretool-call-fabricmetrics-speccore-typesapi-protectclicontrol-planemcp-edgeopenai-adapterstore-sqlitepolicyguardskernelsettlecorefederationkernel-coretool-call-fabricmetrics-speccore-types

Sixteen crates, nine layers

Every dependency among the drawn crates points down a layer or across one; none points up. Select a crate to draw what it depends on, in blue, and what depends on it. Architecture.

In Rust · lib.rs:11
//! This crate re-exports `chio-core-types` and the dedicated domain crates
//! (`chio-appraisal`, `chio-autonomy`, `chio-credit`, etc.) under a single
//! `chio_core::*` import surface for consumers that prefer a unified entry point.
// …
pub use chio_appraisal as appraisal;
pub use chio_autonomy as autonomy;
// …
pub use chio_credit as credit;
// …
pub use chio_federation as federation;
Sixteen of the 163 workspace crates, at most three to a layer, with dependencies read from each Cargo.toml at the pinned revision. Dev-dependencies are left out, since a test may depend on any crate. The layers group crates by role and are ordered so that no drawn dependency points up.Architecture

Because the core depends on nothing above it, it builds for a browser or a phone without the runtime: each target links the core and supplies the rest itself.

A local check can refuse; it cannot authorize

The browser exercise evaluates one signed capability for srv-files/file_read five times: as captured, repeated, after its expiry, with its issuer removed from the trusted set, and for a different tool. Each evaluation passes the envelope to the core, then converts the core’s verdict into the value JavaScript receives.

Five captured browser evaluations mapped from the core verdict to the browser verdict; none is authorizedcaptured evaluationcore κbrowser φ(κ)dispatchValid contextas capturedSame context againrepeatedAfter expiryclock = expires_at + 1Issuer removedtrusted_issuers = []Different tooltool_name = file_writeAllowDenyPendingApprovalnever returned by the coredowngradedpending_approvaldenyauthorized: falseneeds a mediatedprevent receiptverdict: "pending_approval"capability_verdict: "allow"authorization_basis: "capability_only"
φ(Allow)=pending_approvalφ(Deny)=denyauthorized=false\begin{aligned} \varphi(\htmlClass{role-call}{\text{Allow}}) &= \htmlClass{role-grant}{\texttt{pending\_approval}} \\ \varphi(\htmlClass{role-deny}{\text{Deny}}) &= \htmlClass{role-deny}{\texttt{deny}} \\ \texttt{authorized} &= \htmlClass{role-deny}{\texttt{false}} \end{aligned}
Five captured browser evaluations mapped from the core verdict to the browser verdict; none is authorizedcaptured evaluationcore κbrowser φ(κ)Valid contextas capturedSame context againrepeatedAfter expiryclock = expires_at + 1Issuer removedtrusted_issuers = []Different tooltool_name = file_writeAllowDenyPendingpending_approvaldenyauthorized: falseno dispatchneeds a mediatedprevent receipt

Valid context: the core returned allow; the browser reports pending_approval with authorized false. Reason: capability-only browser evaluation requires a mediated prevent receipt before execution.

φ(Allow)=pending_approvalφ(Deny)=denyauthorized=false\begin{aligned} \varphi(\htmlClass{role-call}{\text{Allow}}) &= \htmlClass{role-grant}{\texttt{pending\_approval}} \\ \varphi(\htmlClass{role-deny}{\text{Deny}}) &= \htmlClass{role-deny}{\texttt{deny}} \\ \texttt{authorized} &= \htmlClass{role-deny}{\texttt{false}} \end{aligned}
In Rust · wire.rs:15429 lines
pub(crate) fn from_core(value: chio_kernel_core::EvaluationVerdict) -> Self {
    let capability_verdict = match value.verdict {
        chio_kernel_core::Verdict::Allow => "allow",
        chio_kernel_core::Verdict::Deny => "deny",
        chio_kernel_core::Verdict::PendingApproval => "pending_approval",
    };
    let (verdict_str, reason, authorization_basis) = match value.verdict {
        chio_kernel_core::Verdict::Allow => (
            "pending_approval",
            Some(
                "capability-only browser evaluation requires a mediated prevent receipt before execution"
                    .to_string(),
            ),
            "capability_only",
        ),
        chio_kernel_core::Verdict::Deny => ("deny", value.reason, "denied"),
        chio_kernel_core::Verdict::PendingApproval => {
            ("pending_approval", value.reason, "pending_approval")
        }
    };
    // … subject, issuer, and capability id from the verified token
    Self {
        verdict: verdict_str.to_string(),
        capability_verdict: capability_verdict.to_string(),
        reason,
        authorized: false,
        // …
    }
}
The five evaluations of the browser exercise, as recorded in results.json. The core verdict is diagnostic; the browser result is never authorization, and no tool is dispatched from it.

The conversion is a function, and it has no branch that authorizes. A core Allow becomes pending_approval with the reason that a mediated prevent receipt is required before execution. A core Deny stays deny and keeps the core’s reason: capability has expired, capability issuer is not a trusted CA, or the out-of-scope message. Every result carries authorized: false.

The raw verdict survives as capability_verdict so a page can explain itself. It is diagnostic, not permission. To act, send the requested work to the service that controls the tool. That service authenticates the caller and applies its own admission checks before execution; the pricing service shows an unauthorized price change that never reaches the database handler. A browser-supplied verdict, or a receipt signed by an arbitrary client key, cannot substitute for the service’s authority.

Choose the host for the responsibility

An application can put local authority checks and record verification in its interfaces while leaving execution with the host that controls the tools. Assign each responsibility to a host that supplies what it needs.

TargetWhat you can useWhat you must supply
Native runtimeMediated admission, guards, dispatch, accounting, and receipts.Tools, policy, identities, configured stores, and the process and network boundary.
Browser WASMCapability-only evaluation, capability and receipt verification, content-aware signing.Trusted keys, supported request context, and a mediated execution host. A local success remains pending approval.
Mobile UniFFILocal evaluation, receipt primitives, capability and passport verification, and platform attestation entry points.Matching bindings, a trustworthy clock, key custody, application enforcement, and synchronization.

In code, much of what a host supplies arrives as Rust traits. Protocol edges drive the runtime through its evaluation API, and guards and hooks, stores and authorities, and tool connections plug into its other ports. The same port takes different adapters in different deployments: state can live in SQLite files, behind the trust-control service over HTTP, or in memory for local runs.

Kernel portsChoose a port.
drives the kernelthe kernel calls out to

The kernel depends on traits, and adapters fill them

The kernel depends on traits, not on databases, transports, or rails. A deployment wires one adapter to each port when it builds the kernel, and edges drive it through the evaluation API. Portable kernel.

In Rust · construction.rs:914
pub fn set_payment_adapter(&mut self, payment_adapter: Box<dyn PaymentAdapter>) {
// …
pub fn set_revocation_store(&mut self, revocation_store: Box<dyn RevocationStore>) {
// …
pub fn set_budget_store(&mut self, budget_store: Box<dyn BudgetStore>) {
// …
pub fn add_guard(&mut self, guard: Box<dyn Guard>) {
// …
pub fn register_tool_server(&mut self, connection: Box<dyn ToolServerConnection>) {
Ports are the Rust traits the kernel and the decision core depend on; adapters are the workspace crates that implement or drive them, test doubles left out. A deployment wires one adapter to each port it uses when it builds the kernel.

The browser, mobile, and C bindings are core-only hosts. They link the decision core without the runtime and supply its clock and randomness, which is why the browser and mobile rows in the table offer evaluation, verification, and signing, but no mediated admission or dispatch.

Run the browser exercise

Open the executable decision-context exercise. It loads a real WebAssembly build and evaluates a signed historical test vector five times, the evaluations drawn above. It performs no external work and needs no account or credential.

Run the same exercise locally

Download and serve the exercisebash
curl --fail --location https://www.chio.computer/examples/architecture-lab.zip -o architecture-lab.zip
unzip architecture-lab.zip
cd architecture-lab
python3 -m http.server 8080 --bind 127.0.0.1

Open http://localhost:8080. The download includes the WASM module, JavaScript, fixture, and recorded output. To rebuild the module from the public Chio checkout, run wasm-pack build --target web --release crates/kernel/chio-kernel-browser and replace the exercise’s pkg directory with that build.

Read the JavaScript return value

Local capability checkjavascript
import init, { evaluate } from './pkg/chio_kernel_browser.js';
await init();
const envelope = await fetch('./evaluation.json').then(r => r.json());
const result = evaluate(JSON.stringify(envelope));
console.log(result.verdict);             // pending_approval
console.log(result.capability_verdict);  // allow
console.log(result.authorized);          // false
// Do not dispatch a tool from this local result.

The fixture pins time inside the capability’s signed validity window. Production evaluation uses the host clock; do not copy a historical clock override into a live authorization path. evaluate already returns a JavaScript value, so applying JSON.parse to it is incorrect.

Use a preflight in a browser application

preflight.jsjavascript
import init, { evaluate } from './pkg/chio_kernel_browser.js';
let ready;
export async function preflight(envelope) {
  await (ready ??= init());
  const result = evaluate(JSON.stringify(envelope));
  if (result.verdict === 'deny') {
    throw new Error(result.reason ?? 'Capability check refused');
  }
  return result; // Still unauthorized; submit work to your governed service.
}

A browser preflight can stop a request that fails the local capability check. Load the web-target module in client code, including a Next.js client boundary when using that framework, and keep the authoritative check on the service that executes the request. The browser guide covers the adapter’s wire format, signature checks, and target-specific build commands.

Integrate mobile evaluation and signing

Generate Swift or Kotlin bindings from the same source revision as the native library. The current sign_receipt interface takes three arguments: receipt-body JSON, canonical-content bytes encoded as hex, and a signing seed encoded as hex. Older two-argument generated bindings must be regenerated together with the library.

The following functions are integration helpers. Supply a complete evaluation envelope containing a parsed capability object, trusted_issuers, request, and optionally now_secs. A positive timestamp fixes time for a test; otherwise the mobile adapter uses its clock. The mobile API returns JSON text, unlike the browser API.

Swift

LocalDecision.swiftswift
import Foundation
import chio_kernel_mobile

func localDecision(_ envelope: [String: Any]) throws -> [String: Any] {
    let bytes = try JSONSerialization.data(withJSONObject: envelope)
    let result = try evaluate(requestJson: String(decoding: bytes, as: UTF8.self))
    guard let value = try JSONSerialization.jsonObject(
        with: Data(result.utf8)
    ) as? [String: Any] else {
        throw CocoaError(.coderReadCorrupt)
    }
    return value
}

func signLocalRecord(bodyJSON: String, content: Data, seedHex: String) throws -> String {
    let contentHex = content.map { String(format: "%02x", $0) }.joined()
    return try signReceipt(
        bodyJson: bodyJSON,
        canonicalContentHex: contentHex,
        signingSeedHex: seedHex
    )
}

Kotlin

With UniFFI 0.32, the generated error payload named message collides with Kotlin’s inherited Throwable.message. Use the supplied generator configuration to name that Kotlin payload detail and select the native library name. This changes generated language names, not the native wire format.

Generate matching Kotlin bindingsbash
uniffi-bindgen generate \
  crates/kernel/chio-kernel-mobile/src/chio_kernel_mobile.udl \
  --language kotlin --config ../architecture-lab/mobile/uniffi.toml \
  --out-dir bindings --no-format

Run this from the public source checkout with a matching UniFFI 0.32 generator. The exercise includes binding setup and qualification notes, both helper files, and a mobile evaluation fixture. Kotlin uses org.json for object serialization and JNA for the generated native bridge.

LocalDecision.ktkotlin
import org.json.JSONObject
import uniffi.chio_kernel_mobile.evaluate
import uniffi.chio_kernel_mobile.signReceipt

fun localDecision(envelope: JSONObject): JSONObject =
    JSONObject(evaluate(requestJson = envelope.toString()))

fun signLocalRecord(bodyJson: String, content: ByteArray, seedHex: String): String {
    val contentHex = content.joinToString("") {
        (it.toInt() and 0xff).toString(16).padStart(2, '0')
    }
    return signReceipt(
        bodyJson = bodyJson,
        canonicalContentHex = contentHex,
        signingSeedHex = seedHex
    )
}

Use JSON objects for the capability and arguments; do not build nested JSON by concatenating strings. Check the returned verdict before your application admits an effect. An offline evaluation establishes a result against the supplied view; it cannot discover a later server-side revocation while disconnected.

Signing is a separate responsibility. These helpers accept raw signing seeds; they do not demonstrate Secure Enclave or Android hardware-keystore custody. Platform attestation, custody integration, and device qualification require their own configured path, and a locally signed record also needs an application-defined upload and verification protocol.

Keep the content inside the signing boundary

Content-aware signing recomputes the hash of the supplied canonical bytes and refuses a mismatch with the receipt body. In the browser, supply { body, canonical_content: [...] } to sign_receipt. In mobile bindings, supply the equivalent bytes as canonicalContentHex.

Signing callWhat it checks before signing
Browser sign_receiptRecomputes the hash of the supplied canonical bytes and refuses a mismatch with the body’s content hash.
Mobile signReceiptThe same content check, and that the body’s kernel key equals the public key derived from the signing seed.
sign_receipt_relaying_trusted_bodyNo content check: it trusts the supplied body hash, for a signer that does not hold the preimage.

Do not use the relaying call when the signer has the content. A verifier must still decide independently whether the signing identity is trusted. Shared canonical bytes let platforms verify the same record format; they do not make every signer an execution authority.

Keep operational authority in the native runtime

Embed the kernel in Rust, put an MCP server behind the bridge, or expose an authenticated HTTP service. Configure the stores and tool connections for the deployment. The repository-review example embeds the kernel in a Rust application; HTTP service deployment explains a shared listener. Portability does not require every application to use the same transport.

Qualify the boundary you ship

The published Swift and Kotlin helpers were compiled against freshly generated bindings and exercised through the native FFI on macOS: the valid fixture allowed and the expired fixture denied, matching content signed and changed content was rejected. Kotlin used the supplied rename configuration. These host checks do not establish iOS or Android device behavior, hardware custody, or attestation.

CheckWhat it establishes
Core compilationThe selected Rust target and features build.
Browser exerciseThe WASM interface returns the recorded capability decisions. No tool is dispatched.
Generated mobile bindingsThe language interface matches the native library. Host compilation does not qualify physical-device custody or attestation.
Application integrationA real refused request cannot reach the protected effect through the tested path.

Source contracts: browser result conversion, mobile binding interface, and mobile implementation.

Connect the host to the rest of the system