**Note** from Bead: Stephenguerin.live Router Bead · [canonical source](https://redfish.acequia.io/guerin/.agents/82bd6fa4-4d5a-435b-9deb-ca930a82aa89/2026-06-13/notes/00-router-bead-brief.md) · session 2026-06-13 · discussion: Talk: Stephenguerin.live Router Bead
**Date:** 2026-06-13. Read-first for bead `82bd6fa4`. Birth-parent: [`7049c694`](https://redfish.acequia.io/guerin/.agents/7049c694-e45b-4116-a887-49e64885ac8e/about.md). Design home: [bead-as-webdav-handler](https://redfish.acequia.io/guerin/.agents/fe7fbaf5-4c47-43b6-be1b-2f6ca15e8bfa/2026-06-11/notes/bead-as-webdav-handler.md).
## The split (bead-first) ``` purpose-built bead (THIS) node runtime (server.js — generic, thin) ───────────────────────── ──────────────────────────────────────── agent.md (operative self) binds :3500 (behind Caddy TLS edge) agent.json (machine twin) ◄── loads agent.json, mounts ./handler.mjs handler.mjs (rung-0 logic) maps incoming HTTP → bead resource, calls handle(req,ctx) names.json (mount table) waits for the response; serves it back ``` The bead is the **source of truth** (config + logic, portable to any animator). `server.js` is **bead-agnostic** — it could load any bead, which is the "one animator, many beads" shape. Build the bead first (done); the runtime is written against the bead's contract.
## The handler contract (what `handler.mjs` exports) ``` handle(req, ctx) -> res | { passthrough: true } req = { verb, path, headers, body, caller } // path is bead-relative ctx = { readBeadFile(rel) -> string|null, beadUri, now } res = { status, headers, body } ``` Rung-0, pure, deterministic. `{ passthrough: true }` tells the runtime to let the static WebDAV layer serve the file/folder face (cost-free). Implemented: `OPTIONS`→capability, `GET/HEAD`→content-negotiated self (`Accept: application/json|text/markdown|text/html`) + passthrough for other paths, `PUT /uploads/`→`202`, `PROPFIND`→passthrough.
## server.js: **resolve to a bead URI and WAIT for the response** (Stephen, 2026-06-13) The runtime is more than `handle()`-in-process. **`server.js` can resolve an incoming request to a *URI of the bead* and wait for the response** — this is what bridges the **synchronous HTTP caller** to **possibly-asynchronous bead handling**: 1. **Resolve.** Map the incoming `(host, method, path)` to a bead resource — internally "redirect" the request to the bead's URI (a `names.json` lookup; the bead URI may be local-origin or a network peer in the distributed-origin mesh). 2. **Dispatch + await.** Hand it to the bead and **block on the answer**: - If `handle(req,ctx)` returns a `res` synchronously → return it. (Fast path, rung-0.) - If the request needs thought, `server.js` deposits it (dock lane) **and waits** for the answer resource to appear under the bead subtree — the answer being produced **out-of-band by a cognition client that connected inward** (WebSocket; node = server, client = the brain). The HTTP caller sees one normal awaited response, not a `202`-then-poll. 3. **Fall back.** If no responder materializes within a deadline, degrade to the honest async answer: `202 Accepted` + `Location` of where the answer will appear. **No request is refused because the brain is asleep — it's answered more slowly, at a lower liveness level.** So the sync and async lanes are **one awaited path**: `server.js` waits; whether the answer comes from rung-0 in-process, from an inward cognition client, or later via the dock, the caller's protocol is identical. The "redirect to a URI and wait" is the unification point — paths-as-event-bus made synchronous-when-possible. ### Where cognition (and any key) lives — NOT here The public host (`server.js` + `handler.mjs`) holds **no cloud API key**. When step 2 needs a model, the **cognition client connects inward** and brings it. If that client uses a cloud model, the **provider API key (BYOK) lives on the client**, off the internet-facing node — [EULA-compliance](https://redfish.acequia.io/skills/EULA-compliance.md) satisfied by construction. A local-model client (rung 1–2) needs no key at all.
## Build sequence (the runtime, when greenlit) 1. **`server.js`** (generic, in a sibling repo or `artifacts/`): bind `127.0.0.1:3500`; load `agent.json`; mount `./handler.mjs`; HTTP → `handle(req,ctx)` with `ctx.readBeadFile` reading the bead tree; serve `res`, honour `{passthrough}` from a static file/folder layer. 2. **Point `:3500` at it** — swap the parent bead's placeholder NSSM service to run `server.js` loading this bead. Verify `https://stephenguerin.live/` serves the agent-face (`OPTIONS`, negotiated `GET`). 3. **WebSocket server** in `server.js` — the inward channel; implement the resolve-and-**wait** bridge (step 2 above): a connected cognition client claims requests and writes answers; `server.js` awaits them. 4. **Mount table** — grow `names.json` into the node's dispatch map so `server.js` can route to *other* beads (the full router-as-bead, handler §8 Q7).
## server.js — BUILT & tested (2026-06-13) Runtime: [`../artifacts/server.js`](../artifacts/server.js) (dependency-free; Node built-ins only). Reference cognition client: [`../artifacts/example-cognition-client.mjs`](../artifacts/example-cognition-client.mjs). **Run:** `node server.js [beadDir] [port]` (env: `BEAD_DIR`, `PORT`=3500, `COGNITION_DEADLINE_MS`=25000). Binds `127.0.0.1` only. **What it serves:** - agent-face via the bead's `handle(req,ctx)` (`ctx.readBeadFile` reads the bead tree); - `{passthrough}` → static file/folder face from the bead dir (else 404); - `GET/POST /ask` (or any `res.defer`) → **resolve-and-wait**: dispatch to a connected cognition client, await its answer, else **202** after the deadline; - `PUT /uploads/*` → persists bytes, `202`, and emits a `deposit` tick to clients; - `GET /_status` → health/JSON. **WS protocol (node = server, cognition client = connector):** server→client `{type:'hello'|'request'|'deposit'}`; client→server `{type:'response', id, res}`. RFC 6455, text frames, dependency-free (client frames masked). **No model, no provider key in server.js** — the client brings cognition + its own BYOK key. **Verified 2026-06-13** on `:3501`: `/_status`✓, `OPTIONS /`✓, negotiated `GET /` (json/md/html)✓, static `GET /README.md`✓, `GET /nope.txt`→404✓, `/ask` with no client→**202**✓, with client connected→**200 + client's answer**✓ (`clients:1`), `PUT /uploads/…`→202 + deposit tick✓.
## Status — LIVE (2026-06-13) **`https://stephenguerin.live/` now serves this router-bead.** The `stephenguerin-live-origin` NSSM service (auto-start, boot-persistent) was re-pointed from the stage-0 placeholder to `node server.js <this-bead-dir> 3500` (runtime staged at `C:\caddy\origins\stephenguerin.live\`). Verified through Caddy: `GET /_status`→200, `OPTIONS /`→200 `application/json` agent card, `GET / (Accept: application/json)`→agent.json, browser→landing; valid TLS. `server.js` holds **no model, no key** — cognition is an inward WebSocket client (rung-0 deterministic by default). **Next (optional):** connect a real cognition client (replace the stub in `example-cognition-client.mjs` with a model call that carries its own BYOK key); grow `names.json` into the node's mount table to route to sibling beads.
## References (bead cross-links) - Bead: Home Port Forward Hosting · [canonical](https://redfish.acequia.io/guerin/.agents/7049c694-e45b-4116-a887-49e64885ac8e/) - Bead: As Webdav Handler · [canonical](https://redfish.acequia.io/guerin/.agents/fe7fbaf5-4c47-43b6-be1b-2f6ca15e8bfa/)