Fewer tokens. Same JavaScript.

Your AI writes KernScript. You save money.

KernScript is a tiny language your model generates in a fraction of the tokens, then compiles to the exact JavaScript and TypeScript you already run, review and trust.

Every token your AI writes is billed and waited on. KernScript cuts the code it writes by up to 75%: lower cost, lower latency, fewer retries.

KernScript0 tokens
JavaScript0 tokens
import { Hono } from "hono";
import Database from "better-sqlite3";
import { ok, dbAdd, dbAll, defineTable, openDb, readBody, readEnv, reply, serve } from "./server.js";

const env = readEnv(["PORT", "DATABASE_URL"]);
const _db = openDb(env.DATABASE_URL);
const users = defineTable(_db, "users", { columns: [
  { name: "name", type: "str" },
  { name: "email", type: "str", unique: true },
]});
const app = new Hono();
app.get("/users", async (c) => reply(c, ok(dbAll(users))));
app.post("/users", async (c) => {
  const _b = await readBody(c, { name: "str", email: "str" });
  if (!_b.ok) return c.json({ error: "invalid body", fields: _b.errors }, 400);
  const { name, email } = _b.value;
  return reply(c, ok(dbAdd(users, { name, email })));
});
serve(app, env.PORT);

In plain terms

One idea, three steps.

The model writes KernScript

A compact, uniform syntax the AI produces in far fewer tokens than JavaScript or TypeScript.

The compiler does the rest

It type-checks the program, then emits readable JavaScript or strict TypeScript. No build server, no magic.

You ship plain JavaScript

Your team reviews and runs the same code it would have written by hand, on the standard Node ecosystem.

Why it matters

The languages AI writes best are the ones it writes most expensively.

01

JavaScript and TypeScript dominate AI output

They are the most generated languages in coding assistants and agents. Their inefficiency is not a one-off cost. It repeats on every request your product makes.

02

Every token is paid for in cost and latency

await, Promise.all, res.ok, res.json, try and catch, imports, type annotations. All of that is generated, billed and waited on before your program does anything useful.

03

More syntax means more ways to get it wrong

Verbose, irregular grammar gives a model more surface for mistakes, and each fix is another full generation. The real cost is the round-trips to a working program.

See the power

The same program. A fraction of the tokens.

Pick a real program and watch what each language costs the model to write. Same behavior, same audited output, far fewer tokens.

KernScript0
JavaScript0
0%
fewer tokens for the model to write
KernScriptwhat the model writes
(env PORT DATABASE_URL AUTH_TOKEN)
(server api :port PORT
  (db authors  {name: str, email: str unique, created: time auto})
  (db posts    {title: str, body: str, author: num, published: bool, created: time auto})
  (db comments {post: num, text: str, created: time auto})
  (auth :bearer)

  (route POST "/authors" {name email} (ok (db.add authors {name email})))
  (route GET  "/authors" :public      (ok (db.all authors)))

  (route POST "/posts" {title body author published}
    (ok (db.add posts {title body author published})))
  (route GET  "/posts" :public        (ok (db.all posts :order created)))
  (route GET  "/posts/:id" :public
    (let p (db.get posts :id)) (if p (ok p) (err 404 "post not found")))
  (route PUT  "/posts/:id" {title body} (ok (db.set posts :id {title body})))
  (route DELETE "/posts/:id"          (db.del posts :id) (ok))

  (route POST "/posts/:id/comments" {text}
    (ok (db.add comments {post: (toNum id), text})))
  (route GET  "/posts/:id/comments" :public
    (ok (db.find comments {post: (toNum id)})))

  (route GET "/stats" :public
    (ok {authors: (len (db.all authors)), posts: (len (db.all posts)), comments: (len (db.all comments))})))
KernScriptwhat the model writes
(env PORT DATABASE_URL AUTH_TOKEN)
(server api :port PORT
  (db users {name: str, email: str unique, age: num, created: time auto})
  (auth :bearer)
  (route GET "/users" (ok (db.all users :order age)))
  (route GET "/users/:id"
    (let u (db.get users :id)) (if u (ok u) (err 404 "not found")))
  (route POST "/users" {name email age} (ok (db.add users {name email age})))
  (route PUT "/users/:id" {name age} (ok (db.set users :id {name age})))
  (route DELETE "/users/:id" (db.del users :id) (ok)))
