In development · opening up soon
The language for agent‑written code.
Agents write more of our code every month. Tern is a compiled language built for them: syntax models already know, errors that say exactly what to write instead, and checks that catch the bugs agents make. What they write compiles to native code that outruns Rust and Go.
struct Order:
item: str
qty: int
price: float
fn total(orders: [Order]) float:
t = 0.0
for o in orders:
t += float(o.qty) * o.price
return t
fn main():
orders = [Order("tea", 2, 3.5), Order("scone", 3, 2.25)]
busiest = orders.sort_by(fn(o) -o.qty)[0]
print(f"total {total(orders)}, most ordered: {busiest.item}")total 13.75, most ordered: scone
import roost
import json
import sqlite
from todos_db import list_todos # typed, from queries.sql
fn todos(req: roost.Request, d: sqlite.Db) roost.Response:
return roost.json(json.encode(list_todos(d)))
fn main():
d = sqlite.open("todos.db")
r = roost.router()
r.get("/todos", fn(req) todos(req, d))
roost.serve(r, 8080)[{"id":1,"title":"ship it","done":false}]roost: routing, middleware, WebSockets and SSE on green threads, with no async anywhere. More requests per second on one thread than Rust’s axum.fn is_prime(n: int) bool:
if n < 2:
return false
d = 2
while d * d <= n:
if n % d == 0:
return false
d += 1
return true
fn main():
n = 2_000_000
found = [false] * n
parallel for i in 0..n: # on every core
found[i] = is_prime(i) # each i writes only found[i]
print(len(found.filter(fn(p) p)), "primes below", n)148933 primes below 2000000
-- name: find_todo :one SELECT id, title, done FROM todos WHERE id = @id; -- name: add_todo :one INSERT INTO todos (title) VALUES (@title) RETURNING id;
import sqlite
import todos_db # tern sqlgen queries.sql -o todos_db.tn
fn main():
d = sqlite.open("todos.db")
id = todos_db.add_todo(d, "ship it").unwrap()
if todos_db.find_todo(d, id) is Some(t): # Option[Todo]
print(t.title, t.done)Made for agents
Easy for a model to get right. Cheap to run once it does.
We gave a coding agent the same spec, a link shortener with a database and click counts, and an empty directory in Tern, Go and TypeScript. All three apps pass the same 32 acceptance checks.
| Agent-built app | Tern | Go | TypeScript |
|---|---|---|---|
| Lines of code excluding tests | 273 | 805 | 502 |
| Redirects per second | 146k | 154k | 47k |
| CPU per redirect | 23 µs | 32 µs | 21 µs one thread |
| Deployed size | 408 KB | 13.6 MB | 25.8 MB |
| Memory at idle | 4.4 MB | 18.6 MB | 89.6 MB |
Looks like what models know
Python-shaped syntax, so a model writes plausible Tern on the first try, and the compiler checks every type before anything runs.
Errors that say what to write
Every error names the fix, and habits from Python, Go, Rust and JavaScript get a hint: append becomes push, range(n) becomes 0..n. Machine-readable with --json.
One page to learn it
The whole language fits in a reference a model reads in one go, and a plugin teaches Claude Code to write, check and test Tern.
Benchmarks
Tern against Rust and Go, losses included.
Each benchmark is the same program written in all three languages. All three print the same answer, and they run interleaved on an Apple M4 Pro. This is ongoing work: the numbers change as Tern does, and we keep adding benchmarks.
Core language
| Benchmark | Tern | Best Rust | Go |
|---|---|---|---|
| n-body floating point | 896 ms | 982 ms | 2,671 ms |
| binary-trees allocation, parallel | 326 ms, 99 MB | 343 ms, 96 MB mimalloc | 1,559 ms, 166 MB |
| word count hash map of strings | 122 ms, 40 MB | 166 ms, 40 MB foldhash | 207 ms, 199 MB |
| int map 2M keys | 69 ms, 67 MB | 106 ms, 142 MB foldhash | 621 ms, 121 MB |
| JSON encode + decode 300k structs | 172 ms, 107 MB | 249 ms, 194 MB serde | 809 ms, 303 MB |
| sort stable sort_by | 169 ms, 118 MB | 255 ms, 189 MB | 1,870 ms, 167 MB |
| lines split and scan text | 118 ms, 55 MB | 192 ms, 57 MB | 370 ms, 257 MB |
| sieve primes | 229 ms, 25 MB | 642 ms, 192 MB | 723 ms |
| fib fork-join, all cores | 114 ms | 115 ms rayon | 168 ms |
| tasks parallel for | 25 ms | 25 ms rayon | 36 ms |
Web servers and concurrency one server thread
Standard library one thread; Tern’s regex engine is written in Tern
(?i)error|warning|fatalMB/s, higher is betterBuilds, binaries and WebAssembly
Where Rust still wins: single-threaded WebAssembly binary-trees against Rust with mimalloc (2.84 s vs Tern’s 3.75 s), and binary-trees memory natively (96 vs 99 MB). SHA-512 is a tie.
Rust release builds on the build charts; Rust’s unoptimized debug build rebuilds in 0.94 s.
Built with Tern
We’re using it to build real products.
The best test of a language is what you can ship with it. These two products are written in Tern.
ternTS
TypeScript, transpiled exactly like tsc. Many times faster.
A TypeScript-to-JavaScript transpiler whose output is identical to tsc’s on 37,000 files from NestJS, VS Code, Grafana, Excalidraw, Immich and Twenty, decorator metadata included. Compiled to WebAssembly, the whole transpiler is 1 MB, so you can run it on your own project in your browser.
swc 202 ms · tsc 1.44 s
swc 6.07 s · tsc 40.2 s
nest start --watch 676 msStackGrep
Grep the source code of the web.
Search the HTML of tens of millions of websites by text or regex, to find who uses a library, a tracker or a pattern. The whole thing is written in Tern: crawler, live fetcher, HTML-to-Markdown converter, a trigram index served straight from S3, the API and its MCP server for AI agents.
Visit StackGrep →the server starts in 0.5 s with 374 MB of RAM
5× less than Rust’s fast_html2md
half the CPU of Go’s net/http
Safe without references
The compiler catches the race.
Tern has no pointers and no borrow checker to satisfy. Because values can’t point at each other, three simple rules are enough to rule out dangling data, aliasing bugs and data races:
- Everything is a value. Nothing dangles, nothing forms a cycle, and reference counting frees it all.
- Exclusivity. An argument passed as
&xcan’t overlap any other argument of the call. - Race freedom. In
parallelcode, what one task writes no other task touches.
Errors say what’s wrong and what to write instead, including when the code looks like Python, Go, Rust or JavaScript.
fn main():
xs = [3, 1, 4, 1, 5, 9, 2, 6]
total = 0
parallel for i in 0..len(xs):
total += xs[i]
print(total)race.tn:5: error: iterations of `parallel for` run at the same time, so writing `total` from every iteration would race; store per-iteration results in an array indexed by `i`, or combine results after the loop
Batteries included
Everything a server needs, in one small binary.
Parallel by default
parallel: blocks and parallel for loops on a work-stealing scheduler, checked for races at compile time.
roost, the web framework
Axum-style routing, middleware, WebSockets and SSE on green-thread tasks. No async/await anywhere.
Typed SQL
PostgreSQL and SQLite, with migrations and generated query functions: a renamed column is a compile error.
Runs everywhere
macOS, Linux (even a static binary in an empty container), WebAssembly with threads, iOS and Android.
Talks to C
Call C with no binding generator, or build Tern as a library for C, Swift, Kotlin, Python and JavaScript.
Sub-second rebuilds
An optimized rebuild after an edit takes well under a second, so an agent’s edit, compile and test loop stays fast.
Tern isn’t public yet.
We’re opening it up soon: the compiler, the standard library, the docs and a package index. In the meantime, ternTS runs in your browser today.