Operate an Agentic OS
Kill processes, cancel work, expire authority, and recover only the outcomes the effect owner can prove.
On this page
An agent operating system tells you what happened when a worker disappears. This control application runs failure exercises against the fleet and network services from the other chapters; it stops worker and host processes at named points, reads the retained accounting and adapter state, and records whether the work completed, was refused, or stays unresolved.
Run an exerciseA host crash leaves partial work unresolved and unpublished
Recover an operating system without guessing its effects
Stop real workers and hosts, inspect their retained authority and effects, and reconcile before deciding what can safely run again.
Choose a crash point and run the exercise. A host crash during a report leaves partial work and no published report. A lost acknowledgement happens after publication and is reconciled to the original operation. Cancellation interrupts active work, and an expired grant stays expired after restart. Compare the retained effect, accounting, and receipt before you decide what to do next.
Only the adapter’s operation record can resolve an outcome
Claims and grants live in the host’s retained state, so a restart keeps every refusal in place. The adapter’s own record of the effect decides completion.
Restart keeps refusals in place
The host retains the grant and each job's claim across process replacement. After a restart, a stopped worker's stale claim cannot publish, and a grant that expired stays expired. Recover an operation.
Each failure happens at a named point
The control application dispatches each named exercise to the fleet or personal-network service, and the failure description is part of the input and the retained run. A process termination, transport failure, or expired capability happens at its own boundary in the running service.
pub async fn exercise(input: Value, run: Run, executable: PathBuf) -> Result<Value> {
let exercise = input["exercise"]
.as_str()
.context("Select a recovery exercise")?;
if ["lost-acknowledgement", "budget-exhaustion"].contains(&exercise) {
let mut request = Fleet.sample();
request["document"] = input["document"].clone();
if exercise == "budget-exhaustion" {
request["allowance"] = json!(30);
}
let child = Run::create(&run.directory()?.join("fleet-runs"), Fleet.name(), &request)?;
let result = chio_worker_fleet::run_fleet(request, child.clone(), executable).await;
child.finish(&result)?;
let output = result?;
run.emit(
"operations.fleet",
"operator",
"Actual fleet restart followed by effect reconciliation",
child.snapshot()?,
)?;
return Ok(json!({"exercise":exercise,"fleet":output}));
}
if exercise == "peer-removal" {
let app = chio_personal_network::Personal;
let mut request = app.sample();
request["document"] = input["document"].clone();
let child = Run::create(&run.directory()?.join("network-runs"), app.name(), &request)?;
let result = chio_personal_network::run_network(request, child.clone(), executable).await;
child.finish(&result)?;
let output = result?;
run.emit(
"operations.network",
"operator",
"Live peer removal and restoration across node processes",
child.snapshot()?,
)?;
return Ok(json!({"exercise":exercise,"network":output}));
}
anyhow::ensure!(
[
"worker-crash-before-dispatch",
"host-crash-during-report",
"cancel-running-report",
"expired-authority"
]
.contains(&exercise),
"Choose a documented recovery exercise"
);In the verified run, event 03 stops the worker and the host at crash point host-crash-during-report.
The adapter’s completed result survives replacement
The fleet’s report handler owns an operation record and its published report, and the control application reads that state across process replacement. A saved task record shows intent; the adapter’s own record of the effect is what makes reconciliation possible.
async fn reconcile(
State(service): State<Arc<Service>>,
headers: HeaderMap,
Path(id): Path<String>,
) -> ApiResult {
admin(&headers, &service)?;
let mut stored = service.jobs.lock().await;
let mut jobs = stored.clone();
let job = jobs
.iter_mut()
.find(|job| job.operation_id.to_string() == id)
.ok_or_else(|| error("Unknown operation"))?;
if service.cancellations.lock().await.contains_key(&id) {
return Err(error(
"Worker is still executing; wait for its terminal state",
));
}
let file = service
.config
.directory
.join("reports")
.join(format!("{id}.json"));
let resolution = if file.exists() {
let artifact: Value =
serde_json::from_slice(&std::fs::read(file).map_err(error)?).map_err(error)?;
let expected =
chio_core::sha256_hex(&chio_core::canonical_json_bytes(&job.input).map_err(error)?);
if artifact["input_sha256"] != expected || artifact["operation_id"] != id {
return Err(error(
"Artifact does not match this operation; leave it unresolved",
));
}
let outcome = job.reconcile_published().map_err(error)?;
json!({"state":"completed","reused_existing_effect":true,"artifact":artifact,"receipt_id":outcome["receipt_id"]})
} else {
json!({
"state":job.label(),
"reused_existing_effect":false,
"recovery":"No published artifact proves completion. Inspect retained admission and partial output; this adapter does not blindly retry uncertain work."
})
};
service.persist(&jobs).map_err(error)?;
*stored = jobs;
service
.run
.emit(
"fleet.reconciled",
"operator",
"Reconciled the report's actual state",
json!({"operation_id":id,"resolution":resolution}),
)
.map_err(error)?;
Ok(Json(resolution))
}Cancellation is checked at the effect owner
Cancellation is checked against the retained job and the active operation. The host aborts the running report and keeps the job’s cancellation decision. A partial draft stays distinct from a published report, and the control application checks that cancellation left no accepted report behind.
async fn cancel(
State(service): State<Arc<Service>>,
headers: HeaderMap,
Path(id): Path<String>,
) -> ApiResult {
admin(&headers, &service)?;
if let Some(flag) = service.cancellations.lock().await.get(&id) {
flag.store(true, Ordering::SeqCst);
return Ok(Json(json!({"cancellation_requested":true})));
}
let mut stored = service.jobs.lock().await;
let mut jobs = stored.clone();
let job = jobs
.iter_mut()
.find(|job| job.operation_id.to_string() == id)
.ok_or_else(|| error("Unknown operation"))?;
if matches!(job.state, JobState::Queued | JobState::Claimed { .. }) {
job.cancel_before_dispatch().map_err(error)?;
service.persist(&jobs).map_err(error)?;
*stored = jobs;
return Ok(Json(
json!({"state":"cancelled_before_dispatch","receipt_id":null}),
));
}
Err(error(
"No active worker; inspect the operation before attempting recovery",
))
}Evidence exports without repeating work
The capture path validates the run identity, event sequence, and completed-result digests before it returns the retained record, and it never re-enters the application. The docs reconnect operation uses the same native capture interface, so refreshing a page starts no second mission.
pub fn capture(root: &Path, id: &str) -> Result<Value> {
// IDs are server-generated UUIDs. Validate before constructing paths.
let id = uuid::Uuid::parse_str(id)?.to_string();
let directory = root.join(id);
let mut manifest: Value =
serde_json::from_slice(&read_bounded(&directory.join("run.json"))?)?;
let path = directory.join("events.ndjson");
let mut events = Vec::new();
if path.exists() {
let bytes = read_bounded(&path)?;
for line in std::str::from_utf8(&bytes)?.lines() {
anyhow::ensure!(
events.len() < 4096,
"Retained event count exceeds its bound"
);
events.push(serde_json::from_str::<Event>(line)?);
}
}
anyhow::ensure!(
events
.iter()
.enumerate()
.all(|(index, event)| event.sequence == index + 1),
"Retained event sequence is incomplete"
);
if manifest["status"] != "running" {
anyhow::ensure!(
manifest["output_sha256"]
== chio_core::sha256_hex(&chio_core::canonical_json_bytes(
&manifest["output"]
)?),
"Retained output digest mismatch"
);
anyhow::ensure!(
manifest["events_sha256"]
== chio_core::sha256_hex(&chio_core::canonical_json_bytes(&events)?),
"Retained event digest mismatch"
);
}
manifest["events"] = serde_json::to_value(events)?;
Ok(manifest)
}
}Event 04 refuses the stale worker request and leaves the operation unresolved, with 20 credits reserved and none spent.
A stale claim, a crash mid-report, and an expired grant stay refused
The sample input crashes the host during a report. The retained run shows what recovery could and could not prove:
./run --run input.json01 operations.claimed report-worker
Independent worker retained its job claim
02 operations.effect_started report-host
The governed report has written partial output
03 operations.processes_stoppedoperator
Worker and authoritative host processes have stopped; 0 of 40 local demo credits
spent
04 operations.reconciled operator
state unresolved; stale request: Claim is no longer executable; inspect the
retained operation- Stop a worker after it claims work: its stale claim cannot publish after recovery.
- Kill the host during report construction: no partial draft becomes a completed report.
- Lose a committed response: recover the original operation and receipt instead of spending again.
- Restart after authority expiry: the old grant stays expired.
- Remove a peer: new and held-connection admission both observe the membership change.
The downloadable tests assert each effect or its absence: run cargo test --locked -p chio-operations from the extracted project.
Run the control application on your machine
Keep the run directory to connect the fault point, the retained operation, the effect state, and the reconciliation result.
cd chio-operations
./runThe archive holds the application, the shared host code, tests, sample input, pinned dependencies, and a README. The launcher builds and runs the Rust source included in this archive. Install Rust 1.94.1 and the README’s native prerequisites; filesystem isolation and operator workloads require the listed Linux host profile. Cargo resolves immutable public dependencies, so a sibling checkout is unnecessary.
For model workers, set OPENROUTER_API_KEY on the local host and select model mode; credentials stay on that host and never enter the docs form. The qualified model is openai/gpt-4.1-mini. Analysis, retrieval, and deterministic worker modes run without a model key. Ctrl-C stops the local web process and its active child work and keeps the run directory.
Download identity and source revision
Download complete project · View public source
af5cb2898020bacc8ad56b8a695ad3fe63652f6de33f8bd658ab4c62acaf98ccAdd a fault exercise for every new adapter
Add one fault exercise for each new effectful adapter. Name the crash point, the authoritative effect record, and the evidence that makes recovery safe. Keep an outcome unresolved when the adapter cannot prove what happened, and derive any retry policy from that contract, never from whether a client happened to receive a response.