Chio/Docs
LOGIN · JOIN

Emergency Stop

Halt every call routed through a kernel with one signed record that survives restart, then resume it after review without restoring revoked authority.

Concept chio@5f63e6e

On this page

An operator can stop a kernel’s work with one command and rely on that stop through a crash, a restart, or a replaced host. chio emergency-stop appends a signed record to the admission store, and from that commit the kernel refuses new effects until an authorized resume record commits.

The stop is reachable from the CLI, the process-host socket, and the mounted HTTP route, and the CLI appends it directly while the host is down. Everything an incident needs stays available while the kernel is stopped: revocation, verification, receipt signing, reconciliation, cancel, inspect, and shutdown.

Stop epochChoose a moment.
operatorsstop recordcallsrestartpreviousrunningstoppedready_stoppedreadyKkernelheads loadκ₁operatorchio emergency-stope₁epoch 1Stopκ₂second principalchio emergency-resumee₂epoch 2Resumer₁ output withheld in durable custodyreleasedr₁admittedr₂refused · e₁r₃refused · e₁r₄admittedoperatorrecordcallsrestartpreviousrunningstoppedready_stoppedreadyKkernelheads loadκ₁stopse₁epoch 1κ₂resumese₂epoch 2withheldcustodyreleasedr₁admittedr₂refusedr₃refusedr₄admitted

One operator stops the kernel with a signed record

chio emergency-stop, from the CLI, the process-host socket, or the mounted HTTP route, appends stop epoch 1 to the hash-chained stop record in the admission store. From that commit the kernel refuses every call routed through it, at entry and again inside the crossing that would dispatch it, and each deny receipt names the epoch it observed. Emergency stop.

One kernel scope on a time axis: two stop epochs, one restart, and four calls routed through the kernel. Times are illustrative; tenant and recovery scopes, the stop-intent latch, and containment dispositions are left out.

One authenticated request stops a kernel scope

One authenticated operator is enough to stop. The request names a reason and a scope: the kernel, one tenant, or one recovery scope.

Terminalbash
curl -sS -X POST "$CHIO_URL/emergency-stop" \
  -H "X-Admin-Token: $CHIO_ADMIN_TOKEN" -H "Content-Type: application/json" \
  -d '{"reason": "fixer read PR #421 and touched /secrets"}'
chio emergency-status
Output
result               stop_durable
scope                kernel
epoch                1

readiness            ready_stopped
stopped              true
reason               fixer read PR #421 and touched /secrets
host_latch           none
withheld_operations  0

You should see stop_durable (the output is illustrative): the record is committed and anchored. Each transition is a signed chio.stop-epoch.v1 record in a hash-chained table, where every record names the digest of the one before it. The record keeps a salted SHA-256 commitment to the reason; the reason text lives in an operator-readable note table.

The routes POST /emergency-stop, POST /emergency-resume, and GET /emergency-status are mounted on the Trust Control Plane, the API sidecar, and the hosted MCP admin service, and each compares its X-Admin-Token in constant time. The process-host socket lives in the host’s runtime directory and checks the peer’s owning uid plus the control credential. chio emergency-stop, chio emergency-resume, and chio emergency-status drive either one, and with the host down the CLI takes the serving-owner lock and appends the stop itself.

The HTTP control handler

Each mounting service constructs one handler at startup and passes it to all three routes. A request without the configured token is refused as EmergencyHandlerError::Unauthorized, HTTP 401.

crates/platform/chio-http-core/src/emergency.rs146–149rust
pub struct EmergencyAdmin {
    kernel: Arc<ChioKernel>,
    expected_admin_token: String,
}

The kernel refuses at entry and again inside the crossing

Every kernel attached to one admission store reads the same stop heads, so a stop reaches every hosted session over that store, including the sidecar’s per-request HTTP authority. The first check is that early read at each entry site, and a process host makes it before committing a call slot, so a stopped call spends no call budget.

The second check decides. It runs inside the writer transaction that would let an effect cross custody, and each kind of crossing has a recorded disposition:

CrossingDispositionWhile stopped
DispatchIntent, CallerStart, NativeCapture, MutationSubmitDenyRefused; nothing crosses
RecoveryCapture, SemanticCaptureDenyRefused, recovery scopes included
OutputRelease, ArtifactRelease, ConfinedReturn, ExternalEvaluationWithholdHeld in durable custody until resume
ExternalPrepare, FederationCosign, ChannelReleasePublishSettleCompletes effects committed before the stop
ActiveResponseExecuteAllowIfContainmentRuns when the stop allows containment

