soma.cell

The killer feature: an agent whose termination is PROVEN by soma verify

The lifecycle has a single terminal set {done, failed} reachable from every state, a `* -> failed` catch-all, and the matching soma.toml below drives `soma verify` to prove deadlock-freedom and liveness (eventually done|failed). Check: soma check verified_termination.cell Verify: soma verify verified_termination.cell (uses the soma.toml beside it) Run: SOMA_LLM_MOCK=echo soma run verified_termination.cell --signal solve "x" Test: SOMA_LLM_MOCK=echo soma test verified_termination.cell

A Soma program from the agents part of the verified corpus: 52 lines, cells Solver, SolverTests. It passes soma check with soma 3.2.4.

How it is built

The cell Solver declares 1 signal in its face: solve(task: String) -> Map. Its state machine workflow starts in idle and allows 5 transitions: idle -> understanding, understanding -> solving, solving -> verifying, verifying -> done and * -> failed. done and failed are terminal. Handlers: solve (line 26).

The file also holds 1 test cell (SolverTests) with 1 assertion, run by soma test.

Generated from soma describe on this file.

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"]

verified_termination.cell

// The killer feature: an agent whose termination is PROVEN by soma verify.
// The lifecycle has a single terminal set {done, failed} reachable from every
// state, a `* -> failed` catch-all, and the matching soma.toml below drives
// `soma verify` to prove deadlock-freedom and liveness (eventually done|failed).
//
// Check:  soma check verified_termination.cell
// Verify: soma verify verified_termination.cell   (uses the soma.toml beside it)
// Run:    SOMA_LLM_MOCK=echo soma run verified_termination.cell --signal solve "x"
// Test:   SOMA_LLM_MOCK=echo soma test verified_termination.cell

cell agent Solver {
    face {
        signal solve(task: String) -> Map
        promise "eventually reaches done or failed"
    }

    state workflow {
        initial: idle
        idle        -> understanding
        understanding -> solving
        solving     -> verifying
        verifying   -> done
        *           -> failed
    }

    on solve(task: String) {
        set_budget(4000)
        let id = to_string(next_id())

        transition(id, "understanding")
        let u = try { think("Restate the task precisely: {task}") }
        if u.error != () { transition(id, "failed")  return map("status", "failed", "error", u.error) }

        transition(id, "solving")
        let s = try { think("Solve it: {u.value}") }
        if s.error != () { transition(id, "failed")  return map("status", "failed", "error", s.error) }

        transition(id, "verifying")
        let v = try { think("Double-check the solution: {s.value}") }
        if v.error != () { transition(id, "failed")  return map("status", "failed", "error", v.error) }

        transition(id, "done")
        map("status", "done", "task", task, "tokens", tokens_used())
    }
}

cell test SolverTests {
    rules {
        assert solve("compute 2+2").status == "done"
    }
}

What the toolchain says

Run on a fresh copy of the program at build time; the outputs are pasted unedited.

$ soma check verified_termination.cell
note: promise on 'Solver' is documentation (not machine-verifiable): "eventually reaches done or failed"
  --> verified_termination.cell:14:17
   |
14 |         promise "eventually reaches done or failed"
   |                 ^
✓ All checks passed (1 note above).
$ soma test verified_termination.cell
test SolverTests ...
  ✓ assert solve("compute 2+2").status == "done"

1 tests: 1 passed, 0 failed
$ soma verify verified_termination.cell
Verifying verified_termination.cell...

State machine 'workflow': 6 states, initial 'idle'
  States: [done, failed, idle, solving, understanding, verifying]

  ✓ 6 states, 9 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 done -> failed — so done is not final. If it should stay final, write `* -> failed except [done]`
  ✓ think-isolated: CTL safety properties hold regardless of LLM output (1 handlers, 7 literal transitions, 0 dynamic)
  ✓ termination: all 1 handlers structurally terminate
  ✓ refinement: handler `solve` ⟶ {understanding, failed [if u.error != ()], solving, failed [if s.error != ()], verifying, failed [if v.error != ()], done}
  · 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 6 sites (the cost line counts loop rounds) — no tools available

10 passed, 1 warnings, 0 failures

  Temporal properties for 'workflow':
  ✓ deadlock_free — no deadlocks in any reachable state
  ✓ eventually(state in [done, failed]) — all paths eventually satisfy the predicate
  ✓ after('understanding', state in [solving, failed]) — after 'understanding', all paths eventually satisfy the predicate
  ✓ after('solving', state in [verifying, failed]) — after 'solving', all paths eventually satisfy the predicate
  ✓ after('verifying', state in [done, failed]) — after 'verifying', all paths eventually satisfy the predicate
soma.toml: 4 user-defined properties loaded
Temporal: 5 passed, 0 failed
VERIFY OK

Builtins used

map, next_id, set_budget, think, to_string, tokens_used, transition

Source file: /corpus/agents/verified_termination.cell · examples/corpus/agents/verified_termination.cell