AlmidePRE-1.0

Almide · a programming language · pre-1.0

A language I'm building to make AI-written code safer.

AI agents write more code than I can read. So I want the safety to come from the compiler. And then I don't let the compiler grade its own work either.

ALMIDELANGUAGE / OPEN SOURCE
654theorems and lemmas proven in Rocq and checked by its kernel. None admitted, no added axioms
100%of the test corpus builds with machine-checked evidence, on both targets: 1,050 programs on Wasm, 1,055 on Rust
1,324standard library functions in 51 modules, written in Almide itself
325 Bhello world as a WebAssembly file. Emitted directly, no LLVM

Counts are from the public repository at commit 79cdadd (2026-11-10; the contract count is the ledger stamped 2026-11-08). The 325 bytes is version 0.69.0 without wasm-opt. They move.

01 — WHY

I can't read everything my agents write anymore.

AI agents write a lot of my code now. I think that is going to be true for a lot of people soon, and review does not scale to it.

So I wanted a language where more of the safety comes from the compiler and less from somebody reading carefully. Something I can lean on for code I didn't write.

I think Rust is the language you'd want AI to write. But honestly I'm not a strong Rust programmer, and I never got used to ownership. Almide is my attempt to keep that kind of safety without making anyone, person or agent, write the ownership part.

It is statically typed, compiles to native code through Rust, and emits WebAssembly directly. It has Option and Result, exhaustive pattern matching, and explicit effect declarations.

02 — NOTHING TO GET WRONG

You don't write ownership. The compiler infers it.

type Order = { id: Int, items: List[String], total: Int }

fn large(orders: List[Order]) -> List[Order] =
  orders |> list.filter((o) => o.total > 500)

fn main() -> Unit = {
  let orders: List[Order] = [
    { id: 1, items: ["pen", "ink"], total: 260 },
    { id: 2, items: ["desk"], total: 900 },
  ]
  let big = large(orders)
  println("${list.len(orders)} orders, ${list.len(big)} large")
  for o in orders {
    println("${o.id}: ${list.join(o.items, ", ")}")
  }
}
$ almide run orders.almd --target wasm
2 orders, 1 large
1: pen, ink
2: desk

orders is passed to large and then used again in the loop. Nothing in the source says moved, borrowed or copied. No lifetimes, no free.

The compiler works out where every heap value is created, shared and consumed, with a Perceus-style inference. The two targets then carry that out differently. Native output is Rust, and Rust's own drops do the freeing. Wasm uses reference counting.

The cost. Copies and reference-count operations still happen. You just can't see where in the source. In Rust you can see a .clone(). Here you can't, and I don't have a good answer for that yet.

03 — THE PART I CARE ABOUT

The compiler doesn't get to grade its own work.

I don't trust my own compiler. I built it with AI agents, so why would I?

Every time it builds Wasm, the compiler has to write down evidence of what it did: every place it shares or frees a value, every array index it checked. A separate program reads that evidence. If it doesn't add up, there is no output file.

A rejection is not your bug. It is the compiler's, and the build says so:

error[certificate-rejected]: app.almd: almide-verify judged this build's
certificate and did not accept it (exit 1)
  app.wasm was not written: the build's own certificate does not hold,
  which is a compiler bug — please report it

Abridged. The verifier's own report follows the first line.

04 — TRUST SPINE

The checker can have bugs too. So there are four.

One checker would just move the trust somewhere else. These four are built in different ways, so the same mistake is unlikely to be in all of them. Together with the evidence they check, the project calls this the Trust Spine.

◆

The whole picture

One source, two targets, and who checks what

One compiler, two targets. The top lane is WebAssembly and the bottom lane is Rust. Between them is what they share: the rule, Proofcore, which is the proof that the rule is right, and Proofcast, which runs the rule over the evidence from both lanes.

Where the two lanes differOn Wasm, Buildgate stands in front of your disk and Byteanchor ties the evidence to the bytes. On Rust, rustc stands in that place and is trusted. Buildgate judges the evidence in the project's verification instead of at your build, and nothing ties the evidence to the compiled binary yet. Coverage is no longer one of the differences. Every program in the cross-target corpus now goes through the certified route on both lanes, 1,050 on Wasm and 1,055 on Rust. A program outside what the Rust renderer handles still falls back to the standard code generator and carries no evidence.

1

Proofcore

The Rocq proof kernel

runs in CI

In concrete terms

(* proofs/MemorySafe.v *)
Definition safe_ev (h : Heap) (e : Ev) : Prop :=
  spatial_ev h e /\ temporal_ev h e /\ init_ev h e /\ type_ev h e.

Definition MemorySafe (t : list Ev) : Prop := holds safe_ev empty_heap t.

Theorem rc_check_gives_temporal : forall t,
  holds fresh_alloc_ev empty_heap t ->
  (forall b, check (gproj b t) = true) ->
  Temporal t.

This is the actual text. Memory safety is four conditions on every memory event. The theorem says that if the reference-count check accepts every block, nothing touches freed memory. Rocq checks the proof, so nobody has to take it on trust.

The checker is defined and proven inside the Rocq proof assistant. Its verdict can be computed by the proof kernel itself.

