Chio/Docs
LOGIN · JOIN

How a Call Enters the Kernel

Choose where the kernel meets your application: inside a Rust service, behind an MCP endpoint, or at a proxy’s authorization gate.

Concept chio@5f63e6e

On this page

Bring existing services into an agentic operating system by putting their operations behind the kernel’s admission decision. A service can keep its handler and storage while Chio evaluates authority before it accepts governed work. The integration makes the service’s resources available to agents under explicit operating rules.

Where the kernel joins the application determines two things: which requests it can stop, and how much of each request its checks can see. Chio can join through middleware inside the service, through a protocol server that exposes governed tools, or through a proxy that asks for an authorization decision before forwarding.

Admission belongs in front of the effect

Start with the service that performs the effect. Put the admission check before that service accepts the work, and make its result binding on the path to execution. An adapter translates the protocol; the configured authority evaluates the request. Network and process isolation must close any alternate route around that check.

The two common shapes differ in who holds the request. Inline middleware gates the call inside the application’s own process: the request, the admission check, and the handler share one stack. With a proxy such as Envoy, the proxy keeps the payload path and asks a separate authorization service for a verdict before it forwards the request. Either way, the check is binding only if the service cannot be reached around it.

A refused write leaves the database unchanged

Start with a familiar effect: a price change in SQLite. The pricing-rust example serves the pricing package from the Quickstart over HTTP and wraps its Axum router in ChioLayer. Its embedded evaluator admits safe methods such as GET and requires a capability for side-effect methods such as POST.

Use Rust 1.94.1 and jq. Cargo fetches the published chio crates pinned in the project’s Cargo.lock.

Terminal 1bash
chio new pricing-rust --example pricing-rust && cd pricing-rust
cargo run --locked --release
Terminal 2 · pricing-rustbash
# Read the price before attempting a change.
curl --fail-with-body -sS http://127.0.0.1:9440/v1/prices/sku-301

# No capability: expect HTTP 403.
curl -sS -i http://127.0.0.1:9440/v1/discount \
  -H 'Content-Type: application/json' -d '{"sku":"sku-301","percent":15}'

# The refused change must not appear.
curl --fail-with-body -sS http://127.0.0.1:9440/v1/prices/sku-301

# Supply the local host's five-minute grant: expect HTTP 201.
curl --fail-with-body -sS -i http://127.0.0.1:9440/v1/discount \
  -H 'Content-Type: application/json' \
  -H "X-Chio-Capability: $(jq -c . .pricing/capability.json)" \
  -d '{"sku":"sku-301","percent":15}'
curl --fail-with-body -sS http://127.0.0.1:9440/v1/prices/sku-301

# Admitted, then refused by the application: expect HTTP 400.
curl -sS -i http://127.0.0.1:9440/v1/discount \
  -H 'Content-Type: application/json' \
  -H "X-Chio-Capability: $(jq -c . .pricing/capability.json)" \
  -d '{"sku":"sku-301","percent":120}'
Bodies and status lines, in order
{"sku":"sku-301","price":"24.00"}
HTTP/1.1 403 Forbidden
{"sku":"sku-301","price":"24.00"}
HTTP/1.1 201 Created
{"sku":"sku-301","price":"20.40"}
HTTP/1.1 400 Bad Request
Six requests to the pricing service and the layer each one reachedrequestChioLayeradmissionhandlerpricing.dbGETread price200POSTno capability403GETread price200POSTcapability201GETread price200POSTpercent 120400
∣changes∣|\text{changes}|
nn + 1unchanged after the 403one row, at the 201
∣changes∣k=n+#{ j≤k:sj=201 }|\text{changes}|_k = n + \#\{\, j \le k : s_j = \htmlClass{role-seal}{201} \,\}
row added⇒admitted,admitted⇏row added\text{row added} \Rightarrow \htmlClass{role-call}{\text{admitted}}, \qquad \htmlClass{role-call}{\text{admitted}} \nRightarrow \text{row added}
HTTP/1.1 403 Forbidden x-chio-receipt-id: 636b…53ab…CapabilityGuard: side-effect route requires a capability token. The handler never runs.
Six requests to the pricing service and the layer each one reachedChiohandlerchangesGETread pricen200POSTno capabilityn403GETread pricen200POSTcapabilityn + 1201GETread pricen + 1200POSTpercent 120n + 1400HTTP/1.1 403 Forbiddenx-chio-receipt-id: 636b…53ab…CapabilityGuard: side-effect routerequires a capability token. The handlernever runs.

