# Low Passage — Research What was checked, when, and what it changed. The specification is [[Low Passage]]; this is the evidence behind it. The order runs from the thing being replaced to the thing it will run on: OpenRig analysed first, then this machine, then the candidate runtimes and the hardware that could host them, then the conclusions, and the research queue last. Each section says what was checked and when, because a fact about a version is only true until the version moves. Machine facts were verified on 2026-11-07, and the Erlang toolchain was re-checked on 2026-11-08. Versions and ports are true when written, not forever. ## Terminology This note uses OpenRig's vocabulary when describing OpenRig and this project's vocabulary when describing the design. The mapping: | OpenRig | Low Passage | |---|---| | Rig | OpenRig's word for a running instance. This project says *ship*, and *Rig* was its own name until 2026-11-09. | | Seat | Station | | Pod | Department | | Queue | Watch bill | | Lease | Watch | | Closure / handoff | Handover | | Event log | Ship's log | | Project / mission / slice | Campaign / mission / sortie | | Wisdom | Standing orders | | Sediment | Rough log | | Monitor | Bridge | | Steward | Analyst | | Federated agent | Consort | | Brain pool | Mind pool | | Brain (earlier term) | Mind | The OpenRig sections below keep their own terms on purpose; the analysis uses ours. Concepts with no OpenRig counterpart are ours alone — commission and rank, tender, clarification, standing watch, hailing, the ship test, working party, reserve, reassignment, the definition library and the store — and [[Low Passage]] is their only definition — the vocabulary where they are terms, and the section that uses them where they are procedures. ## Sources - [OpenRig repository](https://github.com/mvschwarz/openrig) — README, `ARCHITECTURE.md`, `docs/reference/rig-spec.md`. - [OpenRig architecture](https://openrig.dev/docs/architecture), [work tree](https://openrig.dev/docs/work-tree), [RigSpec reference](https://openrig.dev/specs/rigspec). - *I Run an AI Civilization in Herdr* — the talk that introduced the design. Useful for intent and failure modes, not for implementation detail. - Direct inspection of this machine: Erlang install, Bionic configuration, and the local model endpoint. ## OpenRig as implemented Not a pattern library. A working control plane. | Fact | Value | |---|---| | License | Apache 2.0 | | Language | TypeScript | | Runtime | Node.js 22 or 24 | | Platform | macOS or Linux; WSL2 on Windows; native Windows unsupported | | Transport | tmux | | Packages | `daemon` (private), `cli` (published), `tui` (private), `ui` (maintenance mode), `test-system` | | Daemon | Hono HTTP on port 7433, SQLite in WAL mode at `~/.openrig/openrig.sqlite` | | Interfaces | CLI, TUI, MCP server, legacy web UI | | Scale | ~87 top-level CLI commands, 18 MCP tools, 6 runtime adapters | The daemon rule is worth stealing outright: the web framework exists only in the route layer. Domain services and adapters import zero web-framework code. Routes depend on the domain; the domain never depends on routes. That is why the CLI, the TUI, and the MCP server all build on the same logic. The other rule is the one to tattoo somewhere: **tmux is transport, not truth.** If tmux and the database disagree, the database wins. tmux is how you reach the agent and read what it printed. It is not where the record lives. ### The invariants Four carry the behavior. **State splits by mutability.** SQLite is canonical for coordination: event log, queue, workflow instances, chat, audit. The filesystem is canonical for content: plugins, agent images, context packs, and the project/mission/slice tree, which the daemon caches in memory rather than copying. **Hot-potato closure.** A queue item cannot be marked done without a reason from a fixed set — handed off, blocked, denied, canceled, finished with no follow-on, escalated — and handoff, block, or escalation must name a target. It is enforced inside the database transaction, so every interface gets the same guarantee. In Erlang the same rule is one message to one process, which is why it is the first thing to port. The ship adopts the set and adds two reasons of its own: `awaiting clarification`, because a crew waiting on an answer is blocked for a different reason than one waiting on a dependency, and `superseded`, because work whose directive changed under it did not fail and was not cancelled. **The workflow runtime is a scribe, not a gatekeeper.** A workflow is a markdown or YAML spec; its instance lives in SQLite. Closing a step does four things in one transaction: close the step, create the next step's work item, append the audit entry, update the instance. The runtime never decides whether the closure was valid. The queue remains the authority. **Only the leaf is fully specified.** Every node has one authored `SPEC.md` with intent in the frontmatter, but only the slice carries a full specification. The slice scaffold is `SPEC.md`, `slice.yaml`, `PROGRESS.md`, `PROOF.md`, and a `proof/` folder. Readiness is derived from attributed judgments, and approval is a freeze or sign-off, never "proven green." Startup is layered in a fixed order: agent base, profile, rig culture, rig, pod, member, operator. That makes "what did this agent know at boot" answerable. The worked examples in [[Low Passage]] translate three missions into this structure — a survey, a mind-host enrollment, and a nightly watch — which between them earn the `survey`, `tender`, and `picket` classes, while `starter` remains a class that comes with the fleet rather than the only possible first ship. ### Why not adopt - Requires WSL2 on the development host, which is a dependency the project does not want. - Assumes Claude Code or Codex; this workspace uses Bionic. - The harness model is a persistent terminal session, which Bionic does not expose externally. - Most of what would be adopted is the coordination layer, which is the part Erlang already provides. ## The Erlang mapping OpenRig calls these seats; the project calls them stations. | OpenRig | OTP | |---|---| | Seat address | Registered process or `{via, Registry, Name}` | | Seat config and wisdom | `gen_server` state, persisted and reloaded | | Seat mailbox | Process mailbox — volatile, so durable messages go to the log | | Pod | Supervisor subtree or `pg` group | | Rig | Root supervisor | | Queue item lifecycle | One `gen_server`; closure is a single message | | Hot-potato closure | `handle_call({close, Id, Reason, Target})` rejecting invalid transitions | | Workflow instance | `gen_statem` | | Audit trail | `disk_log` or an append-only table | | Watchdog sweeps | `timer:send_interval` | | Runtime adapters | A behaviour: `describe`, `open`, `submit`, `await`, `cancel`, `close` | | CLI, MCP server | `escript`, or a thin HTTP listener | | Multi-machine | `net_kernel` distribution | Where the mapping breaks: 1. **Mailboxes are volatile.** At-most-once delivery, and a crashed process loses its queue. The fix is architectural: the log holds durable messages; the mailbox only wakes the process. 2. **The harness is absent.** No tmux on Windows, and `open_port` gives pipes, not a pty. Persistent terminal sessions are not reachable, so the station owns the loop and the mind is an HTTP call. 3. **The truth model flips.** OpenRig says the database wins. OTP says the supervisor restarts the process with clean state. That is correct for a station and wrong for a slice in progress, so work state must live outside the station. The two models agree on the deeper rule: processes are disposable; addresses and work are not. ## Platform notes OTP 29 is installed on the development host at `C:\Program Files\Erlang OTP`, with `erl`, `erlc`, and `escript`; `rebar3` is absent. The specification said 28 until 2026-11-08; the install had already moved on. No Linux node has been tested yet. The target is cross-platform, not Windows-native. Windows is where development happens. That means a short list of things to verify rather than assume: - Path handling through `filename`, not string concatenation. - File locking and `exclusive` open semantics. - Signal delivery and graceful shutdown. - Service startup: a Windows service, a systemd unit, or a foreground process. - `open_port` behavior and process cleanup on both. - EPMD and distribution ports, including firewall rules. - Line endings at the boundary. None of these are architectural. They are the difference between "runs on my machine" and "runs on the VPS," and they belong in a platform test matrix that runs alongside M6, not in the M6 exit criterion itself. ## The Bionic audit Bionic is a desktop app at `C:\Users\thant\AppData\Local\Programs\Bionic\Bionic.exe`. It was running on PID 18196 during the audit and listening internally on `127.0.0.1:64001`. ### What is exposed **An OpenAI-compatible local server.** Configured in `.internal/http-server-config.json`: ```json {"autoStartOnLaunch": false, "port": 1235, "cors": false, "logSensitiveData": true, "justInTimeModelLoading": true, "networkInterface": "127.0.0.1"} ``` The `lms` CLI is at `~/.cache/lm-studio/bin/lms.exe`; `lms server status` reported stopped before the probe. The endpoint serves local models and a native REST companion at `/api/v0/models`. **A native model listing.** `/api/v0/models` returns `state`, `type`, `max_context_length`, and `capabilities` — for example `capabilities: ["tool_use"]`, context windows from 32k to 1,048,576, and 32 local models at audit time. This is the natural source for a mind's `describe()`. **MCP as a client.** `.internal/ng-mcp.json` configures stdio MCP servers (lorakeet is the current one). Bionic consumes MCP servers; it does not advertise one. That is the right direction for control: expose the ship as an MCP server and Bionic sessions can operate it. ### What is not exposed - **The subscription.** `ng-cloud-models.json` lists cloud models including `deepseek-ai/DeepSeek-V4.1-Flash` with a 1,000,000-token context. None of them appear in `/v1/models`. The local endpoint is not a proxy for Bionic Cloud. - **The app.** `127.0.0.1:64001` is an internal port, not a documented API. Driving Bionic from outside would mean GUI automation. - **Remote sessions — inbound only.** `lm-link-config.json` has `allowLoadingModelsOnThisMachine: true` and `allowRemoteBionicSessions: false`. LM Link lets other devices use this machine's models; it does not give this machine another device's models, and it does not let a remote process drive Bionic sessions. It is an inbound path, not an outbound mind. - **A CLI.** No `bionic` command is on `PATH`. - **An API base URL.** `cloud-account.json` contains billing metadata, plan links, and a balance. No endpoint. **Remote LM Studio, if it ever happens.** The plausible setups are the desktop as a LAN mind host, a dedicated inference machine, or a laptop station reaching home over a tunnel. All of them end with the same caveat: LM Studio is a GUI app with no bearer token, so a box whose only job is serving models is better run with QVAC or llama.cpp. Treat LM Studio as the desktop provider and QVAC as the one that can live elsewhere. ### Probe results Endpoint: `http://127.0.0.1:1235/v1/chat/completions`, model `zai-org/glm-4.7-flash` (202,752-token context, `tool_use`). | Test | Result | |---|---| | Plain completion | 87.5 s first request including load; `finish_reason: length`; usage `prompt 12 / completion 64`; no cached-token field; `stats` empty | | Tool call | 9.0 s; `finish_reason: tool_calls`; proper `id`, `name`, `arguments`; usage `prompt 159 / completion 75` | | Nonexistent model | **HTTP 200**, answered by the loaded model; `model` and `system_fingerprint` showed the substitution | | Duplicate calls | The tool test returned **four identical `get_time` calls with four different ids** in one turn | What each means: - **Silent substitution is the default.** A request for `does-not-exist` was answered by the already-loaded model with a 200. The only evidence was the `model` field. The adapter must verify identity on every response and fail closed. - **Tool calls work, but not cleanly.** The format is correct. The duplication is a model behavior, and the executor needs a declared per-turn policy rather than optimism. - **No cached-token accounting.** Usage reports prompt, completion, total, and reasoning tokens, but not cached input. Per-station dollar cost can be estimated but not reproduced at the vendor's accuracy. - **First-call latency is load, not inference.** The 87.5 s first request includes a cold model load; the second was 9 s. Station startup budgets need to account for this. - **Prompt logging is on.** `logSensitiveData: true` means the server may write prompts and completions to its logs. That matters for a ship that touches personal data. ## TLS distribution OpenSSL 1.1.1q is installed at `C:\Program Files\Git\usr\bin\openssl.exe`, so certificates can be generated locally. ```bash openssl req -x509 -newkey rsa:4096 -nodes -days 3650 \ -keyout ca.key -out ca.crt -subj "/CN=lowpassage-ca" openssl req -newkey rsa:2048 -nodes -keyout node1.key -out node1.csr \ -subj "/CN=node1.example.com" openssl x509 -req -in node1.csr -CA ca.crt -CAkey ca.key -CAcreateserial \ -out node1.crt -days 825 \ -extfile <(printf "subjectAltName=DNS:node1.example.com") ``` ```erlang [{server, [{certfile, "node1.crt"}, {keyfile, "node1.key"}, {cacertfile, "ca.crt"}, {verify, verify_peer}, {fail_if_no_peer_cert, true}]}, {client, [{certfile, "node1.crt"}, {keyfile, "node1.key"}, {cacertfile, "ca.crt"}, {verify, verify_peer}]}]. ``` Start with `-proto_dist inet_tls -ssl_dist_optfile tls.config`. Caveats that matter: - The node name's host part must match a SAN. This is the most common failure and the error is unhelpful. - It is mutual TLS; both sides present certificates. - The cookie still authorizes. TLS authenticates; it does not replace the cookie. - Certificates do not hot-reload. Use long-lived certs and a calendar reminder. - Clock skew breaks validity checks intermittently. - Pin the same OTP major on every node. - For a spike, `public_key:pkix_test_data/1` generates a throwaway PKI in one call. Wrong for production. ## QVAC (candidate local mind runtime) Source: *Local AI 101: what your own laptop or phone can run*, [qvac.tether.io](https://qvac.tether.io/), [github.com/tetherto/qvac](https://github.com/tetherto/qvac). Checked 2026-11-07. **What it is.** Tether's Apache 2.0 local-AI runtime. Two SDK clients — JS/TS (`@qvac/sdk`) and Python (`tetherto-qvac-sdk`) — drive one worker, and `@qvac/cli` ships an HTTP server around it. The server has two surfaces: the native QVAC API at `/qvac/v1/*` and an **OpenAI-compatible extension** at `/v1/*`, mounted with `qvac serve --openai` and bound to `127.0.0.1:11434` by default. Chat completions work blocking or as SSE, `/v1/models` lists the configured aliases, and embeddings are available. It runs on Linux, macOS and Windows, with Android and iOS through Expo; Node 22.17+ is required for the JS side, while the Python SDK is self-contained. Models are GGUF and small by design — the examples start at 600M and run to the 9B class — with `ctx_size` set per alias and CPU fallback when there is no GPU. P2P model fetching and on-device LoRA fine-tuning are also part of the platform. **Why it is applicable.** It is another endpoint that speaks the OpenAI API, so it fits the `mind` behaviour with no new interface: a `qvac` adapter is a base URL, a model alias, and the same identity and usage checks. It runs natively on Linux, so a VPS can host its own small mind; Bionic cannot. It also runs on phones and small boxes, so a tiny model can live on whatever device is already on the LAN and serve a station elsewhere — the station only needs its URL. And it targets exactly the grunt-station niche — small models, commodity hardware, offline — which is the half of the problem local models are actually good for. **Why it is not the answer.** It is local-only. It does not reach Bionic's subscription or a frontier-context model, so the 100k-token problem remains. It is a model runtime, not coordination: it replaces nothing in the watch bill, ship's log, stations or bridge. Its OpenAI compatibility is younger than LM Studio's, and tool-call fidelity across its catalogue needs testing rather than assuming — one verified client (Open Interpreter) is a data point, not a guarantee. **What to test if it is picked up.** Start `qvac serve --openai` with one small model and hit `/v1/models` and `/v1/chat/completions` from `httpc`. Run the same five-tool probe used on the LM Studio endpoint, count duplicate and malformed calls, then test 8k/16k/32k context with a needle prompt and measure tokens per second. Compare against LM Studio on the same model and prompt. That is a short experiment, and it answers whether the ship gets a portable local mind or stays tied to this desktop. ## Hardware floor The QVAC requirements and the MSM8916 class draw a useful line between a mind and a station. | Role | RAM | Storage | Compute | Example | |---|---|---|---|---| | Mind, chat | 2 GB minimum, 4 GB practical | 4 GB+ | ARM64/x86_64, GPU optional | this desktop, a VPS | | Mind, tiny classifier or embedding | 1 GB+ | 2 GB+ | ARM64 | marginal | | Station, remote mind | 512 MB+ | 1 GB+ | any 64-bit | MSM8916 stick | The MSM8916 "OpenStick" class — 4× Cortex-A53 at 1–1.2 GHz, 512 MB RAM (about 380–390 MB usable), 4 GB eMMC, 2.4 GHz WiFi, LTE — is a $10–$20 computer and a good one for the money. It cannot host a model mind. QVAC's own minimum is 2 GB of RAM, recommended 4 GB; 512 MB is below the floor, and the CPU has no usable GPU acceleration. A 135M-parameter model might fit in the remaining memory, but nothing that would do useful agent work. As a station that calls a remote mind, a probe, or a network sensor, it is plausible. The Hackaday comment on the same class put it exactly: "the only limit is the little 512MB RAM." Buying caveat: listings reuse the same shell with different SoCs. Confirm MSM8916 and eMMC storage with the seller; ASR1802 and ZX297520 clones cannot run the Linux images. ### Cheap mind candidates A Claw on an OpenStick is a station, not a mind. The runtime is a few megabytes; the model is the chunky part. For a box that actually hosts a model, the order of value is: - **Used x86 mini PC, 8–16 GB — ~$150–$250.** A refurbished Lenovo M720q or HP EliteDesk 800 G4 with an i5-8500T, 8–16 GB and a 256 GB SSD: about $150–$200 for 8 GB and $200–$250 for 16 GB (late 2026). A new N100/N150 box with 16 GB is ~$300–$450. Best value per gigabyte, x86_64, good Linux support. 8 GB runs a 4B model comfortably and a 7–8B model slowly; 16 GB is the sweet spot. - **Raspberry Pi 5, 8 GB — ~$175–$200 board, ~$220–$250 complete.** The 2026 memory shortage pushed the board far above its $80 launch price; 16 GB is ~$305. Add PSU, case and storage. CPU inference only; fine for 1–4B, slow above that. - **Orange Pi 5 / RK3588, 8–16 GB — ~$115 for 8 GB, ~$300+ for 16 GB.** RK3588 boards are faster than the Pi 5 on paper, but the NPU is poorly supported for LLM inference and the software stack is rougher. - **An old phone or tablet, 8–12 GB — $0 if already owned, ~$50–$200 used.** QVAC runs on Android; thermal throttling and background limits make it best-effort. - **Jetson Orin Nano Super 8 GB — ~$399; used Mac mini M1 ~$300–$400 (8 GB) or ~$450–$600 (16 GB).** GPU or unified memory, better tokens per second, several times the price. Worth it only if throughput matters. Prices are rough US market, checked late 2026. DRAM prices moved sharply during 2026, so treat these as bands rather than quotes; board prices exclude storage, power and case unless noted. At these numbers the used 16 GB mini PC is the clear value pick, and the Pi 5 is no longer the cheap option. Rough model-to-RAM mapping, Q4 weights plus KV cache and OS: | Model | Practical RAM | |---|---| | 0.5B | 2 GB | | 3–4B | 6–8 GB | | 7–8B | 10–16 GB | | 13–14B | 16–24 GB | | 30B MoE | 32 GB+ | One 16 GB box serving several stations beats several 2 GB sticks serving none. ### Old phones and tablets The used market changes the math. A 6–8 GB phone from 2021–2022 is roughly $50–$150 on eBay; a 12 GB flagship is $150–$250. From measured llama.cpp and LiteRT numbers: | Device class | RAM | Practical model | Decode | Reality | |---|---|---|---|---| | 2021 mid-range / flagship (Pixel 6, S21) | 6–8 GB | 1B–3B Q4 | 3–7 tok/s | short tasks; throttles in 5–10 min | | 2022 flagship (SD 8+ Gen 1) | 8–12 GB | 3B–4B Q4 | 8–10 tok/s | decent grunt mind; 7B ~3.5 tok/s | | 2023–24 flagship (SD 8 Gen 2/3) | 12–16 GB | 3B–7B Q4 | 12–20 / 6–8 tok/s | the only phone class where 7B is pleasant | | Tablet (SD 870, 8 GB) | 8 GB | 1.5B–3B Q4 | 5–7 tok/s | better thermals, still slow | Android reserves 40–50% of RAM for the system, so a "6 GB" phone gives roughly 3–3.5 GB to the model and an "8 GB" gives about 4 GB. That is why 3B is the practical ceiling on 6–8 GB, and why 7B wants 12 GB to be comfortable. The hidden cost is software, not hardware. QVAC's CLI and HTTP server are desktop-only; on Android you need an app that embeds the SDK and serves `/v1` as a foreground service, or Termux plus a hand-built llama.cpp. PicoClaw's Android APK is a harness, not a mind. That app is the project. Verdict: old phones are excellent **stations** and acceptable **best-effort grunt minds**. A $100 8 GB phone running a 3B at 5–10 tok/s can classify, extract, summarize and answer short questions. It is not a primary mind, and it is not a 7B box unless it is a 12 GB flagship. For a pool, one used 16 GB mini PC is still simpler and faster per dollar; for a swarm of cheap stations, old phones are hard to beat. **Measured, 2026-11-09 — a 4 GB phone, below the band above.** A drawer Pixel 3a XL, wiped, with no accounts, Play Services disabled and 109 of 241 packages stripped, held **1.86 GB available** (0.76 GB as found) and ran Llama-3.2-1B Q4_K_M at **8.25 t/s sustained over twelve minutes** (24 × 256 tokens), 9.82 t/s on a short run, and prompt processing from 18.57 t/s at 512 tokens down to 13.51 by 4K — 8 threads throughout. It peaked at 51 °C in a minute-long run and 77 °C under sustained load, losing about 15% of its throughput to thermal management, which is a slope rather than a cliff. A 3B does not fit at any context length; a 1.5B does, at 6.5 t/s. So the practical floor for a 1B grunt mind is nearer 4 GB nominal than the 6–8 GB this section assumed, provided the device is stripped: the as-found reading would have refused it, and the free memory after stripping is what decided. Full numbers in [[Device Report — Pixel 3a XL]]. That also answers part of the Android question without an app: llama.cpp's arm64 binaries pushed to `/data/local/tmp` and run as the shell user were enough — no install, no root, no Play Services. Whatever the mind-host app ends up being, it is convenience and packaging rather than capability — with one exception, below. A bare process is not protected, either. While benchmarking a second model beside a running one, Android's low-memory killer reaped the server; the port simply stopped answering and nothing announced it. That is the argument for the app's foreground service — survival, not capability — and it is worth having regardless of how thin the app turns out to be. The same phone is the worked example of the two ingestion modes. Hollowed, it is a grunt mind and nothing else. Left to its day job with the fleet alongside, it is an **implanted** station — which is what most household hardware will be, since the phones people already own are not spare and their owners are not giving them up. The numbers above are the hollowed case, because that is the one worth measuring: an implanted phone shares its memory with whatever its owner is doing, so 1.86 GB available is a ceiling rather than a floor. ## What a swarm of cheap stations buys Not intelligence. The mind pool is the mind; stations are the body. What many cheap stations add is coverage: - **Parallel observation.** Twenty watchers on twenty logs, services, sensors or inboxes, each filtering locally and escalating only the interesting event. - **Locality.** The station sits next to the data or device — a phone with a camera, a Pi with GPIO, a stick with LTE — so raw data stays local and only a distilled claim goes to a mind. - **Redundancy.** Several cheap stations can observe the same thing; disagreement is a signal, and losing one is not an outage. - **Heterogeneous hands.** A phone has a camera and mic, a tablet a screen, a Pi GPIO, a laptop a keyboard. The swarm can act through hardware the ship does not own centrally. - **Cheap experiments.** Twenty stations let you test a swarm topology against a hierarchy on the same task class without buying servers. - **Low cost of failure.** A $50 device can be lost, stolen or fried without a postmortem. The bottleneck moves; it does not disappear. Stations add concurrency and coverage; the mind pool's concurrent turns cap throughput, and the watch bill and ship's log cap coordination. Without **edge filtering** — each station doing what it can locally and escalating only the distilled event — a swarm is a token furnace with extra steps. ## Mind lifecycle The host controls already exist. QVAC's OpenAI-compatible server exposes `DELETE /v1/models/:id` to unload a model, `/v1/models` for what is loaded, and `serve.load` settings for lazy loading and cancel-on-disconnect. LM Studio loads just in time and reports `state` in `/api/v0/models`; explicit unload support needs verification. Podman can create and destroy a mind host as a container. What none of them provides is the scheduler: a quartermaster that decides when to spin up, which host, when to drain, and when to cull. That part is ours. The constraint to measure is load and unload time. A model that takes 90 seconds to load cannot be spun up per request; it needs a warm pool. A model that unloads slowly cannot be culled mid-mission without a fallback. That is why culling drains first. ## Agent runtimes: ZeroClaw, PicoClaw, NullClaw These three are **harnesses, not minds**. All of them wrap a model loop, tools, memory and channels; none of them serves a model. They point at providers, including OpenAI-compatible endpoints, which means they can point at the ship's mind pool. The distinction matters: they compete with the station's harness, not with the mind adapter. | | ZeroClaw | PicoClaw | NullClaw | |---|---|---|---| | Language | Rust | Go | Zig | | License | Apache 2.0 | MIT | MIT | | Footprint | ~8.8 MB binary, <5 MB RAM | ~8 MB binary, <10 MB RAM | 678 KB binary, ~1 MB RSS | | Platforms | Linux, macOS, Windows, Docker | Linux, Windows, RISC-V/ARM/MIPS/x86, Android APK | Linux, macOS, Windows, ARM/x86/RISC-V | | Exposes | REST/WebSocket gateway + dashboard | Gateway HTTP + Web UI | Gateway API, A2A v0.3.0, WebSocket | | Providers | ~20+, incl. Ollama and OpenAI-compatible | 30+, incl. LM Studio, Ollama, vLLM, LiteLLM | 50+, incl. OpenAI-compatible | | MCP | servers | client | client | | Notable | SOP engine, plugins, hardware | SubTurn/subagents, steering, failover | memory engines, sandboxing, tunnels, pairing | **What they are good for.** A borrowed harness. PicoClaw is the easiest first trial: one Go binary, explicit LM Studio/Ollama/vLLM providers, a failover cascade. NullClaw is the smallest and the most test-covered, and it is the only one that speaks A2A v0.3.0 out of the box. ZeroClaw is the most production-ready: a Cargo workspace with a gateway, a plugin protocol, SOPs and autonomy levels. **The integration that fits the ship.** Federation, not embedding. Run the runtime as an external agent with its own gateway, give it the ship's mind endpoint as a provider, and let a ship's station delegate to it over A2A or HTTP. The ship keeps the watch bill, the ship's log, the intent tree and the bridge; the external runtime keeps the tool loop and its channels. NullClaw's `/a2a` endpoint and PicoClaw's gateway are the natural boundaries. Treating one as a library inside a station fights its design. **The conflict to resolve first.** All three have their own memory. That is a second source of truth beside the ship's standing-orders files and ship's log. If one is used, its memory should belong in the station's own rough log — kept, but not loaded into standing orders — and never promoted without the ship's provenance step. That is the general rule, not a special case for external runtimes: every source belongs in the rough log until promoted, including the station's own memory and any compacted summary. Otherwise the same bad idea gets two places to live. **Android, again.** PicoClaw ships an Android APK. That does not make Android an Erlang node, but it does mean an Android device can host a *consort*, the other role open to a phone beside mind host. The decision stands as "not an Erlang node", because a phone is never a cluster member. **Security.** NullClaw pairs on first use, binds loopback by default, sandboxes tools and encrypts secrets. ZeroClaw has autonomy levels and a plugin protocol. PicoClaw is pre-v1.0 and says not to deploy it to production yet. None of them should be exposed beyond loopback or a trusted tunnel without reading its security model. ## Podman (candidate isolation layer) Podman is Red Hat's daemonless, rootless, OCI-compatible container engine. It runs natively on Linux; on Windows and macOS it runs inside a "Podman machine" — a WSL2 distribution on Windows, a VM on macOS — and the CLI talks to it remotely. It supports pods (a shared-network group of containers), Kubernetes YAML, and Quadlet units for systemd. The CLI is Docker-compatible enough that `alias docker=podman` usually works. Apache 2.0. **Where it fits the ship.** - **Tool sandbox (best fit).** Run a station's shell and file tools in a rootless container with the workspace bind-mounted. On a Linux node this is clean and gives the harness a real boundary without host privileges. On the Windows development host it requires WSL2, which the project has already ruled out. - **Linux node packaging (optional).** A container image plus a Quadlet unit is a tidy way to ship a node on Linux. But an Erlang node needs distribution ports, a fixed hostname for the cert SAN, and persistent volumes; container networking turns a simple release into a networking problem. The portable release stays the default. - **Consorts (easy).** NullClaw, PicoClaw, and ZeroClaw already ship container paths, and Podman can run them rootless. Containers add isolation, not portability, since each is a single binary. - **Mind hosting (Linux GPU only).** A llama.cpp or QVAC container with GPU passthrough works on a Linux box; on Windows it means WSL2 and the NVIDIA Container Toolkit inside the Podman machine. Not for the dev host. **The naming collision.** Podman's "pod" is a group of containers sharing a network namespace. OpenRig's "pod" is a context group, which the ship calls a department. Same word, unrelated. Say "container pod" for Podman and "department" for the ship. **Caveats.** Rootless networking rewrites source IPs by default (passt/pesto is the newer fix), which matters if a tool needs to see the real client address. Bind mounts across the Windows/WSL boundary are slow and have ownership quirks. GPU passthrough is Linux-first. The REST API is Linux-only; the remote client is cross-platform. **What to test.** One rootless container running the station's tool runner against a real sortie's commands, with the workspace mounted read-write; measure file ownership, bind-mount latency, and whether `git` works cleanly. Then a Linux node packaged as a Quadlet unit, joining the cluster over TLS with the host's cert SAN. ## Enrollment and Android Capacity is gained at whatever scope needs it, and the enrolling is done by a station with the effector for it. There are three kinds — nodes, minds and consorts — because the control plane, the mind plane and the consort plane cost differently, and the platform facts decide which is which. **Node enrollment.** A new box joins the cluster with the portable artifact plus a short-lived token: check OTP, install the service wrapper, sign a per-node certificate with the fleet CA, write identity (ship, cookie, seeds, roles), start, register, and health-check over `net_kernel`. The artifact can be an escript or a release; an air-gapped bundle carries the code, CA and config. This is ordinary deployment work on Windows and Linux. **Mind and consort enrollment.** A model mind is an OpenAI-compatible endpoint, a token, and a health probe; it joins the pool, not the cluster. A human mind enrolls as a mind with a console and a delivery channel. A consort is a runtime with its own loop and tools, reached over A2A or HTTP; it gets an address and a capability card, but no Erlang certificate. **iOS is the open question.** QVAC lists iOS as an SDK deploy target, but nothing so far serves `/v1` from an iPhone: the CLI is desktop-only, there is no `adb` equivalent, and the deployment paths are a developer certificate or TestFlight rather than a cable and a shell. A dedicated device can be left on charge like an Android one, and an iPad has more thermal headroom than a phone. Whether that is worth designing for is item 21; what it would not be is an Erlang node, for exactly the reasons Android is not. **Android as a mind or a consort, not a node.** QVAC's CLI and its HTTP server run on desktops only; its system requirements list `darwin-arm64`, `darwin-x64`, `linux-arm64`, `linux-x64` and `win32-x64` as CLI hosts, with Android and iOS as SDK deploy targets. The SDK reaches Android through Expo, and there is a Kotlin SDK for native apps. So an Android mind host is an app that embeds QVAC and serves `/v1` itself; it is not `qvac serve` in a terminal. Android's background rules make it best-effort: a foreground service, charging, screen off, same LAN, thermal throttling, and Doze. Erlang on Android exists but is not a target: OTP can be cross-compiled for Android (the official `INSTALL-ANDROID.md`), and Termux can run a node, but the sandbox and background limits make it experimental. The project that ran HyperBEAM this way calls it "highly experimental... not recommended for production." A phone is a mind host or a consort, not an Erlang node. **USB-provisioned Android.** USB is for deployment, not runtime. The provisioning sequence: enable USB debugging, authorize the PC, `adb install` the app, push its config (ship, mind id, token), grant the foreground-service and battery-optimization exemptions, start it once, then disconnect. The phone joins Wi-Fi and registers its `/v1` endpoint with the mind pool. ADB port forwarding is a debugging convenience while tethered, not the production path; USB tethering is a fallback for a device with no Wi-Fi, not the default. The app, provisioning, and alternatives are specified in [[Low Passage Mind Host]]. **In practice.** Node enrollment is a one-time, privileged action with a certificate and a service; mind and consort enrollment are an endpoint, a human mind's console, or a capability card, plus a token and a health probe. Keeping them separate is what lets a phone or a tablet contribute capacity without pretending to be a cluster member. And because device enrollment is expected to recur, [[Low Passage]] gives it a ship rather than a working party — a tender, whose standing orders are kept per device class. ## What this changed - **v0 uses local minds.** The subscription is not reachable through the endpoint, and no supported external route was found. The mind behaviour keeps that door open. - **The bridge is a separate application.** Bionic is a chat interface and a subscription; the bridge is an operations tool. Opposite failure profiles, and no reason for one to be inside the other. - **Identity checks are mandatory.** Silent substitution is not an edge case. - **Duplicate tool calls need policy.** The probe produced four calls in one turn. - **Cost attribution is token-based, not dollar-exact.** No cached-token field. - **TLS distribution is viable without a VPN.** Self-signed CA, files only. - **Cross-platform, not Windows-native.** Windows is the development host; Linux and VPS stations are expected targets. The platform section in [[Low Passage]] lists the shims to verify before M6. - **Cognition is the expensive part.** A discussion this size is already past 100k tokens. Local models can handle grunt stations; the long-context stations need either a cheaper cloud path or a much smaller working set. - **Minds are not local to stations.** A station is a cheap process; a mind is a chunky shared resource. The adapter addresses a URL, so the model can live on the desktop, a phone, a VPS, or another machine on the LAN, and a pool decides capacity and fallback. - **A 4 GB phone is grunt-mind capacity, measured.** Not from a spec sheet: 1.24B Q4_K_M at 8.25 t/s sustained over twelve minutes, 1.86 GB free after stripping accounts and Play Services. Free memory after stripping is the number that decides, and the device table's 6–8 GB band for a 1B–3B mind is conservative. See [[Device Report — Pixel 3a XL]]. - **A model server with no app behind it does not survive.** llama.cpp's server, run as a shell process, answered for hours and was then reaped by the low-memory killer — silently. Capability needs no app; availability does, and that is what the foreground service is for. - **Cancellation cannot be trusted to the endpoint.** A disconnected client left llama.cpp generating for another three minutes and 1,500 tokens; a clean timeout cancelled at once. The station must enforce a cancel itself, or a stopped turn keeps costing turns. - **Wi-Fi adb is the deployment path, not the fallback.** The USB link dropped its debug interface twice mid-transfer; `adb tcpip 5555` then moved 986 MB at 55 MB/s over the LAN without a stumble. USB is for the bootstrap. ## Next research Ordered by when an item becomes answerable and what it unblocks, not by how interesting it is. The cheap probes come first: they cost one session and decide what gets built. Anything that needs a running fleet waits for the milestone that makes it answerable, rather than for whoever is curious. The device and container work waits for the bench, because it depends on nothing and nothing depends on it. Each item names the question, the test, and what the answer changes. That ordering is also why the first group gates M0: the subscription question alone can flip the foundation. If it is reachable through a supported route, long-context stations are viable and the ship is a coordination layer over rented cognition. If it is not, local-only is the design rather than a compromise, and every station's design follows from the context numbers. Either answer is cheap to get and expensive to assume. ### Before any code — the endpoint, one session Do all six in one sitting against this machine, before any Erlang is written: probe `/v1/models` with each plan selected, load a small, a mid and a large model and run a needle at 32k/64k/128k/256k, stand up `qvac serve --openai` and repeat the same probes. Record the numbers here, not in a scratch buffer. 1. **Bionic Cloud path.** Is the subscription reachable by a station through any supported external API, or only through the app? Test: check the documented API and whether cloud models appear in `/v1/models` when selected. LM Link is inbound only — other devices using this machine — so it is not an outbound path. Changes: whether long-context stations are viable at all. This is the first question, because a no makes local-only the design rather than a temporary compromise. 2. **Context economics.** What is the largest context a local model can hold at usable speed on this hardware? Test: load a small, a mid, and a large model; run a needle-in-a-haystack prompt at 32k, 64k, 128k, and 256k; record tokens/sec and time-to-first-token. Changes: which stations can live locally, and which need the cloud path from item 1. 3. **QVAC as a second local backend.** Does QVAC's OpenAI-compatible server work as a mind adapter, and does it beat LM Studio for grunt stations on Windows and Linux? Test: `qvac serve --openai` with one small model; run the same completion, tool-call, streaming and cancellation probes the local endpoint gets. Changes: whether the ship gets a portable local mind for VPS stations, or stays tied to this desktop, and whether a small pool of remote minds can serve many stations. 4. **Streaming and cancellation.** Does the endpoint stream deltas, does a cancel actually stop generation, and is usage reported at stream end? Test: one streaming completion with a mid-stream abort; compare wall-clock and usage against a non-streamed run. Changes: bridge responsiveness, station timeouts, and cost control. **Partly answered, 2026-11-09 (llama.cpp server):** deltas stream correctly and usage arrives in the final chunk, but cancellation does not reliably stop generation — a client killed with SIGTERM left the server working for another 177 seconds and 1,500 tokens before the socket failed, while a client that timed out cleanly was cancelled inside a second. The station has to enforce cancellation rather than trust the endpoint. 5. **`/api/v0/models` state fidelity.** Does `state` update reliably when a model loads or unloads, and can it drive station readiness? Test: load and unload a model while polling the endpoint; compare against an actual completion. Changes: the readiness check in the mind adapter. 6. **Tool-call reliability across the local set.** Which models call tools correctly, which duplicate calls, which malform arguments? Test: the same five-tool task across the 32 local models, scored on whether the call was well formed and whether it ran. Changes: which models are stations and which are grunts. ### On the first ship — M0 to M6 7. **Bootstrap mind floor.** What is the smallest mind allocation that lets a Commodore and one ship operate without collapsing independent review? Test: one host, one model mind at capacity 1/2/3/4; a Commodore and a starter; measure queueing, review independence, and cost. Changes: the default allocation in `lowpassage flagship init`. 8. **Force design heuristics.** What heuristics choose the smallest station set that still produces independent proof, when does a task earn a ship rather than a working party (the ship test: memory, queue, boundary), and when is adapting a class safe rather than forking one? Test: design three tasks (survey, code change, research) with 1/2/3/4 stations; measure proof quality and handoff cost; then run the same task as a party and as a ship and compare what each retains. Changes: the Commodore's design checklist and the default classes. 9. **Composite aggregation.** Should `scatter` be the default for `call` on composites, or always explicit? Test: a composite with three children under both policies; measure lost replies and latency. Changes: the addressing API. 10. **Episodic binding.** Does a station that acquires a mind only when it has something to think about lose anything measurable, compared with one that holds a mind throughout? Test: run the same watch mission both ways for a week; compare time-to-alert, the quality of the first diagnosis, context tokens spent, and whether a swap ever lost state that mattered. Changes: whether episodic binding stays the default for sensing stations, and whether the specification's claim that a swapped mind loses nothing needs qualifying. 11. **Observed-topology metrics.** Which graph measures correlate with outcomes: degree, centrality, fan-in, approval depth, or something else? Test: compute all of them over a finished ship's message log and compare against its cohort metrics. Changes: what the bridge and the analyst actually display. 12. **Platform parity.** Which parts need shims for paths, file locking, signals, and service startup, and how much of the suite must run on Linux? Test: the M6 handoff on Windows→Linux and Linux→Linux. Changes: the platform matrix, not the milestone. ### On a fleet — M7 to M11 13. **Muster policy.** What caps on stations per department, delegation depth, and working-party lifetime keep a swarm useful without letting it run away? Test: a Commander musters working parties on a real mission; vary the caps; measure task completion, cost, and orphaned stations. Changes: the bosun's default policy. 14. **Mind lifecycle and autoscaling.** What are the load and unload times and memory footprints for QVAC and LM Studio hosts, and what hysteresis keeps a quartermaster from thrashing? Test: load and unload a 0.5B, a 3B and a 7B model on each host; measure time to ready and time to release; run a demand spike against a min/max policy. Changes: the quartermaster's policy defaults and whether a warm pool is required. 15. **Mind-authored tools.** Equipment is proven by running it, so the question is what a second party's run adds: does an independent run on inputs the author did not choose catch defects the author's own run did not? Test: have minds author ten small tools against a fixed task; run each on the author's input, then on an independent station's; then run them all under a hostile input set and compare what each run would have concluded. Changes: how much of the store can be trusted without re-proving, and whether independent runs are worth the turns or only worth it for tools that grant new reach. 16. **Tools in Erlang.** A tool could be a small process or escript sharing the ship's runtime instead of a shell command in a sandbox. Test: one tool as a shell command and one as an Erlang module doing the same job against a real sortie, compared on latency, sandboxing, crash behaviour and what the record can say about the run. Changes: whether in-language tools are ordinary equipment or a coupling the equipment rules exist to prevent. 17. **Clarification traffic as a signal.** Does a repeated question at the same address predict a standing order that needs rewriting, and how often do crews ask rather than assume? Test: log every clarification with its question, target, and blocked decision across one campaign; count repeats per address; compare against later standing-order edits and against crews' own reports of what blocked them. Changes: whether the analyst gets clarification mining as a tool, where the ask-or-assume line sits, and whether `awaiting_clarification` belongs in the cohort metrics. 18. **Raspberry Pi and the SBC class.** What does ingesting a Pi or an equivalent board look like end to end — node enrollment, a mind host on the same board, or a station that calls a mind elsewhere? Test: one 8 GB Pi 5 enrolled by the node sequence, then the same board serving a 3B model as a mind host, measuring load time, tokens per second and thermals under a sustained sortie. Changes: whether the tender's per-class procedure generalises to boards with no display and no battery, and what a device class actually needs to declare. ### When the thing is on the bench — any time 19. **Android mind host.** Can an Android app embedding QVAC serve `/v1` reliably enough to earn a pool slot? Test: a minimal foreground-service app on a physical device, charging and screen off, serving one small model; measure availability, latency and thermal throttling over an hour. Changes: whether M12 is worth building. **Partly answered:** a bare llama.cpp server on a hollowed 4 GB phone sustains 8.25 t/s, so capability was never in doubt. What the app adds is survival — a shell process was reaped by the low-memory killer under pressure, and the port went quiet without a word ([[Device Report — Pixel 3a XL]]). 20. **USB-provisioned Android.** Can a phone be installed over USB, untethered, and run as a Wi-Fi mind host for a day? Test: `adb install` a foreground-service QVAC/Kotlin app, push config, grant battery exemption, disconnect, join Wi-Fi; measure registration, uptime, reconnect after Wi-Fi drops, thermal throttling, and throughput over an hour. Changes: the Android host adapter and the provisioning checklist. **Partly answered:** the device installs, serves over Wi-Fi and survives being untethered; and after the USB link dropped its debug interface mid-transfer twice, `adb tcpip 5555` over Wi-Fi proved the more reliable channel by a wide margin (55 MB/s against a cable that kept failing). The checklist should treat Wi-Fi as the deployment path and USB as the bootstrap. 21. **iOS as a mind host or a consort.** Does anything on iOS serve an OpenAI-compatible endpoint — QVAC's iOS SDK inside an app, PicoClaw if it builds, a Shortcuts wrapper — and what does deployment actually cost without ADB (developer certificate, TestFlight, background execution limits, no sideloading)? Test: one small model in a minimal iOS app on a device left on charge, measured the way the Android item does, including whether it survives a screen lock and an hour of sustained heat. Changes: whether the pool of cheap household capacity is Android-only, and whether an iPhone is another `enroll` class or a dead end. 22. **Hailing a human mind.** How does a fleet reach a person who is not sitting at a console — a chat message, a text, a mail — with a question that carries its decision, options and deadline, and how does the reply come back as a turn? Test: one real clarification routed over a chat channel and one over SMS, with the request, the delivery, the reply and the timeout all landing in the log. Changes: whether a human mind is usable without a dedicated bridge page, and what the effector on a station looks like. 23. **External harnesses.** Can a NullClaw or PicoClaw gateway serve as a consort behind a ship's station over A2A/HTTP, using the ship's mind pool? Test: one NullClaw instance on loopback, one small model, one delegated sortie; compare against a native station on the same sortie, including memory and audit. Changes: whether v0 needs its own harness at all. 24. **Container isolation with Podman.** Does a rootless Podman container give the tool runner a usable sandbox on Linux without breaking file ownership or the workspace? Test: one station's tool commands in a rootless container with a bind-mounted workspace; measure ownership, latency and `git` behavior; then a Quadlet-packaged node joining over TLS. Changes: whether the tool sandbox is a container or a plain process, and whether Linux nodes ship as releases or images. 25. **The floor, across devices.** Where does grunt-mind capacity actually stop — a 3 GB phone, a 2 GB one, a 512 MB stick? One device has been measured and the table above is still extrapolation from it. Test: ingest one device per class — hollowed where the hardware can be spared, implanted where it cannot — and run the same 1B model and a 0.5B at 2K context on each; record tokens per second, peak memory, whether the OS kills the process, and what an implanted device gives up when its owner is using it. Changes: the device floor table, the preflight's refusal threshold, and whether the 512 MB class is station-only. [[Device Report — Pixel 3a XL]] has the method. 26. **Boot discovery over serial.** What is on a board you have never used — does it boot, what OS, what state, what is on its disk — when it has no display and no network? Test: a USB-serial adapter on the UART pins, the console opened on the PC, the boot log captured and read for kernel, distribution, services, disks and any identity the board carries; then a login, then networking brought up from that console rather than a screen. Changes: whether a serial console is enough to do recon on an unknown device, what a device report looks like before the device can even reach the network, and whether the tender needs a serial step for boards that ship with neither display nor working network. It is also the discovery half of the SBC item (18) — you cannot ingest what you have not looked at. **First build task, not research:** M0 in [[Low Passage]] — supervisor, registry, watch bill, router, one stub station, and the kill test. It answers "does the substrate hold" faster than any further reading.