soma.cell

Planning agent: decompose a goal into ordered subtasks, then execute

Demonstrates: a plan/execute lifecycle, building a task list with range() and push(), and reporting progress. The decomposition is deterministic so the corpus can verify the plan shape without an LLM. Run: SOMA_LLM_MOCK=echo soma run planner.cell --signal plan "ship feature" 3 Test: SOMA_LLM_MOCK=echo soma test planner.cell

A Soma program from the agents part of the verified corpus: 64 lines, cells Planner, PlannerTests. It passes soma check with soma 3.2.4.

How it is built

The cell Planner declares 2 signals in its face: plan(goal: String, steps: Int) -> Map and execute(goal: String, steps: Int) -> Map; keeps 1 memory slot: runs (Map<String, Map>, persistent). Its state machine flow starts in idle and allows 4 transitions: idle -> planning, planning -> executing, executing -> done and * -> failed. done and failed are terminal. Handlers: plan (line 28) and execute (line 37).

The file also holds 1 test cell (PlannerTests) with 5 assertions, 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"]

planner.cell

// Planning agent: decompose a goal into ordered subtasks, then execute.
// Demonstrates: a plan/execute lifecycle, building a task list with range()
// and push(), and reporting progress. The decomposition is deterministic so
// the corpus can verify the plan shape without an LLM.
//
// Run:  SOMA_LLM_MOCK=echo soma run planner.cell --signal plan "ship feature" 3
// Test: SOMA_LLM_MOCK=echo soma test planner.cell

cell agent Planner {
    face {
        signal plan(goal: String, steps: Int) -> Map
        signal execute(goal: String, steps: Int) -> Map
    }

    memory {
        runs: Map<String, Map> [persistent]
    }

    state flow {
        initial: idle
        idle      -> planning
        planning  -> executing
        executing -> done
        *         -> failed
    }

    // Pure decomposition — returns a list of subtask records.
    on plan(goal: String, steps: Int) {
        require steps > 0 else need_at_least_one_step
        let tasks = list()
        for i in range(1, steps + 1) {
            tasks = push(tasks, map("n", i, "desc", "{goal} — step {i}"))
        }
        map("goal", goal, "tasks", tasks, "count", len(tasks))
    }

    on execute(goal: String, steps: Int) {
        set_budget(2000)
        let id = to_string(next_id())
        transition(id, "planning")
        let p = plan(goal, steps)

        transition(id, "executing")
        let done = 0
        for t in p.tasks {
            done = done + 1
        }

        transition(id, "done")
        runs.set(id, map("goal", goal, "completed", done))
        map("status", "done", "goal", goal, "completed", done)
    }
}

cell test PlannerTests {
    rules {
        assert plan("ship", 3).count == 3
        assert plan("ship", 1).tasks[0].desc == "ship — step 1"
        assert execute("ship", 4).completed == 4
        assert execute("ship", 4).status == "done"
        assert_fails plan("ship", 0)
    }
}

What the toolchain says

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

$ soma check planner.cell
✓ All checks passed.
$ soma test planner.cell
test PlannerTests ...
  ✓ assert plan("ship", 3).count == 3
  ✓ assert plan("ship", 1).tasks[0].desc == "ship — step 1"
  ✓ assert execute("ship", 4).completed == 4
  ✓ assert execute("ship", 4).status == "done"
  ✓ assert_fails plan("ship", 0) — raised require failed: need_at_least_one_step: constraint violated

5 tests: 5 passed, 0 failed
$ soma verify planner.cell
Verifying planner.cell...
note: soma.toml [verify] cells names 'Solver', which this file does not define — its properties are not checked here

State machine 'flow': 5 states, initial 'idle'
  States: [done, executing, failed, idle, planning]

  ✓ 5 states, 7 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, 3 literal transitions, 0 dynamic)
  ✓ termination: all 2 handlers structurally terminate
  ⚠ refinement: declared transition `* → failed` is never reached by any handler — spec may be aspirational or stale
  ✓ refinement: handler `execute` ⟶ {planning, executing, done}
  · transition sources: `execute` 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, 2 warnings, 0 failures

  Temporal properties for 'flow':
  ✓ 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 OK

Builtins used

len, list, map, next_id, push, range, set_budget, to_string, transition

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