Features
What Ståge actually does, in the order a person meets it. Each row names the mechanism and the file that owns it; the deep pages are Architecture and The engine.
Talking to it
| Feature | What it is | Owner |
|---|---|---|
| Threads, many and cheap | every conversation is a lane; N threads are N views over one socket. Creating one writes a row and a roster entry | Store.kt, wire/Roster.kt |
| Attach with rehydrate | opening a thread asks the host for a snapshot; the bounded per-lane journal answers, and says truncated = true when it cannot reach far enough. Never a silent gap | loom/LaneJournal.kt, Pages.kt |
| The roster is ordered by attention | waiting → working → unread → settled, then activity descending — a daemon-global counter, because seq is per-lane and incomparable | client/Fold.kt, Activity.kt |
| Streaming answers | text, reasoning, tool calls and usage arrive as typed events on one NDJSON stream; heartbeats carry a counter, not a timestamp | wire/Wire.kt, net/NdjsonStream.kt |
| Steering | your hand lands at a safe checkpoint, never between two wires: a sentence is appended to a running turn and consumed at the next boundary | Steer.kt, TurnSteer.kt, MessageStore.kt |
| Abort | stops the turn, keeps what landed; a partial answer is a reading | TurnMachine.kt |
| Approvals | every approval-required call parks until a human answers it on the deck; the request carries the effect class so the decision is informed | ApprovalGate.kt, EventRecorder.kt |
| Search | server-side over thread titles and the text of prompts and answers | Search.kt |
| Model list | what this daemon can run, and whether it may — read from the provider at boot, not remembered | Models.kt, provider/Targets.kt |
| Usage | per-turn sums across rounds, plus the one lane reading that says how full the window is | TurnUsage.kt, TurnUsageAccumulator.kt |
Working with it
| Feature | What it is | Owner |
|---|---|---|
| The plan | the turn's spine, declared by the agent and read by the human; abandoned steps are sent as dropped, never silently omitted | Plan.kt |
| The desk | the thread's workdesk page — the procedure the reader follows while the work runs. A write replaces the whole page; the tool is bound to its lane and refuses to guess one | Workdesk.kt |
| Files, anchored by digest | file_read returns the digest the next file_edit must cite. A stale edit is refused, never applied to content the model did not see | FileTools.kt, FileState.kt |
| Search before read | file_find / file_search are how the model locates; a content search never returns a file body, only path + line + clipped line | FindTools.kt, FileLocate.kt |
| Skills | operational know-how as plain Markdown cards on disk — listed when unsure, loaded whole when the trigger matches. A card dropped in the directory appears on the next call, no restart, nothing to build | SkillTools.kt |
| The command line | bash: one command through /bin/bash -lc, executed exactly as written, in one of three sandbox modes stated at boot (product / bench / off) | Bash.kt, BashSandbox.kt |
| The gates, reachable | dev_check runs one script/check-*.sh inside a dev tree's copy of a repository, and reads the result | DevCheck.kt |
| The rails | bank_search, bank_get, librarian, research — four reads that reach the knowledge store and the open web, each offered only when its credential exists | rail/ |
| Compaction | a long turn's request is a projection of the record, not a summary invented on the way in | Compact.kt, TurnCompaction.kt |
| Continuation | a turn that stopped short walks on instead of dying, with the state it reached named | Continuation.kt |
| Senses | a local, opt-in record of what this daemon actually did — the nervous system, on this machine | Senses.kt |
| The stand-in | what answers when no client is attached, under a posture that decides what it may decide alone | StandIn.kt |
Working with others
| Feature | What it is | Owner |
|---|---|---|
| Delegation | task spawns one bounded child. The child is a contract, not a transcript: it returns a typed envelope — status, taskId, childThreadId, summary, findings with refs, usage — and the parent never sees the child's context | Delegation.kt |
| The deck | the waiting room between a child that cannot move without its parent and the human who answers there. The parent never sees the exchange | Deck.kt |
| The workforce | background workers as one pure state machine: statuses, an admission with budgets, typed outcomes. Every rule the gate names is one boolean in WorkerRules.kt | Workers.kt, WorkerModels.kt, WorkerRules.kt |
| Verification | a verifier reads the artifact, not the builder's summary. One verifier child per task.verify, on its own thread and its own context; the builder never certifies itself | Verification.kt, Verifier.kt |
| Write children | a child that must change a file gets a disposable worktree cut at clean HEAD, worktree-bound instruments only, and returns a diff with a digest. It never applies it and cannot run bash or tests | WriteChild.kt, WriteSession.kt, Worktrees.kt, WriteTools.kt |
| One writer | apply_change re-checks the stored bytes against the digest, the checkout's HEAD against the base, every patched path against a clean status, and git apply --check — then applies once. A second apply is write.already_applied; a moved base is write.stale | WriteTools.kt |
| The tool profile | a prompt may switch a tool off for its own turn; a child's set is a subset of its parent's by construction | ToolProfile.kt |
Owning it
| Feature | What it is | Owner |
|---|---|---|
| The bearer | a token from the environment or a named file. There is deliberately no --token: argv is printed by ps, pgrep -f and any listing over a pipe | Daemon.kt, TokenBoundary.kt |
| The store is ours | SQLite, our schema, migrations from the first commit; one connection, one writer; the bearer is not workspace-readable | Db.kt, Schema.kt, StoreRoles.kt |
| The environment is scrubbed | credential-shaped variables removed by shape, not by a list of names, before a sandboxed command sees the environment | EnvScrub.kt |
| Entitlement, offline | a licence is an Ed25519-signed payload verified without a network, a clock or a key server; the two facts (parseable, entitled) are returned separately | license/ |
| Determinism | no artifact reads a clock or a random number; the same input is byte-identical output, proven by hash | Clock.kt, Digest.kt, check-determinism.sh |
The shape of a turn, end to end
prompt ──► TurnExecutor.begin ──► TurnMachine (named states)
│ │
├─ provider round ───────┤ text / reasoning / tool_use events
│ (SSE, adapters) │
├─ Toolset.catalog ──────┤ what the model may call this turn
├─ ApprovalGate ─────────┤ parked until the human answers
├─ TurnTools ────────────┤ one result per call, in order
└─ EventRecorder ────────┤ persist, then fan out
▼
store + lane bus ──► surface ──► clients