Human-in-the-loop approval gate guarding an irreversible action
Demonstrates: approve() as a lifecycle gate, a state machine where the "executing" path is only reachable past the gate, and a rejected path that lands in a distinct terminal. approve() never answers on its own: a test scripts it with `mock approve true|false`; `soma run` asks the person at the terminal; under `soma serve` (nobody attached) it raises kind "approval_required" unless SOMA_APPROVE=always|never states an explicit policy. Run: soma run approval_gate.cell deploy "v2.1.0" (asks y/N) SOMA_APPROVE=never soma run approval_gate.cell deploy "v2.1.0" Test: soma test approval_gate.cell
A Soma program from the agents part of the verified corpus: 70 lines, cells Deployer, DeployTests. It passes soma check with soma 3.2.4.
How it is built
The cell Deployer declares 1 signal in its face: deploy(version: String) -> Map; keeps 1 memory slot: history (List<String>, persistent). Its state machine pipeline starts in idle and allows 6 transitions: idle -> planning, planning -> awaiting, awaiting -> executing, awaiting -> rejected, executing -> done and * -> failed. done, failed and rejected are terminal. Handlers: deploy (line 34).
The file also holds 1 test cell (DeployTests) with 4 assertions, run by soma test.
The source
soma.toml
[package]
name = "agents-corpus"
version = "0.1.0"
description = "Verified AI-agent example cells (training corpus)"
# Agent LLM config. `mock = "echo"` keeps every example in this directory
# hermetic: `soma test` / `soma run` need no network and no API key, and
# results are deterministic. For a live run, delete the `mock` line (or
# override with SOMA_LLM_MOCK) and provide the key via env.
[agent]
provider = "anthropic"
model = "claude-opus-4-6"
key = "${ANTHROPIC_API_KEY}"
mock = "echo"
# Termination proof for verified_termination.cell's `workflow` machine:
# every path eventually reaches a terminal, no deadlocks.
# `cells` scopes these properties to Solver — the other agents in this
# directory have their own state names and must not inherit them.
[verify]
cells = ["Solver"]
deadlock_free = true
eventually = ["done", "failed"]
[verify.after.understanding]
eventually = ["solving", "failed"]
[verify.after.solving]
eventually = ["verifying", "failed"]
[verify.after.verifying]
eventually = ["done", "failed"]approval_gate.cell
// Human-in-the-loop approval gate guarding an irreversible action.
// Demonstrates: approve() as a lifecycle gate, a state machine where the
// "executing" path is only reachable past the gate, and a rejected path
// that lands in a distinct terminal.
//
// approve() never answers on its own: a test scripts it with
// `mock approve true|false`; `soma run` asks the person at the terminal;
// under `soma serve` (nobody attached) it raises kind "approval_required"
// unless SOMA_APPROVE=always|never states an explicit policy.
//
// Run: soma run approval_gate.cell deploy "v2.1.0" (asks y/N)
// SOMA_APPROVE=never soma run approval_gate.cell deploy "v2.1.0"
// Test: soma test approval_gate.cell
cell agent Deployer {
face {
signal deploy(version: String) -> Map
}
memory {
history: List<String> [persistent]
}
state pipeline {
initial: idle
idle -> planning
planning -> awaiting
awaiting -> executing
awaiting -> rejected
executing -> done
* -> failed except [done, rejected]
}
on deploy(version: String) {
set_budget(1500)
let id = to_string(next_id())
transition(id, "planning")
transition(id, "awaiting")
let approved = approve("Deploy {version} to production?")
if !approved {
transition(id, "rejected")
return map("status", "rejected", "version", version)
}
transition(id, "executing")
history.push(version)
transition(id, "done")
map("status", "done", "version", version, "count", len(history))
}
}
cell test DeployTests {
rules {
mock approve true
assert deploy("v1").status == "done"
mock approve false
assert deploy("v2").status == "rejected"
// nobody answers: the gate fails closed and the handler rolls back
assert_fails deploy("v3") matching "approval_required"
// one answer per call: only v1 and v4 were ever deployed
mock approve true
let d4 = deploy("v4")
assert d4.count == 2
}
}What the toolchain says
$ soma check approval_gate.cell
✓ All checks passed.$ soma test approval_gate.cell
test DeployTests ...
✓ assert deploy("v1").status == "done"
✓ assert deploy("v2").status == "rejected"
✓ assert_fails deploy("v3") matching "approval_required" — raised approval_required: approve("Deploy v3 to production?") has no one to answer it — script it in the test: `mock approve true` / `mock approve false` before the call
✓ assert d4.count == 2
4 tests: 4 passed, 0 failed$ soma verify approval_gate.cell
Verifying approval_gate.cell...
note: soma.toml [verify] cells names 'Solver', which this file does not define — its properties are not checked here
State machine 'pipeline': 7 states, initial 'idle'
States: [awaiting, done, executing, failed, idle, planning, rejected]
✓ 7 states, 9 transitions
✓ all states reachable from 'idle'
✓ terminal states: [rejected, done, failed]
✓ no deadlocks
✓ liveness: every state can eventually reach a terminal state
✓ wildcard transitions: * -> [failed]
✓ think-isolated: CTL safety properties hold regardless of LLM output (1 handlers, 5 literal transitions, 0 dynamic)
✓ termination: all 1 handlers structurally terminate
⚠ refinement: declared transition `* → failed` is never reached by any handler — spec may be aspirational or stale
✓ refinement: handler `deploy` ⟶ {planning, planning → awaiting, rejected [if !approved], executing, executing → done}
· transition sources: `deploy` calls transition(id, target) without declaring the source — the runtime refuses a move the graph forbids, but the proof does not see where the handler starts; `transition(id, from, to)` names the edge (checked by `soma check`, refused at run time from any other state)
9 passed, 1 warnings, 0 failures
Temporal properties for 'pipeline':
✓ deadlock_free — no deadlocks in any reachable state
soma.toml: 4 user-defined properties loaded — NONE applies to this file ([verify] cells = ["Solver"] names no cell of it)
Temporal: 1 passed, 0 failed
VERIFY OKBuiltins used
approve, len, map, next_id, set_budget, to_string, transition