Guards againstThe checker's own logic being wrong. What is proven is the rule itself: if it accepts, the property holds.

2

Proofcast

A checker generated from the proofs

runs in CI

In concrete terms

witness ownership 8 large
ibd

im

witness bounds 5 (program)
e0 a0

witness init 53 (program)
h12 w4 n2 r1 s0 s1;h12 w4 n2 r0 s0 s1;h12 w4 n1 r0 s0

witness call-modes 25 (program)
1;1 1;1 1|2 1 1;0 1;1 1 1

This is the evidence itself, from the order example above. It is plain text: for ownership, one line per value and one letter per thing done to it. The checker reads this and nothing else. No source, no compiler internals.

The same proven checker, sent out of Rocq as a standalone program so it is fast enough to run over the whole corpus.

Guards againstA proof that only exists on paper. This is the proven rule actually run, on every program in the corpus.

3

Buildgate

A second implementation, written separately in Rust

runs on your machine

In concrete terms

$ almide verify orders.almd --artifact orders.wasm
ARTIFACT  sha256 8d407d7b…273e9356  orders.wasm: MATCH
ACCEPT  ownership        large
ACCEPT  ownership        main
ACCEPT  call-modes       (program)
ACCEPT  bounds           (program)
ACCEPT  init             (program)
ACCEPT  fields           (program)
ACCEPT  names            (module:funcs)
ACCEPT  caps             main
ACCEPT  caps-transitive  (program)
  … 35 more lines
almide-verify 0.2.0: 44 witness(es), 44 accepted, 0 rejected, 0 uncertified -> CERTIFIED

# the same check after flipping one bit of orders.wasm
ARTIFACT  sha256 99a2b283…e4078e57  orders.wasm: MISMATCH (the bundle names 8d407d7b…)
almide-verify 0.2.0: 44 witness(es), 44 accepted, 0 rejected, 0 uncertified -> REJECTED

Real output for the 16-line order example, abridged. The compiler recorded 44 pieces of evidence and all 44 were accepted. Flip one bit of the file and the same evidence is refused, because it was recorded for a different file.

almide-verify. Independent code, not proven. It ships next to the compiler and is the one that stops your build.

Guards againstA bad build reaching your disk. It also refuses evidence that was recorded for a different file, by hash.

4

Byteanchor

A tool that opens the finished file

runs in CI

In concrete terms

;; a recorded bounds check must be exactly this, in the file
i64.ge_u          ;; index >= length ?
if
  i32.const …     ;; the evidence is anchored at this instruction
  …
  unreachable     ;; abort
end               ;; and the if has no else

;; a recorded allocation must be exactly this
i32.const N*W
call $alloc
local.set B       ;; every store that follows goes through local.get B

Taken from the tool's own source. It decodes the finished file and requires each recorded event to sit on an instruction pattern like these. A check that was recorded but not emitted has nothing to sit on.

It decodes the final .wasm and holds each recorded bounds, initialization and field event to the instruction it names. Evidence about code that isn't there does not pass.

Guards againstEvidence about code that isn't there. A bounds check that was recorded but never emitted is caught here.

In CI, Proofcore, Proofcast and Buildgate have to reach the same verdict on every piece of evidence, for every program in the test corpus. A rejection from this setup has already caught a real miscompile in my compiler.

The proofs are 46 Rocq files in the repository, 654 theorems and lemmas in about 21,700 lines. They are about the checker and its models, not about the compiler.

What the evidence is about

The evidence comes in kinds, one per property. Each kind has its own rule, and the rule is what the four checkers apply.

kindthe bug it stopsaccepted only if
ownership
the bug it stopsDouble free, leak, use after free
accepted only ifevery object's reference count never faults and ends at zero
bounds
the bug it stopsReading or writing past the end of a list
accepted only ifevery access is preceded, on every path, by a check of that slot
init
the bug it stopsReading memory that was never written
accepted only ifevery construction writes every slot of its block, and none past it
fields
the bug it stopsTreating a value as the wrong shape
accepted only ifevery record, tuple and variant store, and every field access, fits its layout
call-modes
the bug it stopsCaller and callee disagreeing about who owns an argument
accepted only ifevery call site passes its arguments the way the callee's signature says
names
the bug it stopsA reference to something that doesn't exist
accepted only ifevery name that is used is defined
caps
the bug it stopsCode touching files or the network without saying so
accepted only ifeverything a function can reach, directly or through its calls, is in its declaration

These hold for the code the evidence covers. Byteanchor re-checks the bounds, init and fields events against the bytes, and matches the recorded reference-count events against the calls in the file. Events it cannot tie to an instruction are counted as unlocated, not passed silently.

05 — WHEN THE CODE IS WRONG

The error tells the agent what to change.

Agents make mistakes. What matters is what happens next. An agent that learned other languages will write items.length:

error[E013]: no field 'length' on list
  --> app.almd:3:14
  in field access .length
  here: println("${items.length}")
  hint: Almide values have no fields — use the `list` stdlib module.
  try: (machine-applicable — `almide fix` applies it)
      list.len(items)
