v0.0 · specification

The web language
written for agents.

Lith is TypeScript, JSX and Prisma-style queries plus five declarations. An AI agent builds a correct, full-stack web app in about a quarter of the tokens it spends on Next.js.

1 fileper app
2.3ktokens to learn it
0 kBJS on static pages
todos.lithfull-stack
table Todo own {
  title: string(1..200)
  done: boolean = false
  created: Date = now()
}

page "/" {
  const todos = db.todo.findMany({ orderBy: [{ done: "asc" }] })
  return <main>
    <form action={addTodo}>
      <input name="title" /><button>Add</button>
    </form>
    <ul>{todos.map(t => <li>
      <input type="checkbox" checked={t.done}
        onChange={() => setDone(t.id, !t.done)} />
      {t.title}
    </li>)}</ul>
  </main>
}

function addTodo(title: string) {
  db.todo.create({ data: { title } })
}

function setDone(id: int, done: boolean) {
  db.todo.update({ where: { id }, data: { done } })
} 
01 · Benchmarks

Less to write. Less to read. Fewer retries.

Six reference tasks, each written by an expert in Lith and in five popular web stacks: Next.js, SvelteKit, Laravel, Django and Rails. Tokens are counted with the o200k_base tokenizer: what a model actually pays.

2.8–4.2×
fewer tokens to write
195 vs 537–825 per app −64 to −76%
6files
for all six apps
other stacks: 31–37 −81 to −84%
164lines
of source in total
Rails 363 · Next.js 611 −55 to −73%
8/ 8
unseen apps passed
blind model, card only 100%

Mean tokens per task, by stack

Baseline stackLith
Next.jsTypeScript · 32 files
825
4.2×
SvelteKitTypeScript · 34 files
775
4.0×
LaravelPHP · 31 files
615
3.2×
DjangoPython · 32 files
552
2.8×
RailsRuby · 37 files
537
2.8×
Lithv0.0 · 6 files
195
—

Tokens per task

Next.jsLith
Counter islandT02
229
114
2.0×
Todo CRUDT03
1,193
344
3.5×
Login & logoutT04
1,076
177
6.1×
JSON CRUD APIT07
744
38
19.6×
Per-user notesT09
1,078
233
4.6×
Optimistic starT13
630
263
2.4×

Write tokens are the mean of 6 expert solutions per stack. Files and lines count everything written for a task, starting from each framework's standard scaffold. The blind result: a fresh model that had only the 2,349-token Lith card built 4 unseen apps, 2 testers each, 8 of 8 passing after compiler-guided fixes.

02 · Same app, side by side

One file instead of a framework's worth.

A todo app with per-user data, validation, a detail page and a 404. In Lith, the schema, pages, server logic and the server/client split all live together, and the compiler infers which code runs where.

TypeScript / Next.js

  • app/
  • page.tsxserver
  • actions.ts"use server"
  • todo-controls.tsx"use client"
  • todos/[id]/
  • page.tsxserver
  • lib/
  • db.tsprisma
  • prisma/
  • schema.prismaschema
6 files · 146 lines1,193 tok
T03_app.lithLith
table Todo own {
  title: string(1..200)
  done: boolean = false
  created: Date = now()
}

page "/" {
  const todos = db.todo.findMany({ orderBy: [{ done: "asc" }, { created: "asc" }] })
  return <main>
    <h1>Todos</h1>
    <form action={addTodo} class="flex-row">
      <input name="title" />
      <button>Add</button>
    </form>
    <ul>
      {todos.map(t => <li>
        <input type="checkbox" checked={t.done} onChange={() => setDone(t.id, !t.done)} />
        <a href={`/todos/${t.id}`} class={t.done ? "line-through" : ""}>{t.title}</a>
        <button onClick={() => deleteTodo(t.id)} class="text-red-600">Delete</button>
      </li>)}
    </ul>
  </main>
}

page "/todos/:id" (id: int) {
  const todo = db.todo.findUniqueOrThrow({ where: { id } })
  return <main>
    <h1>{todo.title}</h1>
    <p class="text-gray-500">Created {todo.created}</p>
  </main>
}

function addTodo(title: string) {
  db.todo.create({ data: { title } })
}

function setDone(id: int, done: boolean) {
  db.todo.update({ where: { id }, data: { done } })
}

function deleteTodo(id: int) {
  db.todo.delete({ where: { id } })
}
1 file · 43 lines344 tok · 3.5× fewer
03 · Highlights

Every feature had to earn its tokens.

Lith came out of a measured design lab: four competing designs, blind tests with models that had never seen the language, and a rule that every feature must lower total cost.

/01

Looks like TypeScript because it is

The TS 5 TSX grammar plus contextual declarations. Models already know it: 64 blind programs had zero syntax-caused errors.

table page api crud layout state effect auth
/02

Tables with refinement types

Ranges and lengths live in the type and are checked on every write and every form. Migrations for new columns are automatic.

title: string(1..200) views: int(0..) = 0
/03

Ownership as a keyword

own scopes every query, update and delete to the logged-in user. Other users' rows act as missing.

table Note own { body: string }
/04

Inferred server / client split

A function that touches db or me runs on the server; the browser calls it automatically. No "use server", no API glue.

<form action={addComment}>
/05

Forms that handle themselves

Inputs fill typed parameters by name. Every check runs at once, and failures re-show the form with messages next to each field. It works without JS.

fail({ body: "No links" })
/06

A REST API in one line

List, get, create, patch and delete with validation, JSON errors and correct status codes. Plain api routes return any Response.

crud "/api/products" Product
/07

Compiled signals, tiny islands

state and effect replace hooks. Pages without islands ship 0 kB of JavaScript; the client runtime targets 10 kB gzipped.

state starred = task.starred
/08

Errors an agent can apply

lith check returns the first error as JSON with a ready patch, so a fix costs one short round instead of a debugging session.

{ "code": "E0110", "patch": … }
/09

Native to the web

Emits standard JS, HTML and CSS on Node 22. Import any npm package; export .d.ts types so TypeScript can call into Lith.

import { marked } from "marked"
04 · Principles

Built on what the measurements said, not on taste.

Token counts: o200k_base · 6 measured tasks · 5 baseline stacks · blind validation n = 8