POST no capability: HTTP/1.1 403 Forbidden. CapabilityGuard: side-effect route requires a capability token. The handler never runs.

∣changes∣k=n+#{ j≤k:sj=201 }row added⇒admittedadmitted⇏row added\begin{aligned} &|\text{changes}|_k = n + \#\{\, j \le k : s_j = \htmlClass{role-seal}{201} \,\} \\ &\text{row added} \Rightarrow \htmlClass{role-call}{\text{admitted}} \\ &\htmlClass{role-call}{\text{admitted}} \nRightarrow \text{row added} \end{aligned}
In Rust · main.rs:4517 lines
async fn add_discount(
    State(database): State<database::Database>,
    Json(change): Json<NewDiscount>,
) -> axum::response::Response {
    use axum::response::IntoResponse;
    if !(0..=100).contains(&change.percent) {
        return (
            StatusCode::BAD_REQUEST,
            Json(json!({"error":"percent must be between 0 and 100"})),
        )
            .into_response();
    }
    match database.apply_discount(&change.sku, change.percent).await {
        Ok(price) => (StatusCode::CREATED, Json(price)).into_response(),
        Err(error) => ApiError(error).into_response(),
    }
}
Requests against the pricing-rust service for sku-301. Reads also carry receipts, omitted here. n is the number of price changes the log held before the first request.

A change without a capability receives 403 before the handler runs. The unchanged price afterward is the negative control. The same change with the host-issued capability reaches the handler and records one row in the change log, and sku-301 drops from USD 24.00 to USD 20.40.

The last request is admitted and still fails: the handler rejects a percentage above 100, the bound the fixer added for issue #412. Admission establishes that a request may enter the handler, not that the application accepted its input. The grant authorizes the evaluator’s synthetic HTTP operation; it is not a per-route write budget.

Make admission part of the handler path

src/main.rs · router wiringrust
let app = Router::new()
    .route("/v1/prices/{sku}", get(read_price))
    .route("/v1/discount", post(add_discount))
    .with_state(database.clone())
    .layer(axum::extract::DefaultBodyLimit::max(8192))
    .layer(
        ServiceBuilder::new()
            .layer(HandleErrorLayer::new(|error: axum::BoxError| async move {
                eprintln!("Chio evaluation failed: {error}");
                (
                    StatusCode::BAD_GATEWAY,
                    Json(json!({"error":"chio_evaluation_failed"})),
                )
            }))
            .layer(host.layer.clone()),
    )
    .layer(axum::middleware::from_fn_with_state(
        retention.clone(),
        receipts::retain,
    ));

The host module, src/host.rs, installs receipt, revocation, and durable admission stores through ChioEvaluator::builder, then constructs ChioLayer::from_evaluator. The error layer turns a failed evaluation into a 502 with chio_evaluation_failed. The outer middleware retains the signed HTTP receipt returned with each response.

Inspect the response and its receipt together

