A hyper-strict, verification-mandatory programming language for AI agents, built on Rust.
Thermite is what you get when you add energy to rust. Iron oxide plus aluminum: inert powder until ignited, then it burns at 2,500°C and cuts through steel. The name is the thesis — take Rust's substrate, add the energy budget agents bring (compute, patience, token spend), and produce something hot enough to weld trust into software.
Version 0.1 — Design Document Status: Draft for discussion
Every verification-heavy language in history died of human ergonomics. Annotating contracts on a ten-line function feels, to a human, like filing paperwork to open a window. Humans pay for verification in attention — their scarcest resource — so verification has remained a niche luxury reserved for kernels, hypervisors, and avionics.
AI agents invert the economics:
- The annotation cost is paid in tokens and compute, which are cheap, parallelizable, and falling in price every year.
- The cost of misplaced trust in autonomously generated code is rising, because agents write more code with less supervision.
- Agents have effectively unlimited patience for proof-obligation loops, and their failure mode (locally plausible, globally wrong) is the failure mode machine verification catches.
Thermite is the arbitrage: burn the cheap resource (tokens) to buy the expensive one (trust). It is unpleasant for humans to write, by design; humans are not the intended author.
A Thermite artifact ships with a certificate: the implementation satisfies these contracts, the contracts are machine-certified non-vacuous, and they kill X% of generated mutants. The residual question — "are these the contracts you wanted?" — is answered by reading a short declarative spec layer rather than the code. Trust is relocated twice (code → spec → spec-intent alignment); each relocation shrinks the residue and makes it more legible. A skeptical third party can audit the result in minutes without trusting the agent.
- Verification is the floor, not the ceiling. Every function carries a contract. Every contract is checked. Unverified code requires greppable ceremony; verified code requires none.
- The whole language fits in a skill. The complete surface syntax and semantics must be teachable to an LLM in ≤ 6,000 tokens. This is a hard budget, enforced in CI against the canonical
THERMITE.skill.md. Any feature that doesn't fit doesn't ship. - One way to do everything. No expressiveness for its own sake. No style decisions. Exactly one canonical formatting (the formatter has zero configuration options). Predictability over elegance.
- Feedback is always crisp. Every toolchain response is structured, machine-readable, and actionable. A timeout is never the final answer; the gate degrades, it does not block.
- Locality. Every block is independently parseable, independently checkable, and addressable by name. An edit's blast radius is its block. A proof must not break because of an unrelated edit.
- The contract is the interface. Once a function verifies, no caller (human or agent) ever needs to read its body again. Specs double as the compressed representation of the codebase — a context-window subsidy that grows with project size.
┌────────────────────────────────────────────────┐
│ Thermite surface language │ small, regular, contract-mandatory
├────────────────────────────────────────────────┤
│ Forge (toolchain + goal-state REPL) │ the agent's actual interface
├────────────────────────────────────────────────┤
│ Verification ladder │
│ L4 checked cage (relax / BV / EPR) │
│ L3 SMT proof (Verus/Z3) │
│ L2 bounded check (Kani/CBMC) │
│ L1 runtime contracts (active all profiles) │
│ L0 unverified (#[slag] only) │
├────────────────────────────────────────────────┤
│ Rust (MIR-level lowering) │ semantics, borrow checker, ecosystem
├────────────────────────────────────────────────┤
│ LLVM │ codegen
└────────────────────────────────────────────────┘
Thermite lowers to Rust MIR, inheriting the borrow checker, the optimizer, and crates.io interop (through a contract-wrapped FFI boundary — see §9). Verification reuses the Verus and Kani toolchains rather than reinventing solvers.
Realization note (#21 decision). As shipped (v0.1–v0.5), this is realized as source-level transpilation to Verus-annotated Rust (
thermite-lower);forge buildshells torustc, which performs the MIR lowering internally. The thesis — inherit the borrow checker, the optimizer, and thecrates.ioecosystem — is therefore satisfied transitively throughrustc. Direct MIR emission was retired as an unneeded rewrite; the diagram below keeps its intent (Rust/MIR is the substrate), only the emission boundary moved up to Rust source.
fn binary_search(haystack: &[u32], needle: u32) -> Option<usize>
req sorted(haystack)
ens match result {
Some(i) => i < haystack.len() && haystack[i] == needle,
None => forall_in(haystack, |x| x != needle),
}
fx pure
{
let mut lo: usize = 0;
let mut hi: usize = haystack.len();
loop
inv lo <= hi && hi <= haystack.len()
inv forall_below(haystack, lo, |x| x < needle)
inv forall_from(haystack, hi, |x| x > needle)
dec hi - lo
{
if lo == hi { return None; }
let mid = lo + (hi - lo) / 2;
if haystack[mid] == needle { return Some(mid); }
if haystack[mid] < needle { lo = mid + 1; } else { hi = mid; }
}
}
Anatomy:
req— precondition. Mandatory keyword;req truemust be written explicitly if there is genuinely no precondition, so absence is always a parse error, never an implicit default.ens— postcondition. Mandatory. Must mentionresultunless the return type is()(structurally enforced — see §7).fx— effect row. Mandatory. One ofpure, or a set drawn from{read(path), write(path), net(domain), alloc, time, rand, panic}. The runtime enforces the row as a sandbox: a function declaredfx purethat attempts I/O is killed at the syscall boundary, not trusted at the type level alone. Effect rows compose: a caller's row must subsume every callee's row, checked at compile time.inv/dec— loop invariants and a decreases-measure. Mandatory on everyloop/while. Termination is proved by default; divergence requiresfx divergein the row.
Contracts are written in SpecTherm, a restricted total language:
- No general quantifiers. Quantification is only available through a fixed library of bounded combinators (
forall_in,forall_below,exists_in,count_where,sorted,permutation_of,disjoint, …), each with hand-tuned, frozen SMT triggers. Undecidability lives in quantifier instantiation; Thermite locks the cage. - Closure bodies are flat predicates. A combinator's predicate-closure body (
|x| …) may use comparisons, arithmetic, boolean/logical operators, field/index access, and calls to namedspec fns — but it may not contain another combinator. This is what makes "locks the cage" precise: there are no anonymous nested quantifiers, so the frozen trigger on a combinator's predicate application fully controls its instantiation. Genuine nested quantification is written as a namedspec fn— which may itself quantify (boundedly), but carries its owndecmeasure and appears by name in the audit surface. Stated exactly: every quantifier is a bounded combinator with a frozen trigger; composition happens only through namedspec fns, never anonymous nested quantifiers. - Spec functions are executable. Every
spec fnis total, terminating (checked), and compilable to a runtime check. This guarantees the L1 fallback rung always exists for every contract, and it keeps the solver on predictable ground. - No spec-level recursion without a
decmeasure, same as code.
The expressiveness ceiling is a feature. A contract you can't write in SpecTherm is a contract the solver was going to choke on; the language refuses the bet upfront instead of letting the agent discover the timeout three hours in.
Every item and every block has a stable address:
binary_search — the function
binary_search.loop#1 — its first loop
binary_search.loop#1.inv#2 — the second invariant
The toolchain's edit operations take addresses, not string matches:
forge edit binary_search.loop#1.inv#2 --replace "forall_from(haystack, hi, |x| x > needle)"
forge insert-after binary_search --item "fn linear_search ..."
This eliminates the fragile-string-match editing problem at the language level. Blocks are delimited by explicit { } with no significant whitespace; a syntax error inside one item cannot cascade into the next (items are parsed independently — recovery is per-item by construction).
To fit the 6k-token skill budget and keep agent-written code formulaic:
| Removed | Replacement |
|---|---|
| Explicit lifetimes | Region inference + arena defaults; the three blessed ownership patterns (owned, &, &mut) cover the surface; anything fancier lives behind FFI |
| Trait system (full) | Closed set of built-in interfaces (Eq, Ord, Hash, Iter, Display); no user-defined traits in v0.1 |
| Macros | None. Code generation is the agent's job |
unsafe |
#[slag] (§8), which is harder to write and louder to read |
| Method syntax vs UFCS choice | One call syntax |
match ergonomics special cases |
One desugaring, always explicit |
| Implicit integer widening rules | All conversions explicit; arithmetic overflow is a proof obligation, not a runtime panic |
The result reads like a boring, regular, slightly verbose Rust dialect — exactly the register agents reproduce most reliably.
Agents don't "write a file and run it." The primary interface is a Lean-style dialogue where the unit of progress is discharging a goal, and every step is cheap and fully verified.
agent → forge: declare fn signature + contract (body = hole `?0`)
forge → agent: GOAL STATE
given: sorted(haystack)
want: match result { Some(i) => ..., None => ... }
holes: ?0 : body
agent → forge: fill ?0 with loop skeleton, invariants, two new holes ?1 ?2
forge → agent: GOAL STATE
?1 discharged ✓ (L3, 0.4s)
?2 open — obligation: lo <= hi preserved across `lo = mid + 1`
counterexample: lo=3, hi=3, mid=3 → lo=4 > hi=3
agent → forge: guard the branch
forge → agent: ALL GOALS DISCHARGED ✓ binary_search certified L3
contract score: non-vacuous ✓, mutants killed 23/24 (§7)
Three properties make this loop work for LLMs:
- The oracle does the remembering. Goal state is recomputed and re-presented every turn; the agent never has to maintain global correctness in its head, only propose the next local move.
- Counterexamples, not adjectives. When an obligation fails, Forge returns a concrete witness from the solver (or from Kani's bounded search) — never just "verification failed."
- Every message is a prompt. All Forge output is structured (JSON with a stable schema, rendered to readable text), includes the relevant source inline, and reserves a
suggested_moveslot populated by deterministic heuristics (missing-invariant patterns, overflow-guard templates, trigger hints).
The discontinuity of SMT solvers is the central engineering risk (§6). Forge's contract with the agent: the gate degrades, it never blocks.
- Every obligation gets a fixed solver budget (default 10s, portfolio of Z3 + cvc5 seeds in parallel).
- On timeout, Forge automatically attempts L2 (Kani bounded check, default bound from type-driven heuristics) and reports:
certified L2 (bound: slices ≤ 8, ints full range). - If even L2 times out, the obligation drops to L1: the SpecTherm contract compiles to runtime checks, the function is certified L1, and a
lowered-assuranceflag is attached to the build manifest. - Forge emits a solver profile on every timeout — which combinator's triggers blew up, which assertion consumed the budget — so "maybe" becomes "here's where I got lost," which is actionable for proof repair.
- The whole-project assurance level is the min over functions, displayed on every build. Driving L1s and L2s back up to L3 is a background task agents can run unattended (proof repair is a local, checkable move — the task shape LLMs are best at).
Builds, formatting, codegen, and check results are bit-reproducible given the same toolchain version and solver seeds (seeds are pinned in the lockfile). The agent can reason about behavior without re-running things, and a proof that passed yesterday passes today unless something semantically relevant changed. Proof results are content-addressed and cached per item: an edit to f cannot invalidate g's certificate unless g's contract references f's contract.
| Level | Mechanism | Guarantee | Termination of the check |
|---|---|---|---|
| L4 | Admitted decidable route with checked reconstruction (relaxation, fixed-width BV, or finite EPR) | Contract holds for all inputs; false clauses return a concrete real, bit-pattern, or finite-structure witness | Route-specific budget |
| L3 | Verus/Z3 or Lean proof | Contract holds for all inputs | Not guaranteed → budget + downgrade |
| L2 | Bounded model check (Kani-derived) | Contract holds for all inputs up to bound | Guaranteed |
| L1 | Runtime contract checks | Violations detected at the call site, in every build profile (not just debug) | Guaranteed |
| L0 | #[slag] |
Nothing. Trusted by fiat | — |
Rules:
- L3 is the default target for general functions. L4 is the caged rung for admitted decidable clauses whose result is checked through the route's reconstruction path.
- Downgrades are automatic, logged, and surfaced in the build manifest; upgrades are a standing background task.
- Out-of-cage clauses escalate UP to the forge, they do not degrade down. A clause outside the v1 combinator cage is no longer lowered by inspection down the L3→L2→L1 ladder; it routes UP to the forge, where the relax route (REQ-8) discharges a relaxable polynomial clause via a direct Z3 nlsat (QF_NRA) query and certifies it at the kernel-grounded L4 — its trust profile is
solver(nlsat) + spine-lemma(kernel), the real→integer soundness bridge being the kernel-checked spine lemmasr_relax_sound+rencode_sound(lean/Thermite/Relax.lean). A clause true over ℤ but false over ℝ is not refuted: the route yields aRealWitnesscarrying the real point, escalated to the forge as goal metadata (never a counterexample). (Authority: RFC-1 / GH #2;.design/stage1-forge-tier.mdREQ-8.) - Plain
forge checkalso routes eligible@bvNclauses and admitted finite relation/array clauses through checked QF_BV or EPR reconstruction. They certify at L4 only after replay of the actualreq → clauseobligation; false clauses return concrete countermodels. - The certificate attached to a build artifact lists every function's level, every
#[slag]block, and the contract-quality scores from §7. This manifest is the deliverable's trust statement. #[slag], the L0 row, and L1 enforcement. The L0 row measures assurance about the body: nothing is proved about the implementation — it is trusted by fiat. But a#[slag]function's contract is still mandatory and is enforced at runtime (§8), so its certificate carries level L1 with aslag: trueflag —L1because the contract is L1-checked at the call site,slagbecause the body is unproven. The L0 row therefore names the body-proof aspect (recorded by theslagflag), never an unchecked contract: slag exempts proving, never stating and checking. (fxeffect rows are likewise enforced — at compile time in v0.1, at the syscall boundary later — independent of the proof level.)
A mandatory gate creates pressure to game it: weaken the postcondition until it's trivially true. Thermite makes the degenerate moves mechanically detectable, and runs the battery inside the gate itself. A function does not certify until its contract certifies.
Deterministic checks, in order of cost:
- Structural triage (free):
enssimplifies totrue→ reject.ensdoes not mentionresult(non-unit return) → reject.ensis syntactically implied byreqalone → reject.- Effect row is maximal (
fx *) without#[slag]justification → reject.
- Tautology check (one solver query per contract): is
ensprovable fromreq+ types without the function body? If yes, the contract says nothing about the implementation → reject with the proof as the explanation. - Vacuity check (one query): is
reqsatisfiable? An unsatisfiable precondition verifies everything about the empty set → reject with the unsat core. - Mutation scoring (parallel, budgeted): Forge generates N mutants of the body (operator flips, off-by-ones, early returns, branch swaps — fixed deterministic mutator set, seeded from the lockfile) and re-verifies each against the contract. The kill ratio is recorded in the certificate. A configurable floor (default 60%) gates certification; below it, Forge reports exactly which mutants survived, which tells the agent which behavior the contract fails to constrain — a precise prompt for strengthening.
- Strengthening probes (budgeted): template-based tightenings of
ens(tighter bounds, added conjuncts from the SpecTherm combinator library) are tried automatically; if a strictly stronger contract proves with no body change, Forge suggests it.
What the battery cannot check — whether the contract is the property the user wanted — is the residue surfaced for review: the certificate includes the full spec layer (typically a few percent of total line count), pre-screened to be non-vacuous, non-trivially-weak, and mutation-scored. The reviewer's job is reduced to reading declarative statements and asking "is this what I meant?" That review slot is pluggable: a human, or a critic model whose only question is spec-intent alignment.
Slag is the waste product of a thermite burn — the part that didn't become weld. Unverified code is named accordingly.
#[slag(reason = "vendored SIMD intrinsics; contract checked at boundary by L1 wrapper",
owner = "agent:forge-7/session-2026-06-04",
review = "required")]
fn simd_sum(xs: &[u32]) -> u64
req xs.len() <= u32::MAX as usize
ens result == spec_sum(xs) // contract still mandatory — enforced at L1
fx pure
{ ... }
Rules:
reason,owner, andreviewfields are mandatory and non-empty (checked).- The contract is still mandatory and is enforced at L1 (runtime) — slag exempts you from proving, never from stating and checking.
- Every slag block appears in the build manifest and in
forge auditoutput.grep slagover a codebase is the complete inventory of fiat-trusted code. - CI policy hooks can cap slag count or require a second-party sign-off per block.
The polarity inversion is the point: in every mainstream language, verification is the exotic add-on; in Thermite, non-verification is the exotic add-on, and it costs more keystrokes, more metadata, and more visibility.
Thermite gets Rust's ecosystem, but never silently inherits its trust assumptions.
- A
crates.iodependency is imported through a boundary module: each foreign function used must be given a Thermite signature withreq/ens/fx, enforced at L1 (runtime checks on every crossing) since the foreign body can't be proved. - Boundary modules are slag-adjacent: they appear in the audit manifest with per-function contracts, so the trusted computing base is enumerable — it is exactly (slag blocks ∪ boundary contracts ∪ the toolchain itself).
- Pure-Thermite transitive closures can be certified end-to-end; the manifest distinguishes "verified to the boundary" from "verified, period."
Composition rule (the reason any of this scales): if g calls f only through f's contract, then g's certificate is valid independent of f's body. Trust is invariant under composition instead of decaying multiplicatively — which is the property that matters once unsupervised agents start building large systems.
The canonical language definition is THERMITE.skill.md: the complete surface
grammar, SpecTherm library, Forge method set, ladder semantics, and slag rules,
budgeted at ≤ 6,000 tokens and enforced in CI. Grammar variants are
compile-forced through exhaustive renderers; combinators, recursion schemes,
and Forge methods come from the same registries their implementations consume.
Consequences:
- No cold-start corpus problem. An agent that has never seen Thermite reads the skill at session start and holds the entire language in context — no half-remembered semantics, no hallucinated stdlib, because the stdlib surface is in the skill too.
- No version skew. The skill is versioned with the toolchain; Forge serves the matching skill on
forge skill, so the agent's mental model and the checker are never out of sync. - Uniform training signal. When models do eventually train on Thermite code, the one-way-to-do-everything property means the corpus is stylistically uniform — maximum signal per token.
Thermite explicitly does not try to be:
- Pleasant for humans to write. Humans review the spec layer and the audit manifest; they should rarely write bodies.
- Expressive. No metaprogramming, no clever abstractions, no embedded DSLs. If a pattern is common, it becomes a stdlib function or a SpecTherm combinator through the (slow, budget-gated) RFC process — never a user-level abstraction mechanism.
- A proof assistant. Full functional-correctness proofs of deep mathematics are out of scope; that's Lean's job. Thermite's L3 targets the contracts ordinary systems code needs: bounds, overflow, state-machine invariants, frame conditions, termination.
- Fast to compile. Verification time is an accepted cost, mitigated by per-item caching, parallel solver portfolios, and the degrade-don't-block protocol — never by weakening the gate.
| Risk | Mitigation |
|---|---|
| SMT discontinuity: small edits flip proofs to timeouts | Quantifier-locked SpecTherm; frozen triggers; per-item proof caching; portfolio solving; automatic L2/L1 degrade; solver profiles as repair prompts |
| Goodhart on the gate: vacuous/weak contracts | §7 battery inside the gate; mutation kill-ratio floor; strengthening probes; spec layer surfaced for the one irreducible judgment |
| Spec-intent gap: right proof, wrong property | Spec layer kept small, declarative, pre-screened; pluggable critic-model/human review slot; contracts as the only artifact that needs intent review |
| Ecosystem gravity: nobody adopts a new language | Skill-as-spec removes the training-corpus barrier; Rust lowering gives ecosystem access from day one; the target user (agents) adopts by config flag, not by community persuasion |
| Proof brittleness across edits | Locality by construction: certificates are per-item and content-addressed; cross-item invalidation only through contract references |
| Bounded checks oversold as proofs | Manifest states bounds explicitly; L2 and L3 are visually and programmatically distinct everywhere they appear |
- v0.1 (kernel): surface language §4, lowering to Rust MIR (realized as source-level transpilation to Verus-Rust;
rustcperforms the MIR lowering — direct MIR emission retired as unneeded, #21 decision), L3/L1 rungs via Verus passthrough, structural vacuity checks,forgeCLI with goal-state output, skill generator + CI budget. - v0.2 (ladder): Kani-backed L2 with type-driven bound inference, automatic degrade protocol, solver profiles, proof cache.
- v0.3 (battery): mutation scoring, strengthening probes, tautology/vacuity solver checks in the gate, audit manifest format v1.
- v0.4 (boundary): crates.io boundary modules, L1-enforced FFI contracts, end-to-end vs to-the-boundary certification in the manifest.
- v0.5 (repair): background proof-repair agent loop (L1→L2→L3 upgrades), critic-model spec-review integration, multi-agent Forge sessions.
spec fn spec_sum(xs: &[u32]) -> u64
dec xs.len()
{
match xs {
[] => 0,
[head, ..t] => head as u64 + spec_sum(t),
}
}
fn sum(xs: &[u32]) -> u64
req xs.len() <= 1_000_000
ens result == spec_sum(xs)
ens result <= xs.len() as u64 * u32::MAX as u64
fx pure
{
let mut acc: u64 = 0;
let mut i: usize = 0;
while i < xs.len()
inv i <= xs.len()
inv acc == spec_sum(&xs[..i])
inv acc <= i as u64 * u32::MAX as u64
dec xs.len() - i
{
acc = acc + xs[i] as u64; // overflow: discharged from inv#3 + req
i = i + 1;
}
acc
}
Certificate (excerpt of build manifest):
{
"item": "sum",
"level": "L3",
"solver_time_ms": 612,
"contract_quality": {
"tautology": false,
"vacuous_precondition": false,
"mutants_killed": "17/18",
"survivor": "mutant#11: `i = i + 1` → `i = i + 2` survives ens but killed by inv#2"
},
"effects": ["pure"],
"slag": false
}The live method inventory is thermite_skill::ForgeMethod::ALL. Forge uses it
to recognize top-level methods and render help; the skill generator uses the
same records. Run forge for current synopses or forge skill for the
toolchain-matched language reference. This avoids a second hand-maintained
command list in the design document.