A ReAct-style agent whose tools are real, deterministic computations
Demonstrates: face `tool` declarations that the LLM can call, plus the tool handlers being directly unit-tested (they are ordinary handlers). The think() call dispatches tools automatically; the tools themselves need no network, so the corpus can verify them. Run: SOMA_LLM_MOCK=echo soma run react_calculator.cell --signal solve "2 and 3" Test: SOMA_LLM_MOCK=echo soma test react_calculator.cell
A Soma program from the agents part of the verified corpus: 63 lines, cells Calc, CalcTests. It passes soma check with soma 3.2.4.
How it is built
The cell Calc declares 1 signal in its face: solve(question: String) -> Map. Its state machine task starts in idle and allows 3 transitions: idle -> reasoning, reasoning -> answered and * -> failed. answered and failed are terminal. Handlers: add (line 25), mul (line 26), primes_below (line 28) and solve (line 40).
The file also holds 1 test cell (CalcTests) with 5 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"]react_calculator.cell
// A ReAct-style agent whose tools are real, deterministic computations.
// Demonstrates: face `tool` declarations that the LLM can call, plus the
// tool handlers being directly unit-tested (they are ordinary handlers).
// The think() call dispatches tools automatically; the tools themselves
// need no network, so the corpus can verify them.
//
// Run: SOMA_LLM_MOCK=echo soma run react_calculator.cell --signal solve "2 and 3"
// Test: SOMA_LLM_MOCK=echo soma test react_calculator.cell
cell agent Calc {
face {
signal solve(question: String) -> Map
tool add(a: Int, b: Int) -> Int "Add two integers"
tool mul(a: Int, b: Int) -> Int "Multiply two integers"
tool primes_below(n: Int) -> String "List primes below n, comma-separated"
}
state task {
initial: idle
idle -> reasoning
reasoning -> answered
* -> failed
}
on add(a: Int, b: Int) { a + b }
on mul(a: Int, b: Int) { a * b }
on primes_below(n: Int) {
let out = list()
for k in range(2, n) {
let is_prime = true
for d in range(2, k) {
if floor(k / d) * d == k { is_prime = false break }
}
if is_prime { out = push(out, k) }
}
out |> map(p => to_string(p)) |> join(",")
}
on solve(question: String) {
set_budget(3000)
let id = to_string(next_id())
transition(id, "reasoning")
let r = try { think("Use the tools to answer: {question}") }
if r.error != () {
transition(id, "failed")
return map("status", "failed", "error", r.error)
}
transition(id, "answered")
map("status", "answered", "answer", r.value)
}
}
cell test CalcTests {
rules {
assert add(2, 3) == 5
assert mul(6, 7) == 42
assert primes_below(10) == "2,3,5,7"
assert primes_below(3) == "2"
assert solve("anything").status == "answered"
}
}What the toolchain says
$ soma check react_calculator.cell
✓ All checks passed.$ soma test react_calculator.cell
test CalcTests ...
✓ assert add(2, 3) == 5
✓ assert mul(6, 7) == 42
✓ assert primes_below(10) == "2,3,5,7"
✓ assert primes_below(3) == "2"
✓ assert solve("anything").status == "answered"
5 tests: 5 passed, 0 failed$ soma verify react_calculator.cell
Verifying react_calculator.cell...
note: soma.toml [verify] cells names 'Solver', which this file does not define — its properties are not checked here
State machine 'task': 4 states, initial 'idle'
States: [answered, failed, idle, reasoning]
✓ 4 states, 5 transitions
✓ all states reachable from 'idle'
✓ terminal states: [failed]
✓ no deadlocks
✓ liveness: every state can eventually reach a terminal state
✓ wildcard transitions: * -> [failed]
⚠ `* -> failed` also adds answered -> failed — so answered is not final. If it should stay final, write `* -> failed except [answered]`
✓ think-isolated: CTL safety properties hold regardless of LLM output (1 handlers, 3 literal transitions, 0 dynamic)
✓ termination: all 4 handlers structurally terminate
✓ refinement: handler `solve` ⟶ {reasoning, failed [if r.error != ()], answered}
· transition sources: `solve` 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)
✓ effects: handler `solve` calls think() at 2 sites (the cost line counts loop rounds) — can dispatch [add, mul, primes_below]
10 passed, 1 warnings, 0 failures
Temporal properties for 'task':
✓ 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
floor, join, list, map, next_id, push, range, set_budget, think, to_string, transition