Terminal 2 · pricing-rustbash
cargo run --locked --release --bin verify-http -- \
  .pricing/kernel-public-key.txt .pricing/http-receipts/*.json
Output
2bb2…f650 POST 201
636b…53ab POST 403
d5de…abd2 GET 200
d7d2…bd2e POST 400
e41c…7c10 GET 200
f5de…c388 GET 200

Each line reports a verified receipt’s ID, method, and final HTTP status. The output includes both the 403 that never reached the handler and the 400 that did. Match the X-Chio-Receipt-Id header on a response to its retained file. Verification checks the expected host key, the content-addressed ID, and the signature.

The receipt journal writes after the handler returns. It is not a transaction spanning the pricing and receipt stores: if saving the receipt fails, the application answers http_receipt_save_failed, and the change may already have happened.

Close every route around admission

The governed route is client → ChioLayer → add_discount → SQLite. A second unwrapped listener, direct database credentials, or code that calls the handler outside this stack would create another path to the same effect, and the kernel would never see it.

Give the agent access to the governed listener only. Keep the database, its credentials, and any unwrapped services inside the application’s trust boundary. Connect an application draws every route an agent could take and how to test that each one crosses the kernel.

Match the integration to your application

You already haveIntegrationWhere the decision runs
A Rust HTTP serviceChioLayer or ChioServiceTower middleware prepares the HTTP evaluation through HttpAuthority, then forwards allowed work to the inner service.
Rust code that constructs tool requestsKernelService or build_layeredA Tower service calls ChioKernel::evaluate_tool_call directly, without HTTP translation.
An MCP clientChioMcpEdgeThe edge owns the MCP session and routes supported tool, resource, prompt, and completion operations through the kernel, in process or exposed to a client.
Envoy in front of a serviceChioExtAuthzServiceEnvoy sends a gRPC CheckRequest. A supplied EnvoyKernel implementation evaluates its translated description, and Envoy enforces the response.

One application can use several of these entry points to reach resources through different protocols. Each brings calls to the kernel’s authority boundary, with its own wire format and feature set. An MCP tool call carries structured arguments. The Envoy translation carries an HTTP method, path, selected headers, caller metadata, and a body digest when a body is supplied. Choose an integration whose input can express the policy you need; Protocol Bridges compares protocol fidelity.

Retained information labels belong to identities, apart from any one entry point. The flow store keys them by tenant, principal, delegation lineage, session, and isolation epoch; no part of that key names the protocol that carried a call. When a host admits calls from several entry points against one flow store and resolves them to the same principal and lineage, a send through one entry point meets the labels left by a read through another, and needs a destination whose clearance admits them. Information flow shows how the host selects the principal and lineage for each call.

An adapter translates; the evaluator decides

Take a request for pricing/quote with {"sku":"sku-118","percent":10} as its arguments. The kernel needs the requested server and tool, the caller and session context, and the capability that authorizes the work. The adapter supplies the protocol-specific form of those inputs. It does not turn an asserted identity into permission.

For Envoy, the translated tool name derives from the HTTP method and path, and the adapter selects one credential in a fixed order:

OrderCredential the adapter selects
1The Chio capability header.
2A bearer token, represented by its SHA-256 digest.
3The peer principal.
4Anonymous identity.

Parsing a credential is only translation: the configured evaluator must establish its authenticity and authority. Raw authorization headers are excluded from the forwarded header map, and a supplied body is represented by its SHA-256 digest.

EnvoyKernel is an integration trait. Implement it against Chio’s kernel or HTTP authority to obtain enforcement. Constructing the gRPC adapter alone does not install a kernel, validate a capability, or create a receipt store.

Inspect the Envoy evaluation interface
crates/protocol/chio-envoy-ext-authz/src/service.rsrust
#[async_trait]
pub trait EnvoyKernel: Send + Sync + 'static {
    async fn evaluate(&self, request: ToolCallRequest) -> Result<Verdict, KernelError>;
}

The request and verdict here are the adapter’s local types. An implementation owns the conversion to the selected authority and must return an error on an internal evaluation fault.

Check the boundary you deployed

CheckWhy it matters
Admission controls the effectA decision gates only work routed through that integration. An agent with a direct path to the same resource bypasses an application-level check.
Translation bounds visibilityA body digest binds a value; it does not let a guard inspect the body’s plaintext. Protocol-derived names and identity fields need an explicit mapping to policy.
Adapter faults have a defined responseThe Envoy adapter returns a generic denied response on translation or evaluator errors. The Tower evaluator fails closed by default; its explicit with_fail_open option forwards unenforced requests when preparation fails and increments chio_fail_open_suspected_total.
Receipt coverage starts at the runtime pathA malformed transport request or an adapter failure can be rejected before a kernel decision is recorded. Check both the protocol response and the evidence path.
A crate boundary is not process isolationKeeping translation separate from policy makes authority easier to inspect. Protect the process, signing keys, stores, and proxy configuration that enforce it.

Trace or deploy the chosen path