# Reemoat > **The rename to `reemoat` is complete, and every `remoslop` left in this tree > names something that is not this product.** One of them is load-bearing: > `LEGACY_STORAGE` in `packages/web/src/cp.ts` holds `remoslop.credential` and > `remoslop.apiKey`, which name what is **already sitting in somebody's browser**, > so that a rename does not sign the fleet out. They are read once, adopted and > swept, and they get deleted rather than updated once no tab has been signed in > since before the rename — `webcheck` pins both halves. A blanket rename caught > them once and the failure was total: `setSession` writes the credential and then > sweeps that list, so with the same string in both it deleted what it had just > written. The rest are history rather than behaviour: Q7.71 records what the > migration had to move by hand, and a few comments quote measured paths from the > machine this was developed on (`/Users/rends/remoslop…`), which are still the > paths that were measured. **None of them may be swept by a rename.** A daemon that owns coding-agent sessions and exposes them over HTTP + WS, a control plane that issues identity and relays requests to them, and a desktop app — the same client a phone loads in a browser — that supervises all of it. *Relays requests* rather than *every request*: the app reaches a daemon on its own computer over loopback, which is the one exception and is `.claude/rules/relay.md`'s. **One person, one machine, many agents, and no sandbox.** The daemon runs on your own machine and spawns agents as children of itself, as you — the same thing that happens when you type `claude` in a terminal, except that you can be somewhere else. Several at once, each in its own git worktree, each able to bring up a dev server and run what it just wrote. Multi-user moved to the control plane: several people, each with their own machine and a grant on it. The daemon accepts any token whose `aud` is its own machine id and stops asking who the subject is. It spawns `claude`, `kimi`, `codex`, `opencode`, `grok` or `cursor` over ACP (Agent Client Protocol), normalizes all six into one event union, and puts that behind a network layer built on the assumption that **clients are unreliable**: a laptop lid closes, a phone drops to LTE, a tab is discarded. The daemon is the source of truth and the agent must never notice a client leaving. Node >= 24, ESM, TypeScript strict. Everything in `src/`, `scripts/` and `packages/control-plane` runs straight off `tsx` with no build step. `packages/web` is bundled by Vite **twice, into two directories**: `dist` is the app and goes into the Reemoat binary, `dist-gate` is sign-up, the mailed-link screens, the legal documents and the handoff page, and goes into the control plane's image. **The app carries no gate screen** — it links out to the control plane's own, so there is one sign-up form in the fleet rather than two; `GateCard` is the one shared box and `webcheck` walks both import closures to hold that line. The Authority serves the second at **nine addresses** and the app at none — a closed list rather than an SPA fallback, so the product is not in the image to be served — and **there is no variable that would serve it**: a browser holds no device key, so it could load the app and reach no machine at all. Q4.118, Q1.649, `docs/AUTHORITY.md`. **No test framework.** `typecheck`, `protocolcheck`, `authcheck`, `daemoncheck`, `relaycheck`, `webcheck`, `nativecheck`, `pincheck`, `deploycheck`, `docscheck`, `imagecheck` and `harness` are the whole automated safety net, and they are drivers rather than unit tests on purpose. Ten run offline in one process with no fleet, no agent and no deploy — `docscheck` is the one whose subject is prose: it holds this file to a budget, because the last time it was cut nothing checked the result and it was larger six days later. `nativecheck`'s subject is a *shell configuration*, which is the one thing no other driver can see: `typecheck` compiles no Rust, `webcheck` is scoped to `packages/web`, and the `cargo` build that would catch the rest is a separate job. `protocolcheck` is the newest and the only one whose subject is a specification **somebody else wrote** — the Noise handshake, driven byte-for-byte against the published cross-implementation vectors in both roles, because an implementation that only ever talks to itself round-trips perfectly while interoperating with nothing, and would go on doing so through a nonce written the wrong way round. `harness` drives a real agent and needs a login CI cannot hold. `imagecheck` builds and starts a container, so it is a separate CI job — and it earns that: the control plane reaches into the repository root for a file list written down **twice**, in `.dockerignore` and in `deploy/docker/Dockerfile`'s COPY lines, and an import missing from either passes `typecheck` and all nine other drivers while breaking only the image. Measured while adding `src/http.ts`: missing from `.dockerignore` it fails at COPY with `"/src/http.ts": not found` (the build context never carried it), and missing from the Dockerfile it fails later with `Cannot find module`. Adding an import means editing both. Deploying is a *separate* act from checking, and nothing does it on a push. > **Why any of this is the way it is lives in `docs/DECISIONS.md`** — 1133 entries > as question → decision, with the measurement behind each and the alternatives > that were tried and taken back out. **The count is asserted by `docscheck` > rather than restated here from memory**, which is the whole reason it is right: > it said 453, then 294, because it was re-derived by hand from `### Q…` alone > while Q3 and Q5 — the two largest groups — sit at `####`. This file states rules > as they stand and names the symbol that enforces each; that one answers *why*, > and is where to look before reversing anything. ## Commands ```bash pnpm typecheck # tsc --noEmit, both packages pnpm protocolcheck # packages/protocol: the Noise handshake against the published # cross-implementation vectors, in both roles, with the ephemerals # pinned — plus the two things the specification says nothing about, # the reserved top of the nonce range and this repository's own frame # table. The only driver whose subject somebody else wrote pnpm authcheck # token verification and enrollment pnpm daemoncheck # the daemon's HTTP surface and durable state: routes, # the v6 migration, the login pty, the WS, subagent lineage, # permissions, stopping a turn, the SQLite log, changes/diff, # uploads, installing a harness on a press, and letting an idle # agent go — the refusals one # at a time, that a released session reads as neither # stopped nor interrupted, and the sweep on a fake clock — # and the bounds an agent can push against, # all of them refusals now: a permission's title and # options weighed as one 8 KiB thing rather than clipped, # a form's prose carried whole against one 32 KiB # backstop, `locations` in the # byte accounting, a `.git` reached through a symlink, # and an upgrade target `new URL` refuses. Plus importing # a codebase: both archive readers against archives it # builds itself, every member there is a refusal for, and # the second half of each — that nothing at all was created. # Plus plugins: every manifest refusal, the scope gate swept over # the whole method table, the seven routes under both axes, and the # lifecycle a real `fork` cannot be made to walk — a start that # never completes, an answer that never comes, an exhausted # restart budget — plus what an update keeps and what a failed # one puts back, and the two surfaces a plugin draws on: every # block and every field kind on each, always in pairs, and the # same bytes answered to both over HTTP. Plus editing an # assembled agent: every refusal on `PATCH` in two halves, one # malformed-body table driven through `POST` and `PATCH` and # compared — and, against a **real** store, that a second save # replaces the row and leaves its age alone, which the route # section cannot reach because it stands a `Map` in, and a # `Map` upserts. Plus a message sent while the # agent works: two stubs, one advertising steering and one not, # plus the one that advertises and then refuses — pinning that # the message is never lost and never doubled, no second prompt # on the wire and no second `prompt` event on delivery pnpm relaycheck # framing, flow control, authorization, tunnel supersede, # tunnel presence as a row and the relay's own health route, # live-row revocation, the control plane's routes, # signing in, sessions, passwords, the login throttle, # guessing under concurrency, owned machines, API-key # revocation, proving your own account — and the whole # mail half: the SMTP client against a fake server *and* # over a real TLS socket, the outbox, a message as bytes, # an address as a string, settings and where each value # came from, registration, recovery and the mail that # carries them — plus what a stolen session may not do, # how much of x-forwarded-for is believed, a daemon # that takes a stream and never answers — and the machine # limit: which machine a lowering switches off, that # raising it back un-suspends with the same token, that # revoking one promotes the next, that a same-millisecond # tie still ranks, and that unset still means fifty — # at the four places that enforce it rather than only in # the function: the dial (403, and not the 401 that # invites a pointless re-enrollment), `POST /v1/tokens` # *and its after-the-grant ordering*, the enrollment # code, and the listing's own `overLimit`/`ownerDisabled` # pair. Plus the provisioning key: that a refused # provision changes nothing, that a name two accounts # share bar case is refused rather than guessed, and # that guessing the key is counted and blocked pnpm webcheck # packages/web: the cursor, rotation, replay, the tail, # the credential, the settings routes, admin visibility, # the password rules, the gate (registration, confirmation # and recovery), server settings and how stuck somebody is, # who owns Escape, the machine limit's three states and # the sentence each draws — and the enrollment paste, # against cpctl's own function body. Plus the transcript's # two newest rules: what a diff says (hunks, the counts, # the LCS bound, and the refusal to draw one over an event # the log clipped) and which rows a run may stand for — # never a permission, never a subagent, never one call. # And the import flow: that an old daemon is known by the # shape of its refusal rather than by its version, and that # the export skill asks for what the extractor accepts. And # where a plugin's settings are — their own screen, scoped to # the machines somebody ticked and carrying them in the URL, # narrowed to three controls, drawn with this app's own picker # rather than the platform's — read off the files that place # them, since nothing typed can hold a placement. Plus the # machine table those machines are ticked on: what one row may # offer and what the bar may, both swept; that the bar sits # outside the scroller and the scroller ends no scroll chain; # and what N settings panes add up to when they disagree. # Then: that a transcript missing its beginning # *says so* — `transcriptNotice` as a total partition over # 720 states, its pair with `loadStop`, and the one string # feeding both the line and the live region — plus a retry # schedule long enough to outlast a daemon redialling. # And a repair rather than a subject: **two** # sentences for a pairing failure where one was pinned — a # spelling and a protocol may not read alike, and the check # asserting they must is what kept the false one shipping. # The newest are four, and the sharpest is the one that was # green over a card nobody could reach: the plan-mode # curation was measured against an adapter two versions # back, so its fixture was the only request it still # matched. Now every shape the *pinned* adapter builds, # each with the order and the words this app puts on it, # plus the mixture of two that must match neither. Beside # it: that a `/clear` leaves the command and the words and # nothing offers the conversation back, asserted as an # absence on three files; that the parked card measures # itself and the transcript reserves the room, pinned on # both sides because either half alone is silent; and that # a question says how many of its answers you may pick — # a box or a circle, with the role claimed only where a # button keeps it, on both halves of one form. # Newest again, and it is about a fact this app was # asserting against itself: that a plan card offers the # two grants and *which* two it drops, by name, so # dropping a third cannot pass as "still two buttons". # That driver was green for months over code nothing # could reach. # And the Telegram-shaped shell: that the rail is two # columns on one `--rail-w` with the bounds asserted by # subtraction, that the machine strip is one data source # drawn on two axes and the vertical one carries none of the # horizontal one's three cues, that the menu drawer covers # the app rather than docking beside it — `"sheet"`, never # `"menu"`, or `j`/`k` walk the list behind it — and that # the version is read from the manifest through a `typeof` # guard a Vite-less import survives. # Newest: devices — the three shapes a pasted mailed link may # take and everything it refuses locally rather than sending, # that signing out **keeps** the device while a retirement # gives it up, that the two storage keys are different names # and neither is a swept legacy one, and that the id is kept # in the shell's config rather than its keyring — read off # both languages, since nothing typed can hold it. # And the dark palette: a twin for every token and the # contrast both owe, computed in both; no colour outside # the palette; one key for the two writers of `data-theme`; # and the drawer's switch as its last row pnpm nativecheck # packages/native: the Boot payload's keys against NativeBoot's, # which is the census a missing `serde(rename)` slips past in # five checkers at once; that the frontend is a path inside the # binary and not a URL, that OS file drops still reach the # webview (the assertion with no other symptom), that the # capability list is empty and no plugin the Rust side drives # is reachable from the page, the command census in both # directions, the scheme allowlist against `links.ts`'s own, # the CSP's directives against the ones the control plane # sends, the two version fields that are **not** release # sites, and the one workspace line three deploy behaviours # depend on. Offline, and deliberately **no cargo** pnpm pincheck # every place a version is written down. The agents': # three copies each, the claude adapter's patch key a # fourth, and the adapters actually installed. # And six of this release's seven — the root and all three # manifests, `src/version.ts` and the CHANGELOG's newest # dated heading; `app.ts`'s VERSION is relaycheck's, off the # served response. **None of them says a bump happened** — # they agree with each other, never with a tag — plus # SOURCE_URL against the repository package.json names, # which is the §13 offer's other half and was checked nowhere. # And one number that is not a version: the API-key ceiling, # written once on each side of the wire pnpm deploycheck # deploy/: quoting, env files, PATH, a unit for both init systems, # and RELAY_INPUTS against the relay entry's own import closure pnpm docscheck # the documentation, held to what it claims about itself: this # file's size budget, that every Q. citation resolves, # that every symbol DECISIONS.md cites still greps to source, # that its index count is the real one, and that every # .claude/rules/ glob still matches a file — the one failure # here with no symptom, since a rule scoped to a renamed path # silently stops arriving pnpm imagecheck # the control plane's image, and both services it runs. # NOT offline: needs docker + network deploy/ci-freshness.sh # the adapter pins against the npm registry, weekly in CI: how # far behind each is. Report-only; exits non-zero only for a pin # the registry no longer serves. NOT offline; changes nothing pnpm daemon # needs REEMOAT_TOKEN; see .env.example deploy/agents.sh --check # what the agent CLIs would install or refresh, changing nothing. # `--only ` is what a press in the app runs; # `--refresh-only` is what `deploy.sh` and the daily timer run. # **Nothing installs a harness but a press** curl -fsSL 'https:///install.sh' | sh # a machine, from nothing to enrolled. # `deploy/bootstrap.sh` served by `GET /install.sh` with the # requesting origin quoted in — so an instance hands out an # installer that joins *it*, and a checkout copy refuses rather # than picking one. Hands off to `install.sh daemon # --non-interactive` once the env file exists pnpm harness --agent codex --prompt "hi" # drives a bare Session locally, with no daemon pnpm cp # the control plane + relay in one process (own package, own SQLite). # REEMOAT_CP_RELAY_MODE=embedded is the default and is what this is; # the deployed shape is two containers, see compose.sh below pnpm web # the web UI in dev; Vite proxies /v1 to the control plane pnpm web:build # → packages/web/dist, the whole app — for `pnpm native:build` to # compile into the binary. Nothing serves it over HTTP: the control # plane has no switch for one and the image never carried it pnpm --dir packages/web build:gate # → packages/web/dist-gate, the nine addresses a browser may reach. # This one IS in the image and is served with no switch: /confirm, # /reset and /verify are opened by a mail client and have nowhere # else to land pnpm --dir packages/native install # the native shell's own node_modules. **The root install does # not do this** — `packages/native` is under `packages/` and # excluded from the workspace, so the Tauri CLI never lands on # a daemon host and a Tauri bump never moves the root lockfile pnpm native # tauri dev: Vite on 5173, the window over it pnpm native:build # → a macOS .app with packages/web inside the binary. # REEMOAT_DEFAULT_SERVER is the only build-time input, and no # file here gives it a value — release.yml forwards a repository # variable — so a fork inherits no address; docs/NATIVE.md. **No .dmg**: # `bundle.targets` is `["app"]`, because tauri's `bundle_dmg.sh` # drives Finder over AppleScript and times out anywhere nobody is # logged in — `docs/NATIVE.md` has the measurement and the one-line # `--bundles dmg` escape. # Ad-hoc signed: no identity is committed, and none is needed # for a development build. arm64 only on a checkout with no # rustup; `docs/NATIVE.md` has the rest ``` State lives in one SQLite file per daemon (`REEMOAT_DB`, default `$REEMOAT_HOME/reemoat.db`; `REEMOAT_HOME` is `~/.reemoat` unless set) and each session gets its own git worktree under that root's `worktrees/`. One database is one machine for one account on one server, so the desktop app runs one daemon per account it holds — a server's first account keeps `~/.reemoat` (for the server its `daemon.env` names) or `~/.reemoat/servers//`, and each further account on that server gets `~/.reemoat/servers/@/` — starts every one that is set up when it launches, and stops them all when it quits (Q7.148, Q7.149). A daemon restart leaves every session it did not stop on purpose `interrupted` and puts an agent back on each by itself — see `.claude/rules/daemon-sessions.md`. **Traffic to a remote daemon is end-to-end encrypted and there is no other mode.** The app and the daemon run `Noise_IK` between themselves; the relay authorizes the connection and then carries bytes it holds no key for. The app's static is a **device key** in the OS keyring, the daemon's is a **machine key** it announces on its dial, and every capability names the device it was minted for — so one stolen off the wire is worth nothing elsewhere. `RELAY_PROTOCOL_MIN_VERSION` was raised past every build that spoke plaintext, which is a deliberate flag day: a daemon that has not been updated stops dialling in until `deploy/deploy.sh` runs on its host. `.claude/rules/e2ee.md` is the area; Q7.37 and Q7.143 are the argument. ⚠ It removes the **relay** from the trusted path and defends against nothing else — the Authority still mints every capability and still ships the client. **The daemon's config is env only** (`.env.example`; the client's `REEMOAT_URL`/`REEMOAT_MACHINE` are printed by `pnpm client` with their live values). `REEMOAT_TOKEN` is required. The control plane lives in `packages/control-plane`, with its own SQLite and entry point. It signs tokens, holds users, machines and grants, mints single-use enrollment codes, and relays. **Nothing in `src/` may ever import from it.** ## What is not confined `REEMOAT_AUTH` decides *who* is asking. Nothing decides what they can reach. **An agent runs as you.** It is a child of this process, with your uid, your `HOME`, your files, your `~/.ssh`, your browser profile and your other repositories. `cwd` is not confined, `REEMOAT_ROOTS` narrows the directory *picker* and nothing else, and the ACP `fs` capabilities are granted because declining them would confine nothing — the agent could make the same read itself. That is the trade every coding agent on a laptop already makes. What this daemon adds is that it can be driven from a phone, over a relay, by anybody holding a grant on the machine. **Codex confines itself, and that is codex's doing rather than a feature of this daemon.** Measured 2026-08-07: a codex session runs its commands under a sandbox (`CODEX_SANDBOX=seatbelt` on macOS) with the network off, and its default `agent` mode escalates to a `session/request_permission` when a command needs more — which is how the permission machinery gets exercised at all wherever `~/.claude/settings.json` blanket-allows Bash. Nothing here asks for that and nothing here can rely on it: it is a per-agent default, `agent-full-access` is one of the three modes the session offers, and the paragraph above is still the honest description of what the daemon guarantees, which is nothing. ⚠ And the escalation half has already moved under it: measured 2026-09-04 on codex 0.153.1 under codex-acp 1.8.0, a `curl` the sandbox had refused was re-run **with network** after a `Guardian Review` tool call of codex's own approved it, and no `session/request_permission` ever reached this daemon. So on a current codex the permission machinery may not be exercised at all, and "test it with kimi" below is the only reliable route — `acp-agents.md` records the shape. **Cursor asks about less than its cards suggest, and its rules are shared with the terminal.** Read off 2026.09.28's own ACP server: an edit inside the workspace never asks; a shell command asks unless `~/.cursor/cli-config.json` allowlists it, and **Allow always** on its card writes that allowlist — the same file the person's terminal `cursor-agent` reads, as is the model a session last chose (the owner's call: one config, as cursor keeps it). `-f` is the one flag that would answer every request and is never passed. It also reads `CLAUDE.md`, `.claude/skills` and Claude Code's `settings.json` hooks in the worktree it runs in. Q6.115. Three specifics, each a measurement before it was a policy, each of which reads as a bug if you find it without this section: **The agent inherits this process's environment.** `agentEnv()` strips the session-scoped `CLAUDE_*` names and everything `REEMOAT_*` — claude gets one back, its own `REEMOAT_MCP_BEARER` (Q2.236) — and that is **hygiene, not a fence** — the agent runs as this uid and can read `/proc//environ`, the env file and `REEMOAT_DB` itself. What the strip prevents is three accidents: an agent running `env` and pasting the output into a transcript; an agent running `pnpm daemon` and colliding on the daemon lock; and `REEMOAT_TOKEN` reaching a subagent's context window. **Git hooks run as you, and that is the intent.** `GIT_NO_EXEC_CONFIG` is deleted, so `git worktree add` runs the repository's own `post-checkout` and its LFS smudge filters. Cloning a hostile repository is exactly as dangerous here as in your own terminal — and no more. Neutralising it cost a silent failure: a blanked `GIT_CONFIG_GLOBAL` checks out LFS pointer files instead of content. **A plugin also runs as you, and it is somebody else's code.** It is a child process of this daemon with your uid and your files — the same trade the agent makes, arriving by a different door. `manifest.scopes` is declared, shown at install and refused when exceeded, and that is **hygiene, not a fence**: the child can `import("node:fs")`. What it buys is a named blast radius, a hang that cannot take the daemon's event loop, and a plugin that never holds the daemon's token. The one real boundary is that **the browser executes none of it** — a plugin sends a description and the app draws it, so the origin holding `reemoat.credential` runs nothing a plugin author wrote. `docs/PLUGINS.md` is the author's document; Q1.612 is the argument. **This daemon downloads and executes third-party installer scripts, as you — but a harness arrives on a machine only when somebody presses Install, and the timer only *refreshes*.** That is the narrower posture, and it is narrower than it was: `deploy/agents.sh` used to install all five on the bootstrap, on every `deploy.sh`, and daily — so a harness added to this repository landed on every machine in the fleet by itself, offering a sign-in for a program nobody asked for. Now `deploy/bootstrap.sh` installs **none** (`--install-agents a,b` is the door for a provisioner with nobody to press anything), `deploy/deploy.sh` and `src/agentupdate.ts` pass `--refresh-only`, and `src/agentinstall.ts` runs `--only ` behind a `machine:admin` press. A refresh moves what is there through the door it came in by — a copy installed outside both is named and not moved — and fetches nothing new. What bounds the download is unchanged and narrow: no `sudo` and no system package manager (`deploycheck` asserts the first over the script), no shell profile is edited, the script runs under `agentEnv()`, not this daemon's environment, an installer is downloaded whole before running, a build a live session may be on is kept, and a failure is a warning rather than a stop. Nothing is vendored under it any more (Q4.114): a harness with no CLI is refused with a sentence rather than started, and `AgentAvailability.installable` is what puts a button under that sentence; `REEMOAT_AGENT_SOURCE=npm`, every harness with a package from the npm registry into that toolchain, is a firewalled machine's choice, never a fallback, and decides only how an absent CLI is installed — **two take that door under either value**, kimi because its own updater exits 0 having installed nothing without a TTY, and grok because its vendor installer edits shell profiles and this script edits none (Q4.125), and cursor takes its vendor's installer under either value, having no package — refusing when `~/.local/bin/agent` is somebody else's, since that installer replaces it (Q4.129); `REEMOAT_AGENT_CHANNEL` is which of claude's release channels the fleet follows, `latest` by default, and unlike the source it moves a copy that is already there: re-applied on every refresh (Q4.115). `REEMOAT_AGENT_UPDATES=off` (or `0`) switches off both the timer and the button. What runs is `CLAUDE_CODE_EXECUTABLE`/`CODEX_PATH` outright, else the **first** copy on PATH, then in the directories the script installs into — so a file an agent drops into `~/.local/bin` is the build the daemon runs within ten minutes, as the same uid (Q6.106). **Why it exists at all is a measurement, not a preference**: none of the six self-updates when a *daemon* drives it, every updater being gated on a terminal an ACP-spawned agent never has (Q4.113) — grok is the one that *would*, in the background, which is why it is spawned with `--no-auto-update`, and cursor carries `--disable-auto-update` for the build after the one measured: when a build moves on this fleet is `src/agentupdate.ts`'s decision, not the agent's. **An agent can ask another session's agent to act, and only that agent's own harness weighs the request.** Every agent that takes the `reemoat` MCP server can hand work to every other session on the machine and on its owner's other machines, so a sandboxed codex can ask an unconfined claude on another computer. The daemon bounds how much (Q2.238, Q5.121) and never what; permissions for it are not built (Q7.151). `REEMOAT_PEER_MESSAGES=off` removes the messaging tools and refuses every message. **`~/.claude/settings.json` can bypass the permission machinery entirely.** Where it blanket-allows `Bash`, `Edit` or `Write`, the inner CLI decides for itself and the permission state machine never sees a request — so a permission path that looks untested may simply never have been reached. Test it with `kimi`, or an isolated `CLAUDE_CONFIG_DIR`. The settings screen now **reads that file and says so**, because a switch below called "ask me every time" is a lie next to a config that already answered. If a sandbox is wanted again, the seam is `SessionRuntime` — kept as an interface with one implementation for exactly that reason. ## Pushing There is no forge feature. An agent pushes with your `~/.gitconfig`, your credential helper and your keys, exactly as you would — `GIT_NO_EXEC_CONFIG` had to go for that to be true, since it cleared `credential.helper` and `core.sshCommand` on every invocation. Whether that is right or alarming is the same answer as everything in **What is not confined**. ## Where the rest of this lives The rest of this file is in `.claude/rules/`, one file per area, each scoped by `paths:`. **A rule arrives when a file matching its globs is read, and it does not come back after `/compact`.** So if you are planning without opening anything, or the conversation has been compacted, read the rule for the area you are in before deciding anything. The third column is a list of questions, never their answers — the answers are only in the rule, which is what keeps this table from becoming a second copy that drifts. Dependencies point one way: `server` → `registry` → `session` → `acp/*`, with `events.ts` as the shared vocabulary underneath. The invariants are spread across these by subject, and they are load-bearing: each was a real defect before it was a rule, and **none is enforced by the compiler**. | Rule | Loads on | Answers | |---|---|---| | `daemon-sessions.md` | `src/registry.ts`, `src/session.ts`, `src/events.ts`, `src/store/` | What a restart brings back and what it does not · the two verbs for stopping · what the agent says after the turn ends · what ends a turn the agent never answers, and the three traps in doing it · the log's invariants | | `daemon-bounds.md` | the same globs | Every number the daemon holds and what moves each · what the log is bounded by and what it is not · what a ceiling releases rather than refuses · why this is a file of its own | | `mid-turn-messages.md` | `src/registry.ts`, `src/session.ts`, `src/acp/client.ts`, `packages/web/src/ui/Composer.tsx`, `packages/web/src/attach.ts`, `packages/web/src/wire.ts` | Sending while the agent is working · which door a message goes through, and who decides · what an injection does to the turn, measured · what the queue costs and what a stop does to it · Stop or Send, and what whitespace is worth | | `agent-messaging.md` | `src/peers/`, `packages/web/src/peer.ts`, `ui/PeerMessage.tsx`, `ui/SentFile.tsx` | How one session's agent reaches another's · why every message is acted on, and what the idle notice stands in for · why delivery is the prompt route's and not a copy · what the envelope may carry · what stops a loop · which ended sessions another agent may wake · the two tools on that server that are not messaging, and what a sent file is | | `session-nicknames.md` | `src/nickname.ts`, `src/registry.ts`, `src/peers/hub.ts`, `envelope.ts`, `packages/web/src/nickname.ts`, `mentions.ts`, `mentionLinks.ts`, `ui/MentionMenu.tsx`, `MentionLink.tsx`, `Composer.tsx`, `NewSession.tsx`, `SessionBrowser.tsx` | Why a session has a title and a nickname, and which one leads · what makes a nickname one session's, and why that is a reservation · what the agent gets for a person's `@name`, and why it never waits on a listing · where `@` opens and whose rows it shows · where a drawn `@name` leads, and why it is a button | | `acp-agents.md` | `src/acp/`, `src/session.ts`, `packages/web/src/ui/tail.ts` | What claude, kimi and codex actually send, measured · asking you a question · ultracode · subagents, commands and the snapshot · every gotcha that is a fact about an agent | | `harness-tools.md` | `src/acp/agents.ts`, `scripts/daemoncheck.after-the-turn-and-config.ts` | Which of a harness's own tools this daemon withdraws, and by what it decides · the one door each harness has, and the three that have none · why a name is canonical or it withdraws the wrong tool · what is left alone on purpose · how the list is measured | | `acp-extensions.md` | `src/acp/xai.ts`, `src/acp/cursor.ts`, `src/acp/client.ts` | The three requests grok sends that ACP has no method for, measured · which door each is routed onto and how each is answered · how grok withdraws one, and why a handler's position decides it · its own question timeout, and the tool withdrawn when questions are off · cursor's five, which carry no session, and why an error is the wrong answer to each · its subagents' own session ids | | `agent-login.md` | `src/agentauth.ts`, `src/runtime/`, `packages/web/src/ui/login.ts` | How a credential reaches the host with no terminal · the pty and the two `script`s · what each CLI's status probe prints and on which stream | | `agent-install.md` | `src/agentinstall.ts`, `agentscript.ts`, `transcript.ts`, `packages/web/src/ui/agentInstall.ts`, `settings/AgentsPanel.tsx`, `deploy/agents.sh` | Why nothing puts a CLI on a machine but a press · `installable` against `!available` · why the verdict is a measurement and never an exit status · one run daemon-wide, and the two phases a Stop may not signal into · the two lock layers, and which one is first come, first served | | `files-paths-git.md` | `src/changes.ts`, `src/worktree.ts`, `src/uploads.ts`, `src/stall.ts`, `src/paths.ts`, `src/git.ts` | Attachments in, files out · containment, symlinks and the one `rmSync` · why no synchronous filesystem call may touch a path this daemon did not create · how git is parsed | | `code-import.md` | `src/archive.ts`, `packages/web/src/ui/ImportCode.tsx`, `packages/web/src/importSkill.ts` | Bringing a codebase onto a machine · why containment had to be rebuilt for a path somebody else wrote · what each archive format costs, measured · the one thing the target may not notice | | `relay.md` | `src/relay/`, `src/server.ts`, `packages/control-plane/src/relay/`, `packages/web/src/stream.ts`, `machine.ts`, `localRoute.ts`, `src/announce.ts` | Why there is no direct path in, and the one exception · what bounds it, and how a daemon says where it is · what the tunnel carries and what it must never parse · a socket's lifetime, rotation and cursor · the h2 and flow-control measurements | | `http-and-routes.md` | `src/server.ts`, `src/http.ts`, `src/cors.ts`, `packages/web/src/http.ts`, `packages/control-plane/src/app.ts` | The error envelope every service answers in · which non-2xx is not an error · what a route retry may replay · every `pnpm client` verb | | `auth-and-tokens.md` | `src/auth.ts`, `src/token.ts`, `src/enroll.ts`, `packages/control-plane/src/keys.ts` | What a signature proves and what it does not · why the daemon makes exactly one control-plane request, ever · every credential this fleet mints and how each stops being one | | `authority.md` | `packages/control-plane/src/app.ts`, `main.ts`, `store.ts`, `schema.sql` | What this service is responsible for and what may never arrive in it · the two ratchets that hold that line, and the one exception named by literal · why it serves no browser UI by default · the three rules a migration owes | | `cp-devices.md` | `packages/control-plane/src/devices.ts`, `sessions.ts`, `packages/web/src/ui/settings/DevicesSection.tsx`, `packages/native/src-tauri/src/config.rs` | What a device is and what it deliberately decides nothing about · why a retired id is ignored rather than refused · why the device check is a second statement and never a join · where the id lives on the client, why not the keyring, and why per account | | `cp-accounts.md` | `packages/control-plane/src/app.ts`, `settings.ts`, `registration.ts`, `packages/web/src/ui/gate/` | Who may exist and who may sign up · disable against delete · the settings table and which side won · every `cpctl` verb | | `cp-credentials.md` | `packages/control-plane/src/password.ts`, `sessions.ts`, `throttle.ts`, `net.ts` | The positional gate · what a password change must prove · what a guessing counter is keyed on and what the address half is worth · which 401 signs you out | | `cp-machines.md` | `packages/control-plane/src/machines.ts`, `quota.ts`, `packages/web/src/quota.ts` | Who owns a machine and what a name may collide with · the ceiling against the limit · what a revoke gives back · adding a daemon for somebody else | | `cp-mail.md` | `packages/control-plane/src/mail/`, `emails.ts` | Why a mail outage must never become a sign-in outage · what sits in the outbox and for how long · what a mailed link may carry | | `web-shell.md` | `packages/web/src/ui/AppShell.tsx`, `SessionBrowser.tsx`, `groups.ts`, `overlay.ts`, `settings/`, `packages/web/src/store.ts` | The one question this screen is shaped around, and the rules that keep it answerable · who owns Escape · what a folder is · what a client may not draw optimistically | | `web-transcript.md` | `packages/web/src/ui/tail.ts`, `EventList.tsx`, `DiffView.tsx`, `packages/web/src/diff.ts` | What a conversation may leave out and what it must say instead · what folds into a run and what may never · how a diff is drawn, and what refuses to draw one · what a `/clear` leaves behind | | `background-work.md` | `packages/web/src/ui/TaskPanel.tsx`, `tasks.ts`, `finishedTasks.ts`, `tail.ts`, `EventList.tsx` | Where background work is drawn and the two ways in · when the working line is a door · why a subagent is a task, and the four things that end one the wire never does · what the finished band holds and what a clear destroys · what an empty panel may claim, by agent | | `ask-card.md` | `packages/web/src/ui/AskCard.tsx`, `PermissionCard.tsx`, `ElicitationCard.tsx`, `packages/web/src/permission.ts`, `ask.ts`, `elicitation.ts` | The one card for "the agent is waiting on you" · where it sits and what it may cover · which plan-mode requests are curated and which are drawn as sent · what may be picked, how many, and why nothing you typed is ever erased | | `web-composer.md` | `packages/web/src/ui/Composer.tsx`, `CommandMenu.tsx`, `AgentConfigBar.tsx`, `packages/web/src/keys.ts` | Which key sends · what a `/` opens · why a control never leaves the strip · what a chip may claim before the daemon has answered | | `legal-pages.md` | `packages/web/src/legal.ts`, `legal/`, `ui/legal/`, `ui/gate/Gate.tsx`, `GateCard.tsx` | Why the documents are a route rather than a sixth gate screen · why a policy is data and never markdown · whose terms a fork serves · what the consent box gates and what it deliberately does not record | | `native-shell.md` | `packages/native/src-tauri/`, `packages/web/src/native.ts`, `cp.ts`, `ui/ChooseServer.tsx`, `scripts/nativecheck.ts` | Which one leg of this client leaves the webview, and the four reasons the others may not · what crosses the bridge and what a join does not check · why a credential is keyed on a server and an account · the synchronous read, and the two answers that were refused · why the server picker is a phase rather than a route · one rule, three copies, and what compares them · the one workspace line three deploy behaviours depend on | | `native-accounts.md` | `packages/native/src-tauri/src/accounts.rs`, `seats.rs`, `commands.rs`, `config.rs`, `daemon.rs`, `packages/web/src/slot.ts`, `native.ts`, `store.ts`, `ui/MenuDrawer.tsx`, `ChooseServer.tsx`, `SignIn.tsx` | What an account is on this computer, and why its key is the server *and* the user · why the host decides which account a call is about and the page never names one · the bridge contract, in one table · a document rather than a label, and what a generation refuses · a webview per account on macOS, a rebind and a reload everywhere else · what adding, switching and signing out each keep and give up · a daemon per account, and which one keeps `~/.reemoat` · what the first launch after the update moves, and only on proof | | `native-packaging.md` | `packages/native/src-tauri/tauri.*.conf.json`, `packages/native/scripts/`, `deploy/ci-release.sh` | Which platforms carry a daemon inside them and which carry a client · the one JSON file a profile is, and the measurement that made it one rather than a cargo feature · what an overlay may say, and why the list is that short · why the staging script refuses a Windows triple by name | | `design-system.md` | `packages/web/src/ui/bits.tsx`, `ui/kit/*`, `ui/settings/*`, `scripts/webcheck.kit.ts` | Which primitive answers which question, and what to do when none does · the one height, and why it is written twice · what a dropdown draws, how wide it opens, and why a field's may not head its panel · why a label sits beside its control and never around it · the kit's one-way rule | | `web-typography.md` | `packages/web/src/index.css`, `ui/bits.tsx`, `paths.ts`, `ui/settings/` | Which strings are monospace and which are prose · the one surface where a path is a name instead · the scale, and the single arbitrary size that is allowed to exist · one caps idiom, three constants, and why the choice between them is a colour · what the landing page shares and what nothing can check | | `dark-theme.md` | `packages/web/src/index.css`, `theme.ts`, `public/theme.js`, both HTML shells, `ui/MenuDrawer.tsx` | What a dark token owes its light twin · what may not hold a colour, and the three traps · which palette is on, who writes `data-theme`, and why before the first paint · why light until the switch says dark, and whose choice it is | | `docked-panels.md` | `packages/web/src/ui/paneWidth.ts`, `rail.ts`, `taskWidth.ts`, `PaneHandle.tsx`, `leaving.ts`, `TaskPanel.tsx` | How wide a draggable pane is, and which custom property the panel actually spends · who owns the separator's keyboard path · how a layer leaves | | `machine-gestures.md` | `packages/web/src/machineOrder.ts`, `ui/machineDrag.ts`, `machineSwipe.ts`, `MachineColumn.tsx`, `SessionBrowser.tsx` | What orders the machines until a reader drags one, and why this computer's leads · what this computer's own is called, and on which screens · why the reorder is a hook and not a component · where the merge is applied and which memo is load-bearing · swiping between machines, on the list and not on the strip · the tabs' own numbers | | `native-panels.md` | `packages/web/src/ui/NewSession.tsx`, `download.ts`, `packages/web/src/native.ts`, `packages/native/src-tauri/src/commands.rs`, `packages/web/scripts/webcheck.native-bridge.ts`, `local-route.ts` | Why a cancel is neither a failure nor an answer · `(async)` as a rule and now a mechanism · why the folder panel is the one thing here that is per *machine* · what this loosens and what it does not · the browser arm, which is not a gap | | `plugins.md` | `src/plugins/`, `plugins/`, `packages/web/src/wire.ts` | What a plugin may add and where it may appear · the two axes of authorization, and which applies inside a hook · what an update keeps and what a failed one puts back · why `src/` now holds three `fetch` calls | | `plugin-contributions.md` | `src/plugins/contributions.ts`, `manifest.ts`, `src/acp/`, `src/runtime/local.ts`, `packages/web/src/ui/agentCard.ts` | A plugin that adds an *agent* or a *provider* · which id is checked for membership and which only for shape, and what each costs to get wrong · where a base URL may point now · what a machine's ceiling is and why it is a refusal | | `plugin-ui.md` | `packages/web/src/plugins.ts`, `catalogue.ts`, `install.ts`, `ui/plugins/`, `PluginView.tsx` | What the browser draws for a plugin and what it refuses to draw · where a plugin is installed from and what somebody agreed to · the one client that fails *closed* · what a draft of a fleet is | | `agent-systems.md` | `src/acp/systems.ts`, `src/agentask.ts`, `packages/web/src/agents.ts`, `ui/AgentBuilder.tsx` | Why a harness is not a system · which pairs of them exist, and who answers that · how a model is named to a harness that has never heard of it · what a session records and what it resolves at every launch | | `agent-strip.md` | `packages/web/src/agentStrip.ts`, `agentPick.ts`, `ui/NewSession.tsx`, `ui/settings/MachineAgentsSection.tsx` | Which agents the New session row offers and in what order · what a stored position may name and what it may never be validated against · why hiding is not a refusal · reordering with no library · why a cut row has to look cut | | `agent-catalogue.md` | `packages/web/src/openrouter.ts`, `agents.ts`, `ui/AgentBuilder.tsx`, `src/acp/systems.ts` | The three places a model's name can come from, and which one the browser fetches · the one system whose two spellings are the same models · what the reader drops and why greying it would be worse · what has been tried in a heading and taken back out, twice | | `deployment.md` | `deploy/`, `.github/workflows/` | Two deployments and three services · what a restart costs and what decides one · every rule about writing a value into an env file | | `compatibility.md` | `src/version.ts`, `src/relay/protocol.ts`, `packages/control-plane/src/store.ts`, `schema.sql`, `packages/web/src/wire.ts`, `packages/protocol/src/frames.ts` | What ships with what, and why a client nobody can push decides the rest · negotiated against announced · which way an unknown value must fail · which side ships first, and the one rule that produces both orders · how to make a breaking change without a flag day · what is still one | | `e2ee.md` | `packages/protocol/`, `src/e2ee.ts`, `machinekey.ts`, `packages/web/src/e2ee.ts`, `packages/control-plane/src/machinekeys.ts`, `packages/native/src-tauri/src/device.rs` | What the relay can read and what it cannot · which static key each end holds, where it is kept and who may touch it · why the capability may not ride the handshake · what a tag failure may not do, and the one refusal that cannot say why · what the device binding proves, and the two paths it deliberately does not reach | **Keeping this file small is `docscheck`'s job, not a preference.** It fails the build past a ceiling this file deliberately does not restate — the number lives in `scripts/docscheck.ts` and is read from there, for the reason `SETTING_KEYS` already gives one section down. What belongs here is what is true wherever you are working; a measurement, a post-mortem or a correction belongs in `docs/DECISIONS.md`, and the last time that rule had no check the file tripled in six days. ## Conventions Relative imports end in `.js`; builtins use the `node:` prefix; type-only imports use `import type` (`verbatimModuleSyntax` is on). Stateful classes use a private constructor plus a static async factory (`Session.start`, `AcpClient.launch`); teardown is returned as an unsubscribe function; idempotent shutdown is `this.x ??= this.doX()`. Validation is hand-written — no zod. **Nothing in `src/` writes to stdout or stderr**, with two sanctioned files. `store/sqlite.ts` prints three times, when a migration destroys something (a dropped forge account, a collapsed credential) or a repair retires a machine key. All three run inside `openStores`, before any callback the daemon could have wired, so it is the only moment anybody can be told. And `src/plugins/runner.ts` is the *child* process's entry point rather than the daemon's: its `unhandledRejection` handler writes to the stderr `runtime.ts` already captures into the ring shown on the plugin's failure row, which is the whole of what a process holding none of the daemon's callbacks can say. Everything else reports through an injected callback (`onDegraded`, `onWarning`); only `scripts/` print. ## Comments - Default to no comment. Write one only when a reader would get the code wrong without it: hidden constraint, invariant, workaround, surprising behaviour. One line, two at most. - The why, with its measurements and alternatives, lives in docs/DECISIONS.md. In code, cite it as Q. instead of retelling it. - No history of past versions, no restating code, no banners, no ⚠ or bold emphasis, no commented-out code, no TODO without a ticket. - An empty catch carries a one-line reason. - When you change code, fix or delete the comment next to it. ## Next Deferred work, open decisions and the inventory of what is asserted are group **Q7** of `docs/DECISIONS.md` ("Open questions and deliberate non-goals"). The short version of what is knowingly not built: no sandbox (the seam is `SessionRuntime`), no fleet rollout, no access log on the control plane, and no `@file` mentions, and **a background task's end is on the wire for one agent only** — claude reports it behind a declared capability, which is what stops the sweep releasing an agent mid-build; the other four still say nothing, and so does claude about a backgrounded *subagent*, which was the measured case (Q2.228, Q7.113). **The daemon still has no registry** — it discovers nothing and polls nothing — but there *is* a market, and the half of Q7.104 that survived is which process reads it: a catalogue on its own host, read **by the browser**, with `POST /plugins/source` handing the daemon a repository and a pinned commit. A machine still installs only what somebody named it on. **What a plugin may now add is an agent and an inference provider**, which is the thing Q7.31 and Q7.125 twice declined a registry for and named as the only case that would justify one — an ACP binary this repository does not vendor and cannot measure. It arrives as two declarative blocks in `plugin.json` rather than as `REEMOAT_AGENTS`, so it is chosen by a person, disclosed before it is sent, and switched off with one control; `AGENT_IDS` and `SYSTEMS` are still the six and the nine this repository *ships*, and what a machine *offers* is those merged with what is installed on it. And no plugin draws in the transcript or adds a slash command, both with their seams written down rather than half-built (Q7.105). **CD stops half-way on purpose**: nothing deploys on a push, and the one automated path is a manual `workflow_dispatch` that deploys the *control plane* only, refuses a commit whose `check` run is not green, and calls `deploy/ci-deploy.sh` rather than reimplementing it. **The daemon is still refused** — recreating one interrupts every turn in flight and drops every pending approval on the machine you also develop on, which is the half of the old "no CD" reason that never expired (Q7.94). **Q7.61 is closed by deletion rather than by measurement** — it asked whether an admin password reset should burn the account's enrollment codes, and there is no admin password reset any more; the mailed reset that replaced it deliberately leaves codes alone, because proving control of your own address is not evidence that a daemon you enrolled is compromised. **Q7.62 is closed too, and by the plugin work rather than by a measurement** — it asked whether the upload route's body-cancel discipline survived the auth and scope middlewares above it, and the answer was that the handlers were never the gap: the middlewares were. The cancel now hangs off the `isStreamingRoute` exemption that creates the obligation, so all three streaming routes inherit both halves and Q7.96 closes with it. Three are open because the *measurement* is missing rather than the code, and all three are settled by the same run: one real device-code login on macOS from a signed-out agent. Whether BSD `script` survives a 15-minute flow with `/dev/null` on stdin, which is what `loginStdio`'s macOS fix rests on and which is measured only as far as the spawn succeeding (Q7.63). What those flows actually print, since `ui/login.ts`'s device-code patterns are conservative guesses while its one failure string is measured — the fallback makes a miss cost the old screen rather than a blank one (Q7.64). And whether `codex login --with-api-key`, which exists and reads stdin, closes the gap recorded at `AGENT_LOGIN.codex`, where a pasted `CODEX_API_KEY` reaches the model's API and still leaves `session/new` answering -32000 (Q7.65). Agents are a **five**-member union now, and the fourth met the precondition Q7.31 set for itself — *"a fourth agent, to show the pattern is a pattern rather than two coincidences"* — and left the answer unchanged. opencode cost the five edits codex cost (`resolveAgent`, `AGENT_LOGIN`, the vendored-CLI resolver (gone), `wire.ts`'s hand-mirrored union, `pincheck`'s list) plus one the compiler was *claiming* to force and was not: `AgentGlyph` answers `ReactNode`, `undefined` inhabits it, and a `switch` falling off the end returns exactly that — so a blank tile would have compiled clean for four releases. It ends in a `never` arm now — and grok is what paid for it, the first harness added since. **The fifth is a different shape rather than a fifth coincidence, and it is the cheapest yet.** `grok agent stdio` is xAI's own ACP entry point, so it is the first built-in with **no adapter this repository pins** — `pincheck` has nothing to add, and `AgentCapabilities.cli` records the build as it does for kimi and opencode. Its two controls are doors already driven (`category: "model"`, and `thought_level` in opencode's spelling), and it declares no `providers`, so `hostable` refuses it every foreign system with nothing written. What it *did* cost is **Q6.20, open since the first release**: grok refuses `session/new` until ACP's `authenticate` has been sent — with a key in its environment too — and the method id that works is advertised nowhere, while the only advertised one blocks on a browser. So `ACP_AUTH_METHOD` is written down and `AcpClient.launch` is the one call site. Q6.109, Q6.110. Everything else — questions, permissions, commands, config, resume, context usage — arrived through capabilities already read by `category` and by shape rather than by name. **The sixth cost more than the fifth, and almost none of it was compile errors.** `cursor-agent acp` is cursor's own entry point, so again no adapter — but the compiler forced four places (`AGENT_LOGIN`, `resolveAgent`, `AgentGlyph`, the drivers' tables) and a census found about ninety. What was new: **five requests of its own**, none carrying a session, where an error is read as acceptance (Q6.117); **subagents on session ids nobody opened**, routed home by the client (Q6.117); **no `session/resume`**, so Q5.85's "never `session/load`" became "load where resume is missing", safe because the replay precedes the answer (Q2.248); **a process cwd it reads its rules from** (`inSessionCwd`); **a `status` that ignores a working key**, so the probe is `models` on both streams (Q6.116); **a model pin written to the person's config** (Q6.115); **no question tool from cursor's server over ACP**, so the `reemoat` MCP server serves one of this daemon's own (Q2.250); **no npm package** (Q4.129); and **a hyphen in its command**, which two drivers' `[a-z]+` had silently assumed away. The live half waits on an account (Q7.154). What each new agent *does* cost is the measuring, and opencode is the sharpest case yet (Q6.105): the upstream issue closing "per-session model selection" as **not planned** is about `session/set_config`, a method this daemon has never sent, while `session/set_config_option` — the one `pinNativeModel` does send — works. Reading the tracker instead of running the binary would have bought a whole new environment door, a new arm in `hostable`, and a system credential in a spawn environment, none of which is needed. A registry is still not built; what would justify one is now an agent this repository cannot vendor and cannot measure, which is a different feature from a tidier union (Q7.125).