$ almide fix app.almd
  Applied 1 machine-applicable fix-it(s) from diagnostics: E013 at line 3

That one is mechanical, so the tool applies it. A misspelled function name is different:

error[E002]: undefined function 'string.lenn'
  hint: Did you mean `string.len`?

$ almide fix app.almd
1 diagnostic(s) have `try:` snippets that need manual application:
  [E002] app.almd:3:14  undefined function 'string.lenn' (maybe-incorrect)

Compiler output from 0.69.0, abridged.

That is a guess about what the author meant, so it stays a suggestion. I'm trying to keep the line between a mechanical repair and a decision about intent visible in the tooling.

06 — MISTAKES THAT DON'T SHIP

A forgotten case is a compile error. So is an ignored failure.

The edit mistake I see most from agents: add a variant to a type, and miss one of the places that has to handle it.

type Shape =
  | Circle(Float)
  | Rect(Float, Float)
  | Triangle(Float, Float)      // added

fn area(s: Shape) -> Float = match s {
  Circle(r) => 3.14159 * r * r,
  Rect(w, h) => w * h,
}                               // not updated
error[E010]: non-exhaustive match: missing Triangle(_, _)
  --> shape.almd:6:36
  hint: add arms for Triangle(_, _):
  Triangle(arg1, arg2) => _

Failure works the same way. A call that can fail gives you a Result, and you have to say what happens to it. Dropping it on the floor is an error, not a warning.

effect fn load(path: String) -> String = {
  let text = fs.read_text(path)     // can fail, and nothing says so
  text
}
error[E041]: implicit propagation of this binding's value was removed —
this value is a Result
  --> load.almd:4:14
  hint: Write `expr!` to propagate, or consume the Result as a value
  (`??`, `?`, match ok/err).

Compiler output from 0.69.0, abridged.

One character fixes it: fs.read_text(path)!. The point is that the author had to decide, and the decision is visible in the source.

07 — ONE SOURCE, TWO TARGETS

Native and WebAssembly from the same source, held to the same output.

Native builds go through Rust. WebAssembly is emitted directly by the compiler, with no LLVM in between. Hello world comes out as a 325-byte .wasm file.

Two backends can disagree. So every observable promise is written down as a contract, 454 of them today, each tied to executable evidence. CI runs 1,055 programs on both targets and compares stdout, stderr and the exit code. A third executor, an interpreter that runs the program before any code is generated, votes as well.

The comparison holds under documented conditions. Randomness, clocks and platform details have their own scoped contracts, and the interpreter abstains on 17 programs it can't run.

08 — WHAT ELSE IS IN IT

It is a whole language, not a demo.

Standard library

1,324 functions in 51 modules, written in Almide itself. It is the largest Almide program there is.

Tooling in one binary

almide test, fmt, check, fix and explain. Tests live next to the code in test blocks.

Explicit effects

effect fn marks code that touches the outside world. Propagating a failure is always written, with !.

Structured concurrency

fan runs work on threads natively, and produces the same output order on Wasm.

Releases you can verify

Every release asset is Sigstore-attested. The installer checks the checksum and refuses to install without the attestation.

Used for real programs

Coding agents, parsers, a 2D/3D renderer, a terminal emulator. 144 public repositories carried Almide source in September 2026. See the list.

09 — WHAT IS NOT DONE

It is not finished, and I want to be clear about that.

  • The compiler as a whole is not proven. The proofs cover the checker and its models.
  • Native builds don't run the verifier. They go through a certified renderer, and the whole test corpus now does. But the evidence is judged in the project's verification, not at your build, and a program the renderer can't handle still falls back to the standard code generator.
  • On your machine only one of the four checkers runs. The other three run in CI, on the test corpus. If the verifier isn't installed, a Wasm build goes through unchecked.
  • Some evidence is partial. A certificate that covers only part of a program is let through. Component output is not checked this way.
  • Not every recorded event is tied to an instruction yet. Byteanchor reports the ones it cannot locate as unlocated. That number is not zero everywhere.
  • A program can pass every check and still compute the wrong thing. None of this is about whether the logic is right.
  • I can't show that agents write Almide better than Rust or TypeScript. My first measurement was about 100% in every language with a strong model, so it told me nothing.
  • It is pre-1.0. There are known bugs and things will change.

The full account of what is proven and what is only trusted is in the repository: proven vs trusted.

10 — HOW IT WAS BUILT

I built this with AI agents. That is the point.

The compiler is written in Rust, and I used AI agents heavily to write it. I'm saying that up front because some people will not want to look at a project built that way, and that is fair.

But it is also why the project looks the way it does. If agents write the compiler, I can't vouch for it by having read it. So it has to produce evidence, and something else has to check the evidence. That is the same problem everyone using agents is about to have with their own code. Almide is me working on it in the open.

11 — TRY IT

See where it breaks.

The playground runs in your browser with nothing to install. To install the compiler and the verifier on macOS or Linux:

curl --proto '=https' --tlsv1.2 -fsSL \
  https://raw.githubusercontent.com/almide/almide/main/tools/install.sh | sh

The installer needs the GitHub CLI (gh) to verify the release attestation. Windows and source builds are in the README.