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 runtimeNot 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)
    }
}
FeatureCurrent behavior
faceDeclares signals and tools. soma check verifies that declared signals have compatible handlers.
memoryDeclares slots and properties such as persistent, ephemeral, consistent, local, ttl, and capacity bounds.
stateDeclares a finite state-machine graph. soma verify checks reachability, deadlocks, liveness, temporal properties, and transition refinement.
onDefines 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.

CommandWhat it checksKnown scope
soma checkFace contracts, property contradictions, scale coherence, structural promises, and memory-budget obligations.Return types and arbitrary runtime data invariants are not fully statically verified.
soma verifyState 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 lintAnti-patterns such as redundant to_json/from_json around storage and other common issues.Lint output is advisory.
soma fixSimple 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

AreaCurrent behavior
Interpretersoma run file.cell is the default execution path.
HTTPsoma 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.
SignalsThe runtime has a TCP signal bus and peer joining. This is also used by the current cluster replication path.
Native handlersHandlers annotated with [native] can be compiled to Rust cdylib files for supported numeric workloads. Unsupported constructs should stay in the interpreter.
Bytecode/JITsoma run --jit exists, but the CLI warns that it is deprecated and incomplete.
Record/replaysoma run --record writes a .somalog; soma replay re-executes recorded handler calls and reports divergences.
Deploysoma 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

CLI Summary

CommandPurpose
soma run file.cell [args]Run a handler in the interpreter.
soma serve file.cellStart 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.cellApply narrow automatic fixes.
soma describe file.cellPrint structured JSON describing cells, handlers, memory, face declarations, and state machines.
soma test file.cellRun cell test assertions.
soma deploy file.cell --target fly|cloudflare|awsGenerate deployment files and call the target provider CLI.