A programming language for coding agents. Your agent already writes TypeScript, so it can already write Tov, and when it gets something wrong the compiler hands back the edit that fixes it. What it ships is a native binary that beats Rust's.
binary_trees: millions of small objects made and freed
Tov260 msfastest
Bun376 ms45% slower
Node449 ms1.7× slower
C688 ms2.6× slower
Rust791 ms3.0× slower
Compiling a program to a native binary
Tov42 msfastest
C (clang)49 ms17% slower
Rust (rustc)98 ms2.3× slower
Memory, serving that JSON API
Tov1.6 MBleast
Rust (axum)4.4 MB2.8× more
Bun17 MB10.4× more
What happens when Tov builds a program
Tov is a compiler. It checks the TypeScript your agent wrote, writes it out as C and hands that to clang, so what you ship is one native binary with nothing to install next to it.
1TypeScriptshapes.tov
2Check0.3 ms
3C0.2 ms
4Binary55 ms · 34 KB
Your agent writes TypeScript
Tov reads TypeScript's syntax and types, with int and f64 for numbers, and Bun's and Node's APIs. It leaves out what makes a program hard to follow: any, null and ==.
type Shape =
| { kind: "circle", r: f64 }
| { kind: "rect", w: f64, h: f64 }
function area(s: Shape): f64 {
switch (s.kind) {
case "circle":
return Math.PI * s.r * s.r
case "rect":
return s.w * s.h
}
}
Tov checks every line
The checker, written in Rust, catches what TypeScript lets through: a call that can fail without try, a value shared where it should be copied, a switch that misses a case. Each error has a code and, where the fix is clear, the edit that makes it, which tov check --json hands to your agent.
// without the "rect" case
$ tov check shapes.tov
error[F0301] shapes.tov:6:11: `switch` is not
exhaustive: missing "rect"
6 | switch (s.kind) {
fix: add case "rect"
It writes the program as C
The whole program becomes one C file. Records become structs and a union becomes a tagged union, so switch (s.kind) is a switch on an integer. Values are reference-counted, with the counting written into the C, so there's no garbage collector to the program.
struct Shape {
uint32_t tag;
union { Circle m0; Rect m1; } u;
};
static double area(Shape s) {
switch (s.tag) {
case 0: return 3.141592653589793 * s.u.m0.r * s.u.m0.r;
case 1: return s.u.m1.w * s.u.m1.h;
}
}
clang makes one binary
clang compiles that C with Tov's runtime, a C library with the event loop, the HTTP server and client, and Tov's strings, arrays and maps. The runtime is compiled once and cached, so most of a build is clang's. A program that imports an npm package also carries the package and JavaScriptCore, the engine Bun uses, to run it.
$ tov build shapes.tov --time
check 0.3 ms, generate 0.2 ms, C compile 55 ms
build: shapes
$ ls -lh shapes
34K shapes
$ ./shapes
circle: 3.14
rect: 6.00
How an agent writes Tov
1Your agent writes TypeScript
// hello.tov
Bun.serve({
port: 3000,
fetch(req) {
const url = new URL(req.url)
return new Response(`Hello from ${url.pathname}`)
},
})
2Tov hands back the fix
$ tov check --json
{
"code": "T0831",
"message": "`new URL` can throw `TypeError`; mark the call `try` to pass the error on, or catch it",
"fixes": [{
"applicability": "safe",
"edits": [{ "line": 4, "col": 17, "text": "try " }]
}]
}
3You ship a native binary
// the agent applies the fix
const url = try new URL(req.url)
$ tov build hello.tov
build: hello
$ ls -lh hello
152K hello
$ ./hello & curl :3000/agents
Hello from /agents
It's TypeScript
With the APIs your agent knows from Bun and Node. Bun.serve, fetch, node:fs, async/await and npm packages work as it expects.
Less to get wrong
There's no any, null or ==, and a call that can fail is marked try. If code looks like TypeScript it behaves like TypeScript, and if it can't, it doesn't compile.
A 42 ms compile
--watch restarts the program on save, and tov test runs the tests that sit next to the code.
Against Rust, C, Bun and Node
Tov compiles to C and counts references instead of collecting garbage, so there are no collector s and the binaries stay small.
Programs
binary_trees3.0× faster than Rust
sort1.6× faster than Rust
map_insert1.7× faster than Rust
dispatch2.1× faster than Rust
strings1.6× faster than Rust
fib7% faster than Rust
nbody7% faster than Rust
Servers and builds
web server12% faster than Rust
server memory2.8× less than Rust
fetch()9% faster than Rust
compile2.3× faster than Rust
binary size10.5× smaller than Rust
3.0× faster than Rustbinary_trees: millions of small objects
Tov260 ms
Bun376 ms
Node449 ms
C688 ms
Rust791 ms
Shorter is better.
1.6× faster than Rustsort: 3M floats, with a comparator
Tov45 ms
Rust74 ms
C275 ms
Bun696 ms
Node1175 ms
Shorter is better.
1.7× faster than Rustmap_insert: 2M string keys
Tov284 ms
C476 ms
Rust490 ms
Bun823 ms
Node1178 ms
Shorter is better.
2.1× faster than Rustdispatch: 50M virtual calls
Tov36 ms
C76 ms
Rust77 ms
Bun109 ms
Node191 ms
Shorter is better.
1.6× faster than Ruststrings: format, join, split
Tov143 ms
Bun203 ms
Rust224 ms
C253 ms
Node519 ms
Shorter is better.
7% faster than Rustfib: recursive calls, overflow-checked
Tov416 ms
Rust444 ms
C490 ms
Bun942 ms
Node1485 ms
Shorter is better.
7% faster than Rustnbody: floating point
Tov210 ms
Rust224 ms
C312 ms
Bun512 ms
Node554 ms
Shorter is better.
12% faster than Rustweb server: JSON API, one core
Tov242k req/s
Rust216k req/s
Bun111k req/s
Longer is better.
2.8× less than Rustserver memory: serving that API
Tov1.6 MB
Rust4.4 MB
Bun16.6 MB
Shorter is better.
9% faster than Rustfetch(): 64 requests at once
Tov177k req/s
Rust162k req/s
Bun129k req/s
Node30k req/s
Longer is better.
2.3× faster than Rustcompile: a program to a binary
Tov42 ms
C49 ms
Rust98 ms
Shorter is better.
10.5× smaller than Rustbinary size: a program, stripped
Tov33 KB
C33 KB
Rust351 KB
Shorter is better.
Measured on an Apple M4 Max, and each program prints the same output in every language. Tov checks integer overflow by default; Rust's release builds don't. Method and every result.
×
Trying Tov on Cap
Cap is our open-source screen recorder. We've tried two parts of its video pipeline on Tov: a new encoder written in Tov, and the media server Cap already has, compiled by Tov as it is. Neither runs in Cap's production yet.
A screen encoder, written in Tov
Every Cap recording ends up as H.264 video. We wrote an encoder for it in Tov, tuned for what's on a screen, and compared it with x264 at the same picture quality. The encoder is on GitHub.
5.2×faster than x264, on average
Idle screen8.1×
Code, 4K8.0×
Busy UI, 60 fps7.2×
Slow typing6.9×
Busy UI6.5×
Dark mode6.5×
Code editor6.5×
Slides4.6×
Scrolling text4.4×
Webcam overlay4.2×
Dashboard3.5×
Full-motion video1.5×
46 MB
of memory at its peak (x264: 377)
561 KB
the whole encoder (x264: 1.3 MB)
12 screen recordings at 1080p, on one thread, against x264's veryfast preset. Every result.
The media server, unchanged
Cap's media server is a Bun app (Hono, zod and FFmpeg) that probes uploaded videos and pulls out their audio. Tov runs it without changing a line; the file below is the whole port. Cap still serves it with Bun.