Soma
Soma is a small language for services whose correctness is a lifecycle and a few hard limits — approvals, quotas, escrow, agent workflows. One file holds the data, the state machine, the handlers, the tests and the HTTP routes; soma verify proves the lifecycle and the invariants it can, and says which ones it only checks at runtime. With SQLite or in-memory storage, handlers are atomic and serialized: a request that fails leaves no slot write and no transition behind — effects outside the program (an HTTP call, a file, an LLM call) are not rolled back.
Built for programs written by AI agents: every error names its fix, the whole language fits in one context window (llms.txt), and the toolchain answers with evidence (VERIFY OK, proven bounds, exact counts) rather than promises. This page is factual: implementation status separates what is implemented from what is partial.
curl -fsSL https://soma-lang.dev/install.sh | sh
Latest release: Soma 2.8.10 — corrected native/interpreter divergences, out-of-range List indices, a reserved Map key and quadratic storage and join operations. Sixteen new regression tests cover the fixes. Linux and macOS binaries include SHA-256 checksums. Read the release notes.
Reading this as an AI agent? Go to /agents, or fetch /llms-full.txt (the whole language, one request), /builtins.json (exact signatures) and /corpus/index.json (300+ programs passing check+test, verified where they have a state machine, filterable by feature). A project's AGENTS.md block is at /agent.md.
Installer note: the shell installer executes code fetched from the project repository. Review site/setup.sh before running it in an environment you care about.
One Rule, One Attempt To Break It
A quota of 100 units per user. The rule is written once, next to the data; the handler states its own business preconditions.
cell Quota {
memory {
used: Map<String, Int> [persistent]
invariant used >= 0 && used <= 100
}
on consume(user: String, n: Int) {
require n > 0 else BadAmount
let before = used.get(user) ?? 0
require before + n <= 100 else QuotaExceeded
used.set(user, before + n)
return 100 - (before + n)
}
}
What the verifier says, and what the program then does (real output of the 2.6 toolchain):
$ soma verify quota.cell
✓ invariant used >= 0 && used <= 100 — writer 'consume' proven by induction (writes before + n)
VERIFY OK
$ soma run quota.cell consume ana 60 → 40
$ soma run quota.cell consume ana 60 → error: require failed: QuotaExceeded
$ soma run quota.cell consume ana -5 → error: require failed: BadAmount
$ soma run quota.cell consume ana 40 → 0
Delete the QuotaExceeded line and the verdict changes: the bound is no longer proven, it is checked at runtime — and the write that would exceed it is refused, leaving the slot unchanged (soma verify --strict turns that ⚠ into a failure):
⚠ invariant used >= 0 && used <= 100 — runtime-checked (computed values): consume → used [used <= 100]
because `before + n` is only known to lie in [1, ∞) — narrow it: `require (before + n) <= 100 else …`
$ soma run q2.cell consume ana 60 → error: memory invariant violated on 'used': … rejected write of 120; the slot is unchanged
| Proven (every input) | Checked at runtime | Not covered |
|---|---|---|
| The bound, when every write is narrowed like above; the lifecycle of every declared state machine; declared token bounds. | Every invariant on every write — including one BETWEEN two slots of a cell (invariant (reserved ?? 0) <= (stock ?? 0), checked on writes to either); transitions and guards; typed parameters; token budgets. | Rules that span two CELLS (verify is per cell — it names each handler where one is at stake); business rules you did not write (the n > 0 line is yours: without it a negative amount would give back quota and still satisfy the invariant); external effects; what an LLM answers. Full table. |
Language Shape
A Soma program is organized as cells. The same syntax is used for a small command-line program, an HTTP handler, an agent, or a larger composed system.
cell Store {
face {
signal put(key: String, value: String)
signal get(key: String) -> String
}
memory {
data: Map<String, String> [persistent, consistent]
}
state lifecycle {
initial: empty
empty -> ready
ready -> ready
}
on put(key: String, value: String) {
data.set(key, value)
transition("store", "ready")
}
on get(key: String) {
return data.get(key)
}
}
| Feature | Current behavior |
|---|---|
face | Declares signals and tools. soma check verifies that declared signals have compatible handlers. |
memory | Declares slots and properties such as persistent, ephemeral, consistent, local, ttl, and capacity bounds. |
state | Declares a finite state-machine graph. soma verify checks reachability, deadlocks, liveness, temporal properties, and transition refinement. |
on | Defines signal handlers. soma run executes one handler; soma serve maps request handlers to HTTP. |
|> | Composes collection operations such as filter, map, sort_by, top, group_by, and join. |
Verification And Checking
Soma includes several static analyses. These are useful checks, not a proof of all runtime behavior.
| Command | What it checks | Known scope |
|---|---|---|
soma check | Face contracts, property contradictions, scale coherence, structural promises, and memory-budget obligations. | Return types and arbitrary runtime data invariants are not fully statically verified. |
soma verify | State graphs and temporal properties, transition refinement, supported memory invariants, handler termination and declared cost bounds. | Proofs are per cell and limited to the supported analysis. Other checks are reported as runtime-checked or unproved; --strict rejects warnings. Cluster behavior is not proven. |
soma lint | Anti-patterns such as redundant to_json/from_json around storage and other common issues. | Lint output is advisory. |
soma fix | Simple source rewrites, including missing handlers and some property issues. | It is intentionally narrow; review generated changes. |
[verify]
deadlock_free = true
eventually = ["settled", "cancelled"]
never = ["invalid"]
[verify.after.sent]
eventually = ["filled", "rejected"]
The repository also contains Coq/Rocq proof files under docs/rigor/coq. They mechanize selected results for the CTL depth bound, memory-budget cost lattice, isolation, abstraction, and runtime-fidelity models. They are evidence for those models, not a full formalization of the whole Rust implementation.
Implementation Status
Current release: Soma 2.8.10. Release validation: 681 Rust tests, 118 CLI checks, 321 corpus programs and 1,280 independent numeric comparisons passed.
implemented Language core
Typed cells and handlers, persistent memory, runtime invariants, state machines, pattern matching, collections, pipes, tests and a CLI.
Recent audits corrected numeric boundaries, input and storage validation, argument evaluation, collection updates, call resolution, typed constructors, interpolation analysis, agent-memory isolation, SQLite transaction failures, storage providers, native/interpreter parity and storage complexity. Corrections and regression tests.
experimental Eventual cluster replication
Typed Map entries replicate after local commit. Reconnect and resynchronization carry data and deletion markers; per-key logical versions resolve conflicts deterministically. Process tests cover concurrent writes, partitions, seed loss and restart.
Reads are local and may be stale; concurrent updates can overwrite each other. Consensus, physical sharding and exactly-once delivery are not implemented. strong and causal declarations are rejected. Cluster setup and limits.
scoped Static verification
soma verify proves properties of each cell’s state graph, supported memory invariants, termination and cost bounds. Unproved checks are reported; --strict rejects warnings.
Proofs cover the inputs represented by the analysis. Graph liveness does not ensure handler invocation. Cluster behavior, arbitrary handler semantics, cross-cell rules and LLM answers remain outside these proofs. Exact guarantees and limits.
Agents
cell agent enables agent-oriented declarations. Agents can use think(), set_budget(), tool declarations in face, and regular state machines.
cell agent Researcher {
face {
signal research(topic: String) -> Map
tool search(q: String) -> String
}
state workflow {
initial: idle
idle -> researching
researching -> done
* -> failed
}
on search(q: String) {
return "result: {q}"
}
on research(topic: String) {
set_budget(1000)
transition("task", "researching")
let data = think("Research: {topic}")
transition("task", "done")
return map("summary", data)
}
}
The verifier can check the lifecycle graph and literal transition targets. set_budget tracks token use for real LLM calls. None of this proves the LLM response is correct, safe, or useful.
Hordes
A [task] handler runs as steps: each think() commits the writes before it and waits for the model outside the handler lock, so many requests overlap their model calls. horde() runs such a handler once per input with a bounded pool:
on audit(docs: List) {
return horde(Reviewer.review, docs, map("concurrency", 500,
"budget_tokens", 2000000, "on_result", "_store", "on_done", "_summarize"))
}
on _store(v: Map) { verdicts.set(v.id, v) } // verdicts: [persistent, immutable]
The queue is persisted and resumes after a restart; each result is recorded, and on_result called, with the task's last step — once, also across a kill -9. Every think() of the horde (nested hordes included) reserves its worst case against budget_tokens first, so the spend stays under the ceiling while the provider honors max_tokens. soma verify prints each horde's cost bound. Rounds (snapshot, apply in input order, seed, per-agent instance memory) make a population simulation reproducible. Measured with a mocked 2 s model: 10 000 tasks at concurrency 500 in 41 s; 10 000 agents × 20 rounds in 46 s, identical on two runs.
Runtime And Backends
| Area | Current behavior |
|---|---|
| Interpreter | soma run file.cell is the default execution path. |
| HTTP | soma serve file.cell starts a threaded HTTP server. A handler named request can return HTML, JSON, redirects, and status codes. |
| Storage | [persistent] currently resolves to SQLite-backed storage. [ephemeral] resolves to memory-backed storage. |
| Signals | The runtime has a TCP signal bus and peer joining. This is also used by the current cluster replication path. |
| Native handlers | Handlers annotated with [native] can be compiled to Rust cdylib files for supported numeric workloads. Unsupported constructs should stay in the interpreter. |
| Bytecode/JIT | soma run --jit exists, but the CLI warns that it is deprecated and incomplete. |
| Record/replay | soma run --record writes a .somalog; soma replay re-executes recorded handler calls and reports divergences. |
| Deploy | soma deploy generates deployment scaffolding for targets such as Fly, Cloudflare, and AWS, then shells out to provider CLIs. Cloud credentials and CLIs are still required. |
Native Numeric Handlers
[native] is intended for narrow numeric hot paths. The generated Rust path uses checked fast integer code and a BigInt fallback for overflow-sensitive integer results.
cell Math {
on sum(n: Int) [native] {
let total = 0
for i in range(0, n) {
total = total + i
}
return total
}
}
Performance depends on workload, machine, cache state, and whether the handler fits the native subset. Benchmark claims should be reproduced with the scripts under bench/ before being used for decisions.
Reproduce Basic Checks
After installing (no repository checkout needed), these commands exercise the core paths:
soma --version
soma init demo && cd demo
soma check app.cell && soma verify app.cell && soma test app.cell
soma run app.cell add 5
soma example invariant state_machine # verified programs, by feature
soma example services/payments_approval > pay.cell && soma verify pay.cell
soma docs agent | head -40 # the language, offline
soma serve app.cell -p 8080 # then: curl -X POST localhost:8080/counter/5
The Coq proof files live in the repository (docs/rigor/coq, make check).
Important Limits
- Soma is experimental: one author, used for the demos in the repository and the author's own trading automata, not yet used in a system that other people depend on (/status has the full maturity table). Expect rough edges in syntax, tooling, installer behavior, and deployment integrations.
- What is proven, what is only checked at runtime, and what is out of scope: docs/guarantees.md. What
soma serveexposes and how errors map to statuses: docs/serving.md. What happens when it goes wrong in production: docs/operations.md. - State-machine verification is over the declared finite graph. It does not exhaustively model runtime data values, external systems, or LLM outputs.
- Strong consistency in
scaleis currently a checked declaration, not a Raft/Paxos-backed runtime guarantee. - The current cluster replication path is signal-bus broadcast and should be treated as eventual replication.
- Native codegen supports a subset of handlers. The general interpreter remains the reference path.
- Memory-budget proofs depend on the static cost model and declared bounds such as capacities and loop bounds. Calls to unbounded builtins produce advisory output instead of a proof.
- Agent token budgeting bounds token use; it does not prove semantic correctness or safety of model output.
- A horde's
budget_tokensceiling assumes the provider honorsmax_tokensand counts settled calls: calls in flight at a crash are sent again after the restart. A[task]handler is atomic per step, not as a whole: steps before its lastthink()may run again after a crash. - Cloud deployment commands generate scaffolding and invoke external provider CLIs. They do not remove the need for provider configuration, credentials, or review.
CLI Summary
| Command | Purpose |
|---|---|
soma run file.cell [args] | Run a handler in the interpreter. |
soma serve file.cell | Start HTTP and signal-bus services. |
soma check file.cell [--json] | Run contract, property, scale, and budget checks. |
soma verify file.cell [--json] | Run state-machine, temporal, distribution, and refinement checks. |
soma lint file.cell [--json] | Report style and correctness warnings. |
soma fix file.cell | Apply narrow automatic fixes. |
soma describe file.cell | Print structured JSON describing cells, handlers, memory, face declarations, and state machines. |
soma test file.cell | Run cell test assertions. |
soma deploy file.cell --target fly|cloudflare|aws | Generate deployment files and call the target provider CLI. |