KernScriptwhat the model writes
(tool weather
  (desc "Current weather for a city")
  (in  {city: str}) (out {temp: num, desc: str})
  (let d (fetch "https://api.example.com/weather" {q: city}))
  {temp: d.temp, desc: d.text})

(agent helper
  (model "claude-sonnet-4-6")
  (system "Sales team assistant. Reply in Spanish.")
  (tools [weather]) (memory chat :max 30) (permission [:net]))

(let r (ask helper "Weather in Madrid?"))
(print r.text)

Token counts measured with o200k_base against an idiomatic, hand-written JavaScript equivalent.

Try it yourself

Edit KernScript. Watch it compile, live.

This editor runs the real KernScript compiler in your browser. As you type, the compiled JavaScript, the token counts and the type-checker errors all update instantly.

KernScript (editable)0 tokens
Compiled JavaScript0 tokens

The proof

Measured on a real run, not claimed.

0%
fewer output tokens the model wrote, on a real run with Gemini over the same coding tasks

Tokens per program: KernScript versus the hand-written JavaScript it compiles to

These are the real token counts of each program; the headline number is from a real run on Gemini. Results vary by model and session: the manual rides every request, so on a short session or a terse model that fixed cost can outweigh the savings. Always cache it as a stable prompt prefix, so it is paid once per session, not on every call.

See the code

The model writes KernScript. Your team reviews the JavaScript it compiles to.

KernScript 197 versus hand-written JS 1073 82% fewer
KernScriptthe model writes
(tool weather
  (desc "Current weather for a city")
  (in  {city: str}) (out {temp: num, desc: str})
  (let d (fetch "https://api.example.com/weather" {q: city}))
  {temp: d.temp, desc: d.text})

(agent helper
  (model "claude-sonnet-4-6")
  (tools [weather]) (permission [:net]))

(let r (ask helper "Weather in Madrid?"))
(print r.text)
JavaScriptyou review and ship
import http from 'node:http';
import Database from 'better-sqlite3';
const db = new Database(process.env.DATABASE_URL || ':memory:');
db.exec(`CREATE TABLE IF NOT EXISTS authors (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, email TEXT UNIQUE, created TEXT);
CREATE TABLE IF NOT EXISTS posts (id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT, body TEXT, author REAL, published INTEGER, created TEXT);
CREATE TABLE IF NOT EXISTS comments (id INTEGER PRIMARY KEY AUTOINCREMENT, post REAL, text TEXT, created TEXT);`);
const TOKEN = process.env.AUTH_TOKEN;
const json = (res, code, obj) => { res.writeHead(code, { 'content-type': 'application/json' }); res.end(obj === undefined ? '' : JSON.stringify(obj)); };
const readBody = async (req) => { const c = []; for await (const x of req) c.push(x); return c.length ? JSON.parse(Buffer.concat(c).toString()) : {}; };
const authed = (req) => { const m = (req.headers['authorization'] || '').match(/^Bearer\s+(.+)$/i); return !!m && m[1] === TOKEN; };
const row = (t, id) => db.prepare('SELECT * FROM ' + t + ' WHERE id = ?').get(id);
const now = () => new Date().toISOString();
const server = http.createServer(async (req, res) => {
  const p = new URL(req.url, 'http://x').pathname;
  const method = req.method;
  if ((method === 'POST' || method === 'PUT' || method === 'DELETE') && !authed(req)) return json(res, 401, { error: 'unauthorized' });
  const mComments = p.match(/^\/posts\/(\d+)\/comments$/);
  const mPost = p.match(/^\/posts\/(\d+)$/);
  if (method === 'POST' && p === '/authors') {
    const b = await readBody(req);
    if (typeof b.name !== 'string' || typeof b.email !== 'string') return json(res, 400, { error: 'invalid' });
    const i = db.prepare('INSERT INTO authors (name, email, created) VALUES (?, ?, ?)').run(b.name, b.email, now());
    return json(res, 200, row('authors', i.lastInsertRowid));
  }
  if (method === 'GET' && p === '/authors') return json(res, 200, db.prepare('SELECT * FROM authors').all());
  if (method === 'POST' && p === '/posts') {
    const b = await readBody(req);
    if (typeof b.title !== 'string' || typeof b.body !== 'string' || typeof b.author !== 'number' || typeof b.published !== 'boolean') return json(res, 400, { error: 'invalid' });
    const i = db.prepare('INSERT INTO posts (title, body, author, published, created) VALUES (?, ?, ?, ?, ?)').run(b.title, b.body, b.author, b.published ? 1 : 0, now());
    return json(res, 200, row('posts', i.lastInsertRowid));
  }
  if (method === 'GET' && p === '/posts') return json(res, 200, db.prepare('SELECT * FROM posts ORDER BY created').all());
  if (method === 'GET' && mPost) { const r = row('posts', mPost[1]); return r ? json(res, 200, r) : json(res, 404, { error: 'post not found' }); }
  if (method === 'PUT' && mPost) {
    const b = await readBody(req);
    if (typeof b.title !== 'string' || typeof b.body !== 'string') return json(res, 400, { error: 'invalid' });
    db.prepare('UPDATE posts SET title = ?, body = ? WHERE id = ?').run(b.title, b.body, mPost[1]);
    return json(res, 200, row('posts', mPost[1]));
  }
  if (method === 'DELETE' && mPost) { db.prepare('DELETE FROM posts WHERE id = ?').run(mPost[1]); return json(res, 204, undefined); }
  if (method === 'POST' && mComments) {
    const b = await readBody(req);
    if (typeof b.text !== 'string') return json(res, 400, { error: 'invalid' });
    const i = db.prepare('INSERT INTO comments (post, text, created) VALUES (?, ?, ?)').run(Number(mComments[1]), b.text, now());
    return json(res, 200, row('comments', i.lastInsertRowid));
  }
  if (method === 'GET' && mComments) return json(res, 200, db.prepare('SELECT * FROM comments WHERE post = ?').all(Number(mComments[1])));
  if (method === 'GET' && p === '/stats') return json(res, 200, { authors: db.prepare('SELECT COUNT(*) n FROM authors').get().n, posts: db.prepare('SELECT COUNT(*) n FROM posts').get().n, comments: db.prepare('SELECT COUNT(*) n FROM comments').get().n });
  if (method === 'GET' && p === '/health') return json(res, 200, { status: 'up' });
  json(res, 404, { error: 'not found' });
});
server.listen(Number(process.env.PORT), () => console.log(`serving on http://localhost:${server.address().port}`));
KernScript 240 versus hand-written JS 777 69% fewer
KernScriptthe model writes
(env PORT DATABASE_URL AUTH_TOKEN)
(server api :port PORT
  (db users {name: str, email: str unique, created: time auto})
  (auth :bearer)
  (route GET "/users"
    (ok (db.all users :order name :limit 20)))
  (route POST "/users" {name email}
    (ok (db.add users {name email}))))