A settling crossing passes only for effects ordered before the stop record’s commit, so a new rail authorization is refused while a capture of an existing hold completes. The checks follow the fail-closed convention, and the overload shed in Backpressure runs right after the early stop check on the same fast path.

Early check sites in the kernel source
  • Tool-call and nested-flow entry. crates/kernel/chio-kernel/src/kernel/evaluation/async_evaluation_core.rs and nested_flow_evaluation.rs
  • Resource, prompt, and completion entry, and plan evaluation. crates/kernel/chio-kernel/src/kernel/evaluation/evaluation_entry.rs
  • Last-moment revalidation before dispatch. crates/kernel/chio-kernel/src/kernel/dispatch.rs
  • Native capture. crates/kernel/chio-kernel/src/kernel/credential_reservation/native_dispatch.rs
  • Native lifecycle handoff at dispatch commit. crates/kernel/chio-kernel/src/kernel/admission_coordinator/native_egress/lifecycle.rs
  • Native output release. crates/kernel/chio-kernel/src/kernel/admission_coordinator/native_output.rs
  • Caller output release and replay, for a return that carries a caller-delivery report. crates/kernel/chio-kernel/src/kernel/admission_coordinator/terminal.rs
  • Approval collection. crates/kernel/chio-kernel/src/kernel/admission_coordinator/collection_context.rs
  • Finding-pool debit, in builds with the finding market. crates/kernel/chio-kernel/src/finding_pool.rs
  • Delegation-parent registration and ordinary capability issuance. The early read plus a separate read of the durable head.

Work already in motion stays in the incident

A stop never recalls an effect that crossed before it and never dispatches one again. When a tool admitted before the stop returns during it, the kernel withholds the output and keeps the bytes in durable custody. The caller receives OutputWithheld with retry: AfterResume: an output_withheld error on JSON-RPC, status withheld from the process ABI. After resume, a replay with the same request ID returns the released output.

A non-durable side-effecting call has no custody to resume from. Its result carries retry: Never and effect_executed: true; treat that call as executed with its output lost. A parked operation waiting for approval stays parked. An approval that arrives during the stop is refused at the edge without being consumed, and you resubmit it after resume.

Governed active response, whose every effect narrows authority, runs during a stop when the stop allows containment. For a forensic freeze, stop with containment refused; a restrict transition tightens an existing stop in place. Reconcile the original operations under their existing identities before you decide what may continue.

Read more in Unknown outcomes.

A stopped kernel restarts stopped and still refuses

On boot the kernel loads and verifies the stop heads before the reconciliation sweep runs. The sweep retains operations whose output the stop withholds, and the host reports ready_stopped. Health probes see a live, serving process, and every refusal and withholding is in force from the first request.

  1. A failed or unknown commit. A stop-intent latch is fsynced before the transaction. The route returns stop_not_durable or stop_outcome_unknown, and the next boot still comes up stopped.
  2. A broken chain. A digest mismatch or epoch gap leaves the kernel not_ready with stop_chain_unverified. The kernel scope is latched stopped, and the stop and status routes still answer.
  3. A host that is down. The CLI appends the stop under the serving-owner lock, and the next boot loads it.

A kernel with no durable admission store, as in ephemeral development profiles, keeps its stop in process and reports durability: process_local. Work profiles and process hosts run on durable admission and get the durable stop.

Read more in Protect durable state.

A host latch lasts for one kernel process

Trusted host code can stop its own kernels without an operator. When the native broker’s authority RPC service fails, it calls kernel.emergency_stop("broker authority service failed") and stops serving. The latch applies to every kernel in that process and to every check inside a crossing, and it forbids containment because the authority service itself has failed.

A host latch is held in memory, and a restart clears it, along with the reason and the Unix time in seconds the kernel recorded. A fresh process either re-establishes the failed component or fails readiness. Host code that set the latch clears it in process with emergency_resume(), so no operator resume is involved. The status route reports it as host_latch, and its deny receipts name the latch in place of an epoch. is_emergency_stopped, emergency_stop_reason, and emergency_stopped_since read it.

Latch the stop from an embedding host and observe the next call

This integration function, compiled against the public kernel API, assumes an embedding that already owns its authenticated context and durable stores. It keeps the incident reason and leaves the latch engaged.

security-host/stop_review.rs · embedding hostrust
use chio_kernel::{
    ChioKernel, KernelError, SecurityInvocationContext, ToolCallRequest, ToolCallResponse,
};

