Ståge — the book
What this is. The documentation of Ståge: the engine (this repository), the reference client (:cli, in this repo) and the phone client (staage-android, its sibling). It answers three questions — what is Ståge, what is it made of, and what can it do — and then how it is proven, run and shipped.
Every page is Markdown in docs/. The web edition under stage.tnvs.dev is the same files rendered; nothing on the site exists that is not in the tree.
| If you want | Read |
|---|---|
| what Ståge is, and the shape of the whole thing | Architecture |
| the modules and every package in them | The engine |
| every tool the model may call | The tool catalog |
| what Ståge does for a person using it | Features |
| the phone client | The client |
| how it is proven — the gates | Gates and proof |
| how to run, land and publish it | Operations |
| the words we use and mean | Glossary |
In one breath
Ståge is a resident agent you talk to, and the surface you talk to it through. The threads live on a machine you own (the Mac, the engine host); the phone holds views, not sessions.
Mac: :daemon -> store + bus + turn loop <- the threads live HERE
| one NDJSON stream (events, heartbeats) + a ticket for the attached lane
Phone: N lightweight views, windowed transcripts <- the app holds VIEWS
Term: :cli -> the same stream, the same reducer
Three properties are the reason the rest of the design looks the way it does:
- One stream carries all lanes. N threads are N client-side predicates over one socket. No per-lane subscription, no multiplexer.
- No replay, so: sequence, and rehydrate. The protocol assigns a per-lane monotonic
seqand anepoch; on attach the Mac answerssnapshot.requestfrom a bounded per-lane journal, and saystruncated = truerather than pretend when it cannot reach back far enough. - No clock, no randomness, no timestamps inside an artifact. Time is injected (
Clock); the same input produces byte-identical output, and a gate proves it by hash.
The repository in three lines
| Module | What it is |
|---|---|
:core | the wire contract, the canonical codec, the client reducer and its fold, the transport, the scripted fake, and the Loom side (bounded per-lane journal + the Mac surface) |
:daemon | our store (SQLite, our schema), our bus, our turn loop behind Provider/Tool/Approver, the surface that serves the wire, the tool catalog, delegation, the rails, the model adapters |
:cli | the reference client — a terminal that steps the same Reducer the phone steps |
And beside them: proof/ — one document per unit that says what was proven and where the receipts are — probes/ — reproduced questions with a five-week TTL — and script/ — 36 gates, the harnesses, the runners and the two publishers (docs-site.mjs, docs-deploy.sh).
Two registers, and the letter never crosses between them
core/src/main/kotlin/dev/staage/Brand.kt holds both: NAME = "Ståge" (display — the Nordic Å; screens, emblem, prose) and ID = "staage" (identifier — ASCII; directories, packages, wire headers, USER_AGENT). A spelling change to the name is a change to one line, and it never reaches the wire.
The vocabulary is load-bearing
bay, Loom, rail, the go, receipt, the deck, the desk, the shelf — these are not decoration, they name distinct mechanisms. The glossary is the door; read it before arguing about a word.
The docs are published two ways from the same bytes: this Markdown in the repository, and the rendered site at stage.tnvs.dev (see Operations §Publishing).