JavaScriptruns on Hono and SQLite
const app = new Hono();
const users = defineTable(db, "users", { columns: [
  { name: "name", type: "str" },
  { name: "email", type: "str", unique: true },
  { name: "created", type: "time", auto: true },
] });
const bearer = requireBearer(env.AUTH_TOKEN);

app.get("/users", bearer, async (c) =>
  reply(c, ok(dbAll(users, { order: "name", limit: 20 }))));

app.post("/users", bearer, async (c) => {
  const _b = await readBody(c, { name: "str", email: "str" });
  if (!_b.ok)
    return c.json({ error: "invalid body", fields: _b.errors }, 400);
  const { name, email } = _b.value;
  return reply(c, ok(dbAdd(users, { name, email })));
});
serve(app, env.PORT);
KernScript 382 versus hand-written JS 1186 68% fewer
KernScriptthe model writes
(env PORT DATABASE_URL AUTH_TOKEN)
(server api :port PORT
  (db authors  {name: str, email: str unique, created: time auto})
  (db posts    {title: str, body: str, author: num, published: bool, created: time auto})
  (db comments {post: num, text: str, created: time auto})
  (auth :bearer)

  (route POST "/authors" {name email} (ok (db.add authors {name email})))
  (route GET  "/authors" :public      (ok (db.all authors)))

  (route POST "/posts" {title body author published}
    (ok (db.add posts {title body author published})))
  (route GET  "/posts" :public        (ok (db.all posts :order created)))
  (route GET  "/posts/:id" :public
    (let p (db.get posts :id)) (if p (ok p) (err 404 "post not found")))
  (route PUT  "/posts/:id" {title body} (ok (db.set posts :id {title body})))
  (route DELETE "/posts/:id"          (db.del posts :id) (ok))

  (route POST "/posts/:id/comments" {text}
    (ok (db.add comments {post: (toNum id), text})))
  (route GET  "/posts/:id/comments" :public
    (ok (db.find comments {post: (toNum id)})))

  (route GET "/stats" :public
    (ok {authors: (len (db.all authors)), posts: (len (db.all posts)), comments: (len (db.all comments))})))
JavaScriptyou review and ship
import http from "node:http";
import Database from "better-sqlite3";
const db = new Database(process.env.DATABASE_URL || ":memory:");
db.exec(`CREATE TABLE authors (...);
         CREATE TABLE posts (...);
         CREATE TABLE comments (...);`);
const authed = (req) => /Bearer (.+)/.test(req.headers.authorization || "");
const server = http.createServer(async (req, res) => {
  // bearer-gate every write, read + validate JSON bodies, hand-match
  // /posts/:id, /posts/:id/comments and /stats, run the SQL, pick the
  // right status code for each case, 404s, 401s...
  // ~50 lines of routing, validation and SQL for the same three tables.
});
server.listen(Number(process.env.PORT));

Tutorials

From install to an AI agent, in five short steps.

Drop the cheat sheet into a model's context and it writes KernScript without ever having seen it. Or learn it yourself in a few minutes, since every concept fits on one screen.

1

Install and run your first program

Clone the repo, install once, and run a .ks file. The CLI type-checks, transpiles in memory and executes. There is no separate build step.

git clone https://github.com/lopezonchain/kernscript
cd kernscript && npm install
npx ks run hello.ks
(let who "world")
(print "Hello, {who}!")        ; string interpolation is built in
2

Values, lists and pipelines

Thread a value through a series of transformations with the pipeline form. No intermediate variables, and no nested calls to read from the inside out.

(let orders [{total: 120 paid: true} {total: 80 paid: false} {total: 200 paid: true}])
(let revenue (-> orders
  (filter (fn [o] o.paid))     ; keep only the paid orders
  (map :total)                 ; pluck the total field
  (sum)))                      ; add them up, result is 320
(print "revenue: {revenue}")
3

Fetch data and handle errors as values

fetch is built in. It parses JSON and throws on a non-2xx response. Wrap anything that can fail in try, and return a Result instead of throwing across your whole app.

(fn user-name [id]
  (try
    (let u (fetch "https://api.example.com/users/{id}"))
    (ok u.name)
    (catch e (err :fetch e.message))))

(match (user-name 7)
  {ok: true}  (print "found the user")
  _           (print "lookup failed"))
4

A REST API with a database

A single server form gives you a Hono app on SQLite, with a typed schema, automatic body validation, bearer auth and additive migrations. No glue code to write.

(env PORT DATABASE_URL AUTH_TOKEN)
(server api :port PORT
  (db tasks {title: str, done: bool, created: time auto})
  (auth :bearer)
  (route GET  "/tasks" (ok (db.all tasks :order created :desc)))
  (route POST "/tasks" {title done} (ok (db.add tasks {title done})))
  (route GET  "/tasks/:id"
    (let t (db.get tasks :id))
    (if t (ok t) (err 404 "not found"))))
5

An AI agent with a tool

Tools, agents and the tool-calling loop are part of the language. Inputs and outputs are validated, permissions are enforced, and ask drives the whole loop for you across Claude, GPT, Gemini or Ollama.

(tool add
  (desc "Add two numbers")
  (in {a: num, b: num}) (out {sum: num})
  {sum: (+ a b)})

(agent helper
  (model "claude-sonnet-4-6")
  (tools [add]) (permission []))

(let r (ask helper "What is 19 + 23?"))
(print r.text)

What is in the box

Small to write. Complete enough to ship to production.

( )

Uniform syntax

Everything is one shape, (form args). Lists, records and optional commas. There is nothing to memorize.

Async, inferred

The compiler infers async: it awaits the calls that suspend and leaves pure code synchronous. You never write await, then or Promise.all.

Pipelines and match

Thread values with a pipeline, branch with match, and handle failure with Result.

Type checker

Type inference with zero annotations, and one-line errors a model fixes in a single pass.

Server, database and auth

A full CRUD API on Hono and SQLite with validation, bearer auth and cron, in under 30 lines.

Agents and MCP

Tools, agents and the tool-calling loop across Claude, GPT, Gemini and Ollama.

Modules, npm and the browser

Split across .ks files with cross-file type checking, import any npm package, and target the browser.

🛡

Hardened runtime

An SSRF-guarded fetch, parameterized SQL, constant-time auth, and capability permissions.

The model writes the small KernScript. You ship the audited JavaScript.