What is a cell?
A cell is a value with a type, a history, an access policy, dependencies, and formulas. Cells are the unit of computation. Every sheet is a graph of cells. The graph is the program.
Compare to:
- A variable in a program: a name with a value. A cell is a name with a value, a type, a history, a policy, and dependents.
- A file on disk: a static blob. A cell is a live, addressable, reactive value.
- A spreadsheet cell: a value with a formula. A cell is the same, plus history, plus policy, plus composition.
The sheet
A sheet is a collection of cells, with their dependencies. Sheets are written in YAML (or JSON). A sheet is a graph. The engine runs the graph.
id: hello title: "Hello, Quilt" version: 0.1.0 cells: - id: name kind: value value: "world" - id: greeting kind: formula expr: "Hello, " + name + "!" - id: log kind: listener watch: greeting action: "console.log(greeting)"
Three cells: a value cell (the data), a formula cell (the computation), and a listener cell (the side effect). Change name from "world" to "Quilt" and greeting recomputes, and the listener logs it.
8 cell kinds
Every cell is one of these eight kinds. They cover the full space: data, computation, I/O, control flow.
runtime.get and runtime.call.When to use what
| kind | use when |
|---|---|
| value | The data is known at sheet-author time, or changes externally. |
| formula | The value is computed from other cells. Sync, pure. |
| program | The computation needs async, side effects, or runtime state. |
| sensor | You're reading from a polled source (timer, GPIO, BLE). |
| api | You're calling out to an external service. |
| listener | You want to fire on changes (alert, log, write to disk). |
| router | Multiple callers need different outputs based on context. |
| io | You're driving a physical actuator. |
Reactive
The engine is reactive. Change a value, and every cell that depends on it (transitively) recomputes. The graph is a DAG; the engine does a topological sort and evaluates in order.
Memoization is per-context: a formula only re-evaluates if its inputs actually changed. The same formula with the same inputs returns the cached result. This makes large sheets fast.
cells: - id: a kind: value value: 10 - id: b kind: value value: 20 - id: sum kind: formula expr: "a + b" # 30 - id: doubled kind: formula expr: "sum * 2" # 60 - id: status kind: formula expr: 'doubled > 50 ? "big" : "small"' # "big"
Set a = 100. sum recomputes to 120. doubled recomputes to 240. status recomputes to "big". Three recomputations, all automatic.
API
The public API has three layers.
1. Sheet format (YAML/JSON)
Sheets are the canonical form. Any tool that reads/writes sheets can compose with any other tool. Sheets are also human-readable and diff-able.
2. Engine API (TypeScript / Rust / etc.)
import { QuiltEngine } from '@quilt/core'; const engine = new QuiltEngine(); engine.parseSheet(yaml); engine.set('a', 100); const sum = engine.get('sum'); // 120
use quilt_core::{{QuiltEngine, CellKind, CellValue}}; let mut engine = QuiltEngine::new(); engine.parse_sheet(&yaml)?; engine.set("a", CellValue::Int(100))?; let sum = engine.get("sum"); // 120
3. CLI
$ quilt run hello-sheet.yaml $ quilt eval hello-sheet.yaml --cell sum $ quilt fmt hello-sheet.yaml $ quilt doc hello-sheet.yaml --html > docs.html
Your first cell
Three minutes from zero to a working sheet. Open Quilt Live in your browser, no install, no account.
- Open quilt-live.html
- Click "Add cell" → pick "Value". Set id to
myName, value to your name. - Click "Add cell" → pick "Formula". Set id to
greeting, expr to"Hello, " + myName + "!" - Watch
greetingcompute. - Edit
myName. Watchgreetingrecompute. - Click "Save state" to save as a cookie. Click "Download" to get the file with your state baked in.
You just built a reactive system. The engine handled all the propagation. You wrote one formula.
Patterns
Some patterns come up again and again. Here are the canonical ones.
1. Memoization
Formulas cache their result. A formula re-evaluates only when one of its inputs changes. You don't need to add a memoization cell — the engine does it.
2. Derive-and-aggregate
Compute a value for each item, then aggregate. Use formulas for the per-item computations, and another formula for the aggregate. The DAG handles the order.
- id: spend.rent kind: value value: 1800 - id: spend.food kind: value value: 600 - id: spent kind: formula expr: "spend.rent + spend.food + spend.transit + spend.fun"
3. Status from threshold
Compute a value, then map it to a status. The status is just another formula, depends on the value.
- id: percent kind: formula expr: "spent / total" - id: status kind: formula expr: "percent > 0.9 ? 'danger' : (percent > 0.7 ? 'warning' : 'ok')"
4. Watch and react
A listener cell watches another cell and fires when it changes. Use for alerts, logs, side effects.
- id: alert kind: listener watch: status condition: "status == 'danger'" action: "console.log('Over budget!')"
5. Compose sub-graphs
Take a sub-graph (a few cells with dependencies) and import it into a larger sheet. The dependencies get rewired automatically. The composition is just YAML.
Runtime
The same model runs in many places. Pick the runtime that fits your context.
| Runtime | Where | Best for |
|---|---|---|
| quilt | Node.js, browser | The reference. TypeScript-native. |
| quilt-rust | Native, server | Performance, no GC, single binary. |
| quilt-live | Browser (1 file) | Portable, offline, no install. |
| quilt-esp32 | Microcontroller | Sensors, actuators, battery-powered. |
| quilt-agent | Python, Node | LLM agents, multi-agent graphs. |
| quilt-flow | Browser | Visual editor. Drag-and-drop. |
All runtimes share the same sheet format. A sheet written for one runs on all. The model is the API.
Next steps
- Open Quilt Live — the fastest way to feel the model.
- Open Quilt Studio — the visual editor for non-programmers.
- Read the 5-year roadmap for the bigger picture.
- Star the main repo and the runtimes you use.