Workbooks
Preface
Literate ProgrammingWhy WebAssembly
Introduction
What is a workbookWhat is a .work fileWhat is the NexusWhat is the work CLIWhat is Workbooks Cloud
The .work language
Anatomy of a .work fileBlock grammarThe prose lane & refsDeclarationsPlacement & languagesKinds referenceElixir in Markdown
Running & deploying
Running the NexusDeploy: local and cloudSecrets & configurationWorkbooks Cloud
Agents & tooling
Get started with your CLI agent
Examples
Learn by example
Blog
Why we author these docs as a workbookOne nexus, many sites

Literate Programming

Most code is written for the machine first and the human second. The machine gets a precise list of instructions; the human gets some comments, a README that went stale months ago, and a wiki nobody opens. Workbooks start from the opposite idea: write for the human first, and let the machine read the same document. That idea is old, it has a name, and it turns out to be exactly what you want in a world where an AI agent is reading and writing your code alongside you.

Knuth

In 1984 Donald Knuth described literate programming: a program should be a piece of literature. You write an explanation a person can follow — in plain prose, in the order that makes sense to a reader — and the runnable code lives right there inside the explanation. The prose and the code are not two artifacts that drift apart. They are one document. The compiler pulls the code out; the reader follows the prose.

Knuth's point was that a program is mostly read, not written, so the writing should be aimed at the reader. A .work file is a literate document in exactly this sense: paragraphs narrate, and do … end blocks run.

The agent turn

Here is what changed. For forty years "the reader" meant another developer. Now the reader is also an agent — and the agent doesn't just read your code, it writes it.

When a machine is editing your codebase, the prose stops being decoration. It becomes the shared context that you and the agent both work from: what this is for, what must stay true, what to leave alone. A literate document co-locates the three things that usually live in three different places —

  • intent (the prose: what you're trying to do and why),
  • execution (the code: what actually runs),
  • review (both at once: you read the reasoning next to the change).

An agent can read the intent, make the change, and you can verify it without hopping between a ticket, a wiki, and a diff. The document is the program is the spec.

What agent harnesses get wrong

Two failure modes show up the moment you point an agent at a real codebase.

Detached docs. The conventional wisdom says code and documentation should live apart — code in the repo, docs in a wiki, design notes in a folder of Markdown files. With a human team you can just barely keep those in sync. With an agent you cannot. The agent reads the code and the scattered docs as separate, often contradictory sources, and every gap between them is a place for the code to go fuzzy. The more you separate the explanation from the thing it explains, the worse the agent does. (We feel this so strongly that the first thing we did when writing these docs was delete a pile of stale Markdown that no longer matched the code — detached docs are a liability, not an asset.)

Lines-of-code buildup. Agents are eager. Ask for a change and you tend to get more code — another file, another layer, another abstraction "to be safe." Do that for a few weeks and you have a thirty-thousand-line codebase where nobody, human or machine, can hold the whole picture, and every edit risks drift somewhere far away. The fix is not a bigger context window. The fix is to force simplification: let the agent write its reasoning next to its code, and a simpler path almost always appears. Prose is where you notice that three functions are really one.

Why Elixir

If you're going to be opinionated about writing less, simpler code, you should be opinionated about the language too. We chose Elixir.

Elixir is easy to read. You can hand a block of it to someone who has never seen it and they'll mostly follow along — it reads close to plain instructions. It's easy to learn and genuinely capable as a server-side language. We picked it for two concrete reasons: its concurrency model (lightweight isolated processes, the BEAM) is a perfect fit for running many workbooks and agents side by side, and it fit the architecture we needed rather than fighting it.

And because a .work block can be written in Elixir, you get the full language — this really is Elixir, written inside a literate document:

def elixir :hello
def greet :hello do
  def run(name), do: "hello, #{name}"
end

We were inspired here by org-mode and MDX — both wonderful ways to mix prose and live content. We wanted to go further: deeper language concepts, and the ability to manage real parts of the codebase directly from these blocks, not just illustrate them.

Radical simplicity

There's a piece we keep coming back to, Radical Simplicity. Its argument: developers are happiest, and software is best, when you stop drowning in glue code and framework churn and get back to solving the actual problem — with one database, one language, as little machinery as possible.

That's the same instinct, pointed at the agent era. Microservices and serverless sprawl don't just cost money at scale; they give an agent a hundred moving parts to keep consistent. Collapse the stack — one engine, one language, the document and its code in one file — and you give both the human and the agent something small enough to actually understand. Literate programming is the writing style. Radical simplicity is the discipline. Together they're how you keep an agent-built codebase from turning into the thing everyone's afraid of.

Why WebAssembly

A literate document that can compile any language needs a common ground to run them on — one target every language can reach. That target is WebAssembly. It's also the thing that lets us run many agents safely at once. Two properties, both load-bearing.

One target for every language

If the promise is "write a block in Elixir, Rust, Zig, Python, Svelte — whatever fits — in one document," you need something built to integrate them all. WebAssembly is exactly that: a small, portable instruction format nearly every language can compile down to. So instead of gluing a dozen runtimes together, every block compiles to the same thing and runs in the same place.

WASI — the WebAssembly System Interface — extends that with a standard, portable way for those modules to touch the outside world: files, clocks, the network. One interface, every language. That's what makes the .work promise of "any supported language" actually buildable.

Isolation is the other half

WebAssembly modules run sealed off — from each other and from the host — by default. A module can't reach memory, files, or network it wasn't explicitly handed. That isn't a nice-to-have here; it's what lets the Nexus run many agents and tenants side by side. Each agent's code runs in its own sandbox, granted only the capabilities it needs.

Sandbox per request, not per second

This changes the economics of running agents. The usual approach keeps a Linux container or VM running per agent, billed per second — even while it does the same operation over and over. WebAssembly sandboxes are cheap enough to spin up per request: instantiate one, do the work, throw it away. For agent workloads — often the same operation many times — that's dramatically cheaper and scales to far more concurrent agents than keeping warm Linux boxes around. It's the same scale-by-RAM story, applied to agents.

The honest caveat: speed

WebAssembly's tradeoff is build time. To run a block it must be compiled to WASM first. So you either pre-build your packages, or — for things compiled dynamically at run time — cache the result after the first run and reuse it. In practice you acknowledge what your users and agents actually do, pre-warm or cache those paths, and the first-run cost is paid once. The only real caveat is speed, and it's addressable: caching, a build queue, and languages that adopt WASM well. Zig has been the smoothest of the bunch — but every supported language works.

Standing on others' work

We don't run our own engine — workbooks run on wasmtime, from the Bytecode Alliance. The portable system interface is WASI, stewarded by the Bytecode Alliance and the W3C. And the wider ecosystem — the people behind WASIX at Wasmer, and the broader WebAssembly community — is what makes "every language, one interface" practical at all. Workbooks is an integration of that work, not a reinvention of it. Credit where it's due.

What is a workbook

A workbook is a folder of .work files (plus any assets they use). That's the whole idea. There's no project scaffold to learn, no framework layout to memorize — a directory of literate documents is the application.

A folder you can hold

Each .work file is a literate document: prose that explains, and do … end blocks that run. Put a few of them in a folder and you have a workbook — a complete piece of software where the explanation and the code live in the same place.

Because it's just files in a folder, a workbook moves the way documents move. You can read it top to bottom, drop it in git, hand it to a teammate, or point an agent at it — and what they get is the whole thing: interface, logic, data shape, and the reasons behind all three.

The four lanes

Everything inside a workbook falls into one of four lanes. You'll meet them properly in Anatomy of a .work file; here's the shape:

  • Prose — the rich text that narrates and carries links between ideas.
  • Declaration — structure with no body: a table, an enum, a record.
  • Code — a runnable block: a function, a server unit, a browser island.
  • Placement — the first word of a block says where it runs: in the browser

(client), on the engine (server), or in a capability sandbox (sandbox).

One file can hold all four. That's the point — the thing people see, the code that makes it work, and the data it stands on don't live in three repositories. They live in one document.

How a workbook ships

You don't assemble a workbook by hand. The work CLI operates on the folder: work weave folds the tree into one shippable artifact, work check resolves the links between files and audits what each block is allowed to do, and work dev watches the folder and re-weaves as you edit.

The folder is how you organize; the woven artifact is what you ship; the Nexus is what runs it.

That woven artifact is HTML — but HTML here is purely a build output, the format the weave emits because it runs everywhere. It isn't something you author or configure, and you never hand-write it. You write .work; weave produces the HTML.

Kits and apps

Most workbooks are one of two things. A kit is meant to be imported and composed by other workbooks — a shared piece other things build on. An app is the leaf you launch — the thing with an interface you actually open. A workbook can be both: an app that also exports a kit for the next one to use.

You don't have to declare this up front to get started. Make a folder, write a .work file, and you already have a workbook.

What is a .work file

A .work file is a literate document: plain prose with runnable blocks mixed in. It is not a new programming language you have to learn. It's a way to write literate programming source that compiles the languages you already use.

Prose narrates, blocks run

Open any .work file and you'll see two things. Paragraphs — ordinary text that explains what's going on. And blocks — each one opens with a header and ends with end:

server elixir :hello
server greet :hello do
  def run(name), do: "hello, #{name}"
end

The prose is for the reader. The block runs. That's the whole format.

The first word names the kind

Every block header reads the same way: <kind> [lang] :name … do … end.

  • The kind comes first — it says what this block is and where it runs

(server, client, sandbox, data, def, agent, resource, and more).

  • The language is optional and names what the body is written in.
  • The :name is how other blocks and your prose refer to it.

So sandbox python :scrape do … end is a block named scrape, running Python, placed in a capability sandbox. The body inside is real Python.

Any language we support

A block's body can be written in any of these — and the work toolchain compiles it:

elixir · rust · zig · c · cpp · python · go · js · ts · svelte · solid

This is the lesson that surprises people: you can write a Svelte component in a .work file. The literate document is the authoring surface; the languages inside are whatever fits the job.

It's a real tree, not text matching

One quiet but important detail: a .work file isn't parsed by guessing with regexes. The do … end block is the delimiter, and a block parses into a real syntax tree — kind, language, name, and body all come from the structure, not a pattern match. The same single parse is shared by everything that reads a workbook: the viewer that highlights it, the tool that maps dependencies between blocks, and the check that audits capabilities. One source of truth, read the same way every time.

Links that hold a workbook together

Inside prose you can reference other things directly: [[backlinks]] to other blocks or pages, :atoms and @types you mention inline, #tags, and work:// links across the workbook. These aren't decoration — they're how the document knits itself into a graph the tools can follow.

Next: see all four lanes laid out in Anatomy of a .work file.

What is the Nexus

The Nexus is the engine you own. It runs your workbooks — on your Mac, on a server, or in the cloud — and connects them to your data, your services, and each other. A workbook is the thing you build; the Nexus is the thing that runs it.

One command, dev is prod

One command starts the Nexus and it serves your workbook. There's no separate "production setup" to graduate to later. The engine you run on your laptop while you're sketching is the same engine that runs the finished product at scale — dev is prod. The prototype becomes the deployment without a rewrite.

You'll find the exact command in Running the Nexus.

Everything runs isolated

Inside the Nexus, every moving part is its own isolated process — each agent, each tenant, each piece of running code. This comes from the BEAM (the runtime behind Elixir, originally built to keep phone networks up), and from running untrusted code inside WebAssembly sandboxes. An agent can build, run, and rebuild inside that boundary without ever reaching your keys, your files, or anyone else's work.

The same engine backs all the lanes of a workbook: the server units that run on it, the agents, the data, sync between clients, and the compile/weave step that turns .work files into running software.

The economics: scale by RAM

Here's the part that matters when you do the math. The usual way to build for scale — microservices and serverless — costs an arm and a leg as you grow: every service is a deployment, every function call is billed, and the glue between them is its own tax.

A workbook on the Nexus scales differently. Because everything runs as lightweight isolated processes inside one engine, you scale by memory — give the Nexus more RAM and it holds more. That is dramatically cheaper than scaling out a fleet of services, and it's a single system to reason about instead of a maze.

And it's multi-tenant from the start. What you prototype deploys as a real multi-tenant production app immediately — there's no "now make it production-grade" phase, because the thing you built on day one already runs the way it will run at scale. Building on the Nexus is a head start precisely because production behaves exactly like your prototype.

This is radical simplicity applied to the runtime: one engine instead of a microservices maze.

Yours to run anywhere

The Nexus is portable — a Mac, a Linux box, a VPS, or the cloud, on your own account. You choose where it runs, and it carries the whole journey with it. See Deploy: local and cloud for the two canonical targets, or Workbooks Cloud if you'd rather we run it for you.

What is the work CLI

work is the one Workbooks command line — author, build, run, deploy. It operates on a workbook folder: it reads the .work tree, folds it into something shippable, and stands up a Nexus to run it. The same work binary runs natively on your machine and inside the wasm agent sandbox, so an agent has the exact tools you do.

The lifecycle

Working on a workbook follows a simple arc, and each step is one command.

You author — write .work files, then work check to make sure every reference resolves and every block only does what it's allowed to. work structure lists the units in the tree so you can see what you've got, and work why, work near, and work wit answer questions about how blocks depend on each other.

You build — work weave folds the whole folder into one self-contained HTML artifact. While you're iterating, work dev watches the folder and re-weaves on every change (and hot-swaps into a running Nexus), so the page updates as you type.

You deploy — work deploy stands up a runtime, local or cloud, from declarative settings that live with the workbook: scaffold them, validate, apply.

That's the loop: author → build → deploy, all over the same folder.

Command reference

The CLI groups its verbs the same way:

author — read & verify .work trees, locally:

  • work check [dir] — resolve references + audit capabilities
  • work structure [dir] — list the units in the tree
  • work why / near / wit :unit — code-graph dependencies + the generated WIT world

build — weave & run:

  • work weave <dir> <out> — weave a tree into one self-contained HTML
  • work graph <dir> <out> — render the code graph as a workbook
  • work dev <dir> — watch & re-weave on change (+ Nexus hot-swap)

deploy — stand up a runtime, local or cloud:

  • work deploy init | validate | apply — scaffold · check · deploy the config
  • work deploy verify | status | down — health · inspect · teardown

platform — identity, contexts, the control plane:

  • work ctx · work nexus <url> — manage targets · point at an engine
  • work login · work whoami — authenticate · show identity

Every verb also accepts --json and --no-color. Run work help to see the live surface.

For the deploy story end to end, see Deploy: local and cloud.

What is Workbooks Cloud

The Nexus is yours to run anywhere. Workbooks Cloud is us running it for you — a managed Nexus with a portal in front of it, so you can ship a workbook without standing up any infrastructure yourself.

A managed Nexus + a portal

Cloud is the same engine described everywhere else in these docs, hosted and kept running on your behalf. The portal is where you manage it: you work inside a Nexus (your isolated unit, roughly an organization — cloud or local) and organize work into workspaces under it. You deploy a workbook, it runs, and the

data, storage, and scaling are handled for you.

When to use Cloud vs running it yourself

Reach for Cloud when you want the workbook live without thinking about servers — the fastest path from work weave to a URL.

Run it yourself when you want the Nexus on your own machine or your own cloud account — see Deploy: local and cloud. Nothing is locked away: the engine is the same in both places, so you can start on Cloud and move to your own infrastructure later, or the reverse.

There's also a middle path — bring your own infrastructure. You can point a managed Nexus at your own storage and database and pay for compute only, instead of handing us the whole stack. Same engine, your backend.

What it costs to reason about

Workbooks Cloud is priced around what you actually use — storage and compute — not per seat, so adding people doesn't change the bill. Because a workbook scales by memory rather than by spinning up a fleet of services, the cost curve stays far flatter than the usual per-service, per-seat stack.

Open the portal

The portal is where you sign in, create a Nexus, and deploy. Start at workbooks.sh.

These docs stay deliberately conceptual about Cloud. The portal's own screens — workspaces, members, billing, storage — are documented in the portal itself, where they're always current.

Anatomy of a .work file

A .work file looks like a document because it is one. As you read down it, the parser sees a simple sequence of pieces: headings, paragraphs, declarations, and runnable blocks. Those pieces sort into four lanes. Once you can spot the four lanes, you can read any workbook.

Here's a small file with all four in it:

Orders

This page tracks orders and shows them in a table.

data elixir :Order
data Order do
  id :int
  total :money
end
server elixir :submit
server place :submit do
  def run(order), do: Store.insert(:orders, order)
end

The heading and the sentence are prose. data Order do … end is a declaration. server place :submit do … end is code, and the word server is its placement.

Prose

Prose is the rich text that explains. Paragraphs, headings, lists — ordinary writing aimed at the reader. Prose isn't a comment bolted onto code; it's a lane of its own, and it can carry live references to other parts of the workbook (see The prose lane & refs).

Declaration

A declaration is structure with no body to run — you're describing a shape, not an action. A data block declares a typed table, a list of atoms declares an enum, a record declares a struct. Declarations are how a workbook states what its data looks like. They get their own page: Declarations.

Code

A code block is something that runs. It opens with a header and ends with end, and the body is real code in a supported language:

def elixir :sum
def total :sum do
  def run(items), do: Enum.sum(items)
end

A function, a server unit, a browser island — if it executes, it's in the code lane. The exact header shape is covered in Block grammar.

Placement

Placement is the quiet fourth lane: the first word of a code block says where it runs. client runs in the browser, server runs on the Nexus, and sandbox runs in a capability-scoped box. Same literate file, but each block knows its home. This is what lets one document describe the browser and the server at once — see Placement & languages.

Why this matters

Four lanes, one file. The thing people see (prose + client), the logic that runs it (server), and the data it stands on (data) aren't scattered across a repo — they sit next to each other, in reading order, in a single document. That's the anatomy that makes a workbook legible to a person and to an agent at the same time.

Block grammar

Every runnable block in a .work file is written the same way:

<kind> [lang] :name … do … end

That one line of grammar covers all of it. Let's take the header apart.

The header, token by token

server elixir :submit
server place :submit do
  def run(order), do: Store.insert(:orders, order)
end

Reading the header server place :submit:

  • kind is the first word — always. It says what the block is and where it

runs: server, client, sandbox, data, def, agent, resource, and others. The parser takes the first token as the kind, no exceptions.

  • lang is optional. If a token is one of the supported languages, it's read

as the language of the body. Leave it out and the block uses its default.

  • :name is the atom that names the block — a token starting with :. It's how

other blocks and your prose refer to this one.

  • Anything else on the line is the block's arguments, passed through to the

body.

So sandbox python :scrape do is kind sandbox, language python, name scrape. And client svelte :widget do is a Svelte browser island named widget.

do … end is the delimiter

A block opens with a non-indented line that ends in do, and closes with a non-indented end. Everything between is the body — real code in the block's language. Plain prose never ends in do, so there's no ambiguity about where a block begins.

Blocks can nest: a do inside the body opens another level, and the parser tracks depth so the matching end closes the block. You don't have to think about it — write the language naturally and it just works.

A name without a colon

Some blocks name themselves the way the host language does — defmodule Workbook do declares something called Workbook. When there's no :atom, the parser takes the bare name. You'll mostly use :name, but both are understood.

Why it's a tree, not a regex

This grammar isn't matched with fragile text patterns. Because do … end is the delimiter and the header tokenizes cleanly, each block parses into a real syntax node — kind, language, name, and body are read straight off the structure. That single parse is shared by everything that touches a workbook: highlighting, the dependency graph, and the capability audit. Write the block once; every tool reads it the same way.

Next, the lane that surrounds the blocks: The prose lane & refs.

The prose lane & refs

Prose is a first-class lane, not a comment. It's where you explain the workbook in ordinary language — and where you wire ideas together with live references the tools understand.

It's just markdown

The prose lane is markdown. Headings, paragraphs, bold, italic, inline code, lists, and links all work the way you'd expect. Write naturally; the document reads like prose because it is prose.

References that mean something

Inside prose you can point at other things directly, and the parser picks these tokens out in order:

  • [[backlinks]] — a link to another block or page in the workbook.
  • work://… — a link across the workbook, addressed by path.
  • :atom — a mention of a named block or value (the same :name a block

declares).

  • @type — a mention of a type.
  • #tag — a tag you can group and find things by.

These aren't styling. They're how a workbook knits itself into a graph: when you mention :submit or write [[Order]], that's a real edge the tools can follow — the same edges work why and work near walk when they answer "what depends on this?"

Refs inside code are examples, not links

There's one sensible rule worth knowing: a reference written inside inline code is treated as a syntax example, not a real reference. So a sentence teaching you about [[backlinks]] (in code font, like this) doesn't accidentally create a link. Real references live in the running prose; quoted ones stay quoted. This very page mentions the syntax constantly without tangling itself in links.

Why prose carries the graph

Because references are part of the prose lane, the explanation and the dependency graph are the same artifact. You don't maintain a separate map of how things relate — you write the relationships into the sentences, and the workbook is the map. That's a big part of why a .work file stays legible to an agent: the intent and the connections are right there in the text it's already reading.

Next: the structure lane — Declarations.

Declarations

A declaration describes a shape, not an action. Where a code block runs, a declaration just states what something is — the data your workbook holds, the options a value can take, the settings it runs under. It's the structure lane.

Typed tables

The most common declaration is a typed table. You name it and list its fields as field :type:

resource elixir :Product
resource Product do
  name :text
  price :money
  stock :int
end

That declares a Product with three typed fields. A resource is the persisted, queryable kind — the Nexus stores its rows and serves them. A data table is declared the same way and renders by default, so a table of values shows up right in the page.

Because the table is declared in the document, your prose can show it: a show Product directive renders the resource's rows as a table, columns drawn straight from the fields you declared.

Enums and records

Two more shapes round it out:

  • An enum is a fixed set of options — declared as a list of atoms, like the

states an order can be in.

  • A record is a struct — a fixed set of named fields grouped into one value,

the way a defstruct works in Elixir.

Both are declarations: you're naming a shape, not running anything.

Attributes

Single settings are declarations too. An attribute is a flat @name value line — configuration stated in the document rather than hidden in an environment variable. Tunable settings live as attributes (read back through the engine's config), which keeps a workbook's configuration visible and reviewable alongside everything else.

The reserved words

A handful of words introduce declarations and directives, so the parser treats them specially rather than as ordinary prose: data, resource, show, query, type, deps, checks, theme, task, user, grant, route, workbook, and nexus. You'll meet most of them where they're used; the thing to remember is that a declaration is always describing, never doing.

Next: where blocks run, and what languages they're written in — Placement & languages.

Placement & languages

Two things about a code block decide how it runs: its placement (where) and its language (what it's written in). Both are right there in the header.

Placement: where a block runs

The kind word at the start of a block puts it somewhere. Three placements cover the ground:

  • client — runs in the browser. A client block is a browser island:

its body is the interface, and it ships as WebAssembly so it runs anywhere a page does. This is the lane that draws what people see.

  • server — runs on the Nexus, the engine,

on the BEAM. This is your backend: logic, data access, the work that shouldn't happen in the browser.

  • sandbox — runs in a capability-scoped box. Guest code is compiled to

WebAssembly and given only the powers it's explicitly granted, so you can run untrusted or risky work without handing it the keys to everything.

Same file, but client draws in the browser while server runs on the engine — which is how one document describes the whole application.

Languages: what a block is written in

A block's body is real code in a supported language, and the work toolchain compiles it:

elixir · rust · zig · c · cpp · python · go · js · ts · svelte · solid

You name the language in the header (sandbox python :scrape, client svelte :counter). Leave it off and the block uses its default. The body is ordinary code — there's no special dialect to learn, just the language you already know, written inside a literate document.

Placement and language are independent

The two choices compose. You can write a sandbox in Rust, a client in Svelte, a server in Elixir — pick the placement for where the work belongs and the language for what fits the job. That independence is the heart of the format: one workbook, many languages, each block running in the right place.

Next, a compact tour of every kind: Kinds reference.

Kinds reference

The kind is the first word of a block. Here's the whole vocabulary in one place, grouped by what each kind is for. The header shape is always the same — <kind> [lang] :name … do … end.

Runnable kinds

These execute. Their placement decides where.

  • client — a browser island. The body is the interface; it ships as

WebAssembly and runs in the page. This is what people see.

  • server — a unit that runs on the Nexus,

on the BEAM. Your backend logic and data access.

  • sandbox — guest code in a capability-scoped box, compiled to WebAssembly

and given only the powers it's granted. For untrusted or risky work.

  • agent — a unit with an agent brain: a server-side block that can reason and

act, not just compute.

  • def — a plain function/unit of logic. defp declares a private one.
  • flow — a multi-step flow: several steps composed into one runnable

sequence.

Structure kinds

These declare shape; nothing runs.

  • data — a typed table that renders by default. Declares fields as

field :type and shows up as a table in the page.

  • resource — a persisted, queryable table. The Nexus stores its rows; a

show directive renders them.

  • record — a struct: a fixed set of named fields grouped into one value.
  • enum — a fixed set of options, written as a list of atoms.

Directives

These tell the page to do something with a declaration.

  • show — render a resource as a table, columns taken from its fields.
  • task — a declared task or job the workbook defines.

Reading a kind you don't recognize

The grammar guarantees you can always parse an unfamiliar block: the first word is the kind, an optional language follows, then :name, then do … end. Even if you haven't met a kind before, you can see what it's called, what language it's in, and where it runs — which is usually enough to follow along.

Elixir in Markdown

When a block runs on the Nexus, it's written in Elixir — and it's the real thing, not a cut-down version. A .work file just lets you write that Elixir inside a literate document. This page is a quick tour so the server and def blocks you see elsewhere read clearly. You don't need to master Elixir to use Workbooks; you mostly need to be able to read it.

It reads like instructions

Here's a function. Even if you've never written Elixir, you can follow it:

def elixir :hello
def greet :hello do
  def run(name), do: "hello, #{name}"
end

def run(name) defines a function that takes a name. The #{name} drops the value into the string. That's most of Elixir's day-to-day shape: small named functions that take values and return new ones.

A few ideas worth knowing

  • Atoms are names that stand for themselves, written with a leading colon:

:ok, :submit, :hello. You've already seen them — a block's :name is an atom. They're how Elixir labels things.

  • Pattern matching lets a function branch by the shape of its input. You can

write two clauses of the same function and the right one runs:

def elixir :check
def status :check do
  def run(:ok), do: "all good"
  def run(:error), do: "something broke"
end
  • The pipe |> passes a value into the next function, so a sequence of steps

reads top to bottom instead of inside out: items |> Enum.filter(...) |> Enum.sum().

  • Immutability — values don't change in place; functions return new values.

This is a big part of why Elixir code is easy to reason about, for you and for an agent: nothing mutates behind your back.

Why Elixir fits here

Two reasons, the same ones from the opener. Concurrency: the BEAM runs huge numbers of tiny isolated processes, which is exactly what a Nexus full of workbooks and agents needs. And readability: the language stays close to plain instructions, so the prose and the code in a workbook speak the same calm dialect.

If you want the full language, elixir-lang.org is the canonical guide. Everything there works inside a .work block — because it is Elixir.

Running the Nexus

The fastest way to see a workbook run is the push-to-live loop: point work dev at your folder and keep editing.

work dev .

work dev weaves the workbook once, then watches the tree. Every time you save a .work file it re-weaves — and when a Nexus is configured, it hot-swaps the change in for an instant reload. You write; the running page updates. That's the whole loop.

What "running" means here

There are two things you might mean by "running a workbook," and they line up with two commands:

  • Iterating — work dev gives you the live edit loop above. This is where you

spend most of your time: write prose and blocks, watch them come alive.

  • Standing up a real runtime — when you want an actual Nexus serving the

workbook (local container or cloud), that's work deploy. Same engine, but a durable runtime rather than a watch loop.

Dev is prod

The reason this matters: the engine behind work dev is the same engine you deploy. There's no separate "production mode" to learn later — the behavior you see locally is the behavior you get at scale. You're not building a toy that you'll rewrite for production; you're building the production thing, just on your own machine first.

When you're ready to put it somewhere durable, head to Deploy: local and cloud.

Deploy: local and cloud

When you want a durable runtime — not just the dev loop — you use work deploy. It stands up a Nexus for your workbook in one of two places: local or cloud, and both run the same engine.

The deploy loop

work deploy init local

init scaffolds the configuration. Then:

  • work deploy validate — check the config is coherent (the same lockouts catch

mistakes before they ship).

  • work deploy apply — stand the runtime up.
  • work deploy verify — health-check it.
  • work deploy status — inspect what's running.
  • work deploy down — tear it back down.

Config lives with your workbook

A deployment is described by a set of declared settings that live with your

workbook — version-controlled and reviewable, part of the source like everything

else. It is not a JSON file off to the side, and it is not a scatter of environment variables. Secrets are the one exception: they're never written into the config, they're injected from the environment at deploy time — see Secrets & configuration. work deploy init scaffolds the settings; you edit them in place.

The settings you declare:

  • engine-place — local or cloud.
  • tenancy — single or multi.
  • storage — local-fs or s3.
  • database — sqlite or postgres.
  • auth — trusted, betterauth, clerk, or oidc.

Local

engine-place="local" runs the Nexus in a Linux container on your machine (via krunvm), and it's cloud-identical — the same image you'd run in production. It defaults to local-fs storage and a sqlite database, with a persistent /data volume that survives restarts. This is the closest you can get to production without leaving your laptop.

Cloud

engine-place="cloud" deploys to Fly, under your own account — you set the provider, app, and region. It defaults to s3-compatible storage (such as Cloudflare R2) and a postgres database, so it holds up across scale-to-zero machines.

Same workbook, either target

Because both targets run the same engine, moving between them is a config change, not a rewrite — flip engine-place and re-apply. Start local, go to cloud, or run both. And if you'd rather not manage any of this yourself, that's exactly what Workbooks Cloud is for.

Secrets & configuration

There are three kinds of "settings," and keeping them in the right home is the most important thing to get right when you deploy. Here is exactly how it works.

Configuration — not secret

Tunable knobs — concurrency, caching, which search provider to use — live in your

workbook's deploy config: the `deploy do … end` block (see
[Deploy: local and cloud](/deploy/deploy-local-and-cloud)). It's version-controlled

and reviewable, part of the source. Not JSON, not environment variables.

Secrets — never in your source

API keys and tokens follow one absolute rule: a secret never lives in your source. Not in a .work file, not in the deploy config, not committed to git — ever. A workbook ships and is version-controlled, so a key inside it would leak the moment you share or push it.

Instead, secrets are provided to the running Nexus through the environment at deploy time, from a secure source:

  • Cloud — Workbooks Cloud includes an encrypted, per-organization secret

manager. Values are encrypted at rest (AES-256-GCM), shown masked, and revealed only on demand. You set a secret once in the portal and it's injected into your runtime; the plaintext is never stored in the open and never logged.

  • Local — the work secret CLI keeps keys in your OS keychain, never in a

file. You declare which secrets a workbook needs in a secrets do … end block (names + descriptions, no values — so an agent has full context and zero exposure), then:

work secret set OPENROUTER_API_KEY # prompts; stored in the macOS Keychain work secret list # shows declared secrets, masked (set / unset) work secret schema # emits a varlock .env.schema for varlock run

At run time the values are injected (via varlock) into the environment for that one command — never written to disk, never pasted into a .work file.

The runtime then reads every secret through a single, audited path, so there is exactly one place that knows which secrets exist and nothing reads a key ad-hoc.

Machine identity — deploy injection

A few values look like config but aren't yours to write — the data mount path, the tenant slot. The real test is author-time vs deploy-time: you can't author your own mount path or which tenant you're deployed into, because the orchestrator assigns those at boot. So they're not configuration (you don't tune them) and not secrets (they're not sensitive) — they're facts the deployer hands the running Nexus. Read them where needed; never try to set them in a .work file.

The one rule to remember

  • Tunable and safe to read → deploy config (.work).
  • A key or token → a secret, injected from a secure store, never in source.
  • Identifies the machine → deploy injection.

When in doubt, ask: would I be comfortable committing this to a public repo? If the answer is no, it's a secret — and it stays out of every file you write.

Workbooks Cloud

Deploying yourself puts the Nexus on your machine or your own cloud account. Workbooks Cloud is the managed alternative — we run the Nexus for you, so deploying is just pushing a

workbook to a runtime that already exists.

If you haven't met it yet, start with the concept: What is Workbooks Cloud.

When to choose it

Reach for Cloud when you want the workbook live without standing up or maintaining any infrastructure — the shortest path from a folder of .work files to a URL. You skip the work deploy apply step entirely; the managed Nexus is already running.

It's the same engine

Nothing about your workbook changes for Cloud. It's the same engine you'd run locally or on Fly, so you can move between managed Cloud and your own infrastructure later without a rewrite. You can even bring your own storage and database and let Cloud handle only the compute.

Get started

Sign in, create a Nexus, and deploy from the portal at workbooks.sh. The portal's own screens stay documented in the portal, where they're always current.

Get started with your CLI agent

Workbooks is built to be driven by a coding agent — Claude Code, Codex, or whatever you use. The whole format exists so an agent can read intent and write code in the same document. This page is the on-ramp for pointing your agent at Workbooks.

The agent has the same tools you do

The work CLI runs natively and inside the wasm agent sandbox. That means your agent isn't working blind against a description of the tools — it has the real work check, work weave, and work dev, sandboxed and safe. It can author a workbook, weave it, and see the result, the same way you would.

These docs are agent-ready

Everything here is built to hand straight to an agent:

  • Copy as Markdown — every page has a button that copies its source, ready to

paste into a chat.

  • Copy as prompt — wrap a page in a ready-made instruction ("using this

Workbooks doc, help me…") with one click.

  • /llms.txt and /llms-full.txt — a machine-readable index and a full-text

dump of the whole site, so an agent can ingest the entire documentation at once.

Point your agent at llms.txt and it has the map; feed it llms-full.txt and it has the whole book.

Install the skill

The packaged way to teach your agent the Workbooks workflow is the skill:

npx skills add workbooks-sh/workbooks.sh

That gives your agent the conventions for authoring .work files, running the CLI, and shipping a workbook — so you can describe what you want and let it build.

How to work with it

The loop is simple: describe the outcome, let the agent author the .work files, then review the rendered workbook the way you'd review any change — reading the prose next to the code. Because the document carries its own intent, you review reasoning and implementation together, in one place.

That's the whole point of everything in these docs: the workbook is legible to you and to the agent at the same time. Start by reading what a workbook is, then put your agent to work.

Learn by example

Every template that ships with Workbooks is a small, complete app — and its whole source is a handful of .work files you can read top to bottom. Below, each one is presented by what it teaches, not by its brand. Pick a card, then read the real literate source behind it in the editor. Nothing is hidden: what you see is exactly what the running app is woven from.

Open the live app ↗

    Why we author these docs as a workbook

    A short post on dogfooding — the docs you're reading are built with the thing they document.

    Most documentation sites are a static-site generator, a theme, a pile of Markdown, and a build pipeline that turns the three into HTML. We don't run any of that. These docs are a workbook: a folder of .work files, woven by the same work CLI we ship to you.

    One file is the table of contents

    The whole site is declared in one composition root — index.work — with an app :docs block. It names the site, picks a theme, and lists the sections and pages in order. The sidebar you see is a direct render of that block. Reorder two lines, the navigation reorders. There is no separate config, no front-matter database, no _sidebar.yml.

    Every page is just prose

    Each page is a sibling .work file. The first heading is its title; the rest is Markdown. Because a page is a real workbook, when one needs to be interactive — like the Examples explorer, which reads live source out of running apps — you drop a client island in between two paragraphs. The same document is both prose and program.

    Why it matters

    We have a rule: if we build it, we use it. The site-mode renderer behind these docs (Nexus.Docs) is the same one your work new docs scaffold runs. When it's not good enough for our own documentation, we feel it first — and fix it before you ever see it.

    One nexus, many sites

    How a single runtime hosts this documentation, the example apps, and your projects — side by side.

    A nexus is a host. Not a host for one workbook — a host for as many as you mount on it. The runtime you're reading this on is serving the documentation, the Search Swarm, Brandnana, and a slide deck at the same time, each at its own path, from one process.

    Each workbook gets a mount

    When the nexus boots over a folder of workbooks, every subfolder becomes a mount at /<name>/. The runtime injects a <base href="/<name>/"> so each app's relative links — live/<source>, data/<Resource>, page routes — resolve under its own prefix and never collide with its neighbours.

    Names, not URLs

    A nexus has a memorable codename (an adjective-animal handle) and a friendly name you give it. You deploy into one by name — work deploy . --nexus <name> — not by copying a URL around. The name is a handle, not a credential: it only resolves to a nexus your account actually owns.

    What this unlocks

    It means a documentation site, a marketing page, and three internal tools can live on one runtime you pay for once — the same shape whether that runtime is on your laptop or in the cloud. The Running the nexus page shows how to bring one up locally in a single command.