/// This embedding already owns the authenticated request and durable state.
/// The stop is process-local; this function deliberately does not resume it.
pub async fn stop_and_observe(
    kernel: &ChioKernel,
    request: &ToolCallRequest,
    trusted_context: &SecurityInvocationContext,
) -> Result<ToolCallResponse, KernelError> {
    kernel.emergency_stop("fixer read PR #421 and touched /secrets")?;
    assert!(kernel.is_emergency_stopped());
    assert!(kernel.emergency_stopped_since().is_some());
    kernel
        .evaluate_tool_call_with_security_context(request, trusted_context)
        .await
}

The prepared example resumes the latch and keeps a revoked grant refused

The containment exercise builds the Execution application’s persistent kernel with an authenticated process context and its repository connector. It keeps one kernel alive through a useful read, a latched stop with its incident reason, a refused read, and a resume; it checks that the latch is engaged before the probe. It then latches again, revokes the grant, resumes once more, and finally leaves the latch engaged for operator review before it exits.

The read helper forms each request through the host’s ProcessRuntime, invokes under the known-outcome-only contract, and retains the original receipt. Use the prepared Execution example commands from the example directory, with fresh disposable state:

Terminalbash
containment demo .containment repository > containment.json
jq '{stop, calls: [.calls[] | {phase, decision}],
     resource_reads, capability_revoked, emergency_stopped}' containment.json
containment inspect .containment repository
Output
{
  "calls": [
    {
      "phase": "before-stop",
      "verdict": "allow"
    },
    {
      "phase": "during-stop",
      "verdict": "deny"
    },
    {
      "phase": "after-resume",
      "verdict": "allow"
    },
    {
      "phase": "after-withdrawal",
      "verdict": "deny"
    }
  ],
  "resource_reads": 2,
  "capability_revoked": true,
  "emergency_stopped": true
}
{
  "capability_revoked": true,
  "emergency_stopped": false,
  "resource_reads": 2
}

You should see two allowed reads and two refusals. The latched call adds no read, and neither does the call under the revoked grant, so the connector journal .containment/resource-reads.jsonl holds exactly two. Reopened in a new process, the latch is clear, the revocation is still recorded, and both reads remain. The exercise owns its own disposable kernel and resumes automatically to show the API; an incident runbook resumes only after operator review.

A stopped kernel signs each refusal with the epoch it observed

A stop refusal is temporary. It writes no deny tombstone, and whether the same request ID proceeds after resume depends on where the call stopped:

RefusalWhereAfter resume
deny, kernel_stopped, retryable_after_resume: true
Entry checks and the fused intent commit
Same request ID proceeds
deny, kernel_stopped, retryable_after_resume: false
Dispatch commit of a prepared operation
Returns the bound terminal result
OutputWithheld, retry: AfterResume
Output release after dispatch
Replay releases the output
OutputWithheld, retry: Never, effect_executed: true
Release of a non-durable side effect
None; the effect ran
Approval resolution refused, not consumed
Edge, for a parked operation
Resubmit it
KernelStopped with scope and epoch
Recovery and semantic stores
The workflow resumes
StopHeadMoved, StopIntentPending, StopEvidencePending
Resume or relax
Decide against the current head
EmergencyHandlerError::Unauthorized (HTTP 401)
Control route without the admin token
Authenticate

Every stop deny receipt carries chio_runtime.stop with the scope, decided_by (the early check or the crossing check), retryable_after_resume, and observed: the epoch that refused, the host latch, or a pending intent. An auditor joins each refused request to the transition behind it. A prepared operation refused at dispatch commit is compensated as PreDispatchNoEffect, so its request ID now names that terminal result.

Read more in Replay protection, which covers the replayed-result recheck at caller output release.

Resume needs separate authority and restores nothing that was withdrawn

chio emergency-resume carries the epoch it was decided against, so a delayed resume never clears a newer stop. It needs a principal outside every stopper of the incident, an offline-verified chio.stop-control-quorum.v1 artifact, or a single-operator cooldown of at least 300 seconds set in signed deployment configuration.

The resume record is signed inside its own transaction, within a two-second timeout; if signing fails, the scope stays stopped and the route returns ResumeRefused with EvidenceUnavailable. When authority time is unavailable, a quorum carrying a signed time attestation resumes by break-glass. A tenant resume never clears a kernel stop.

Resuming releases withheld output and runs the full validation pipeline again. A stop revokes nothing, so a revoked grant stays revoked and a capability that expired during the stop stays expired. Pair the stop with revocation when authority has to end for good.

Next: end the authority behind the incident