Nexus runtime — HTTP/RCP surface
The Nexus exposes its capabilities over HTTP/WebSocket. There are two
separate listeners — two planes — never merged: the authenticated
control plane (Workbooks.Web) and the anonymous, GET-only content
plane (Workbooks.PublicWeb). RCP (the Runtime-Connect Protocol) is the
token-authed verb subset of the control plane the thin wbx CLI drives
against a running engine.
Every fact below is anchored at :SRC: a real file:line. Routes are
listed verbatim from the routers; nothing is paraphrased.
The two planes
Each plane is a distinct Bandit listener bound to a distinct port. The
public plane never shares the authed router or its pipeline — that
isolation is the whole point of the split (public_web.ex:1-20).
| plane | router | default port | env to enable | auth | methods |
| control | Workbooks.Web | 4000 | WB_WEB=1 | Workbooks.Auth (see ladder) | all |
| content | Workbooks.PublicWeb | 4001 | WB_PUBLIC=1 | NONE (anonymous) | GET only |
| content (TLS) | Workbooks.PublicWeb | 4443 | WB_PUBLIC_TLS=1 | NONE (anonymous), per-host SNI | GET only |
Ports are overridable: PORT / PUBLIC_PORT / PUBLIC_TLS_PORT
(application.ex:241-243). The desktop daemon (WB_DESKTOP=1) implies
the control plane bound on a fixed desktop port with a single-acceptor
IPv4 listener (application.ex:142-156). All non-desktop planes listen
dual-stack IPv4+IPv6 (application.ex:195-196).
The content plane is anonymous by construction: it has no
Workbooks.Auth plug, only GET clauses (any other method falls to 404),
no Dock / commands / build / agent / secret routes, and the page it serves
carries no call-home script — published content is static bytes only
(public_web.ex:8-20, public_web.ex:181-188).
Auth ladder (control plane)
Workbooks.Auth runs after CORS and before :match=/:dispatch=
(web.ex:15-18). First match wins; it assigns :identity (%{user_id,
tenant_id, session_id}) and :tenant (auth.ex:102-106).
| rung | condition | tenant source | :SRC: |
| public allowlist | path in @public (or /gk/*) | dev fallback (x-tenant else "dev") | auth.ex:28, auth.ex:33-37 |
| desktop token | WB_DESKTOP=1 + matching per-boot token | Workbooks.Desktop.tenant() | auth.ex:62-64 |
| shared-secret | WB_PUBLIC_BEARER set + Bearer <secret> | WB_TENANT (default "local") | auth.ex:67-68, auth.ex:77-85 |
| BetterAuth JWT | valid Guardian-verified bearer | identity.tenant_id from claims | auth.ex:87-95 |
dev x-tenant | unlocked AND single-tenant, no bearer | x-tenant header (else "dev") | auth.ex:51-57, auth.ex:97-100 |
No-credential behavior (auth.ex:51-57): a locked deploy
(WB_PUBLIC_BEARER set) → 401, no fallback. Multi-tenant (unlocked)
→ 401 (isolation can't rest on a spoofable header). Dev / single-tenant
(unlocked) → x-tenant fallback.
Public allowlist — always open, no tenant data (auth.ex:28):
| path | :SRC: |
/health | auth.ex:28 |
/.well-known/workbooks-runtime | auth.ex:28 |
/.well-known/did.json | auth.ex:28 |
/gk/* is not in @public but is treated as public here because the
groundskeeper bridge enforces its own credential and fails closed
(auth.ex:37, web.ex:104).
CORS preflight
The :cors plug runs before Workbooks.Auth so the credential-less
preflight isn't rejected (web.ex:15-16). Headers set on every response:
access-control-allow-origin: *, allow-methods: GET, POST, PUT, DELETE,
OPTIONS, allow-headers: authorization, content-type, x-tenant,
max-age: 86400. An OPTIONS request short-circuits to 204
(web.ex:28-32). Origin is wildcarded because the local engine binds
loopback / a boot-token-gated guest, not the public internet
(web.ex:9-14).
Handshake & infrastructure (control plane, public)
| method | path | does | auth | :SRC: |
| GET | /health | liveness, 200 "ok" | public | web.ex:35 |
| GET | /.well-known/workbooks-runtime | RCP capabilities/handshake doc (JSON) | public | web.ex:109 |
| GET | /.well-known/did.json | the engine's did:web identity document | public | web.ex:669 |
The handshake doc (capabilities.ex:32-44) returns: rcp ("1"),
runtime (app vsn), tenancy ("single" | "multi"), auth (rung
"trusted" | "oidc-jwt" derived from tenancy; issuer; jwks_url),
transports (http, ws both true), and capabilities
[oql, workflow, instances, workbook, agent, library, search, publish,
browse, telemetry]. A client reads this before presenting a credential
to learn the required rung (capabilities.ex:2-23).
OQL, workflow & workbook serving (control plane)
| method | path | body / params | does | :SRC: |
| POST | /oql/parse | raw Org body | parse Org headlines through the OQL kernel | web.ex:147 |
| POST | /api/workflow | {org, input}, ?plan=1 | run (or plan-only) a :workflow: DAG | web.ex:157 |
| POST | /api/workflow/todo | {org} | run a native TODO outline async (202; poll /api/brand-book/:slug) | web.ex:178 |
| GET | /instances | — | list registered Instances for the tenant | web.ex:200 |
| PUT | /w/:id | raw Org body | deploy a Workbook: store its Org under :id (201) | web.ex:206 |
| GET | /w/:id | — | serve the stored Workbook as a webpage (HTML) | web.ex:214 |
| POST | /w/:id/call | {fn, org} | the Workbook's backend: parse=/=tangle=/=validate | web.ex:222 |
| GET | /w/:id/ws | WS upgrade | live bridge WebSocket to the Workbook | web.ex:238 |
| GET | /api/workbooks | — | list stored workbooks | web.ex:805 |
| GET | /api/w/:id/org | — | stored Org source (text/plain) | web.ex:810 |
| GET | /api/w/:id/html | — | server-rendered HTML (OQL.render) | web.ex:816 |
| GET | /docs | — | the document-viewer SPA (HTML) | web.ex:801 |
/api/workflow encode-safes error tuples instead of 500ing, and 500s on
an internal rescue (web.ex:167-172).
Agent runs (control plane)
| method | path | body / params | does | :SRC: |
| POST | /api/run | {system, task, model?, max_steps?, exec?} | start a long-horizon agent run (202) | web.ex:246 |
| GET | /api/run/:id | — | poll a run's status + result + events | web.ex:277 |
| GET | /api/run/:id/stream | WS | live per-tool-step telemetry stream | web.ex:270 |
| POST | /api/ctk/commit | event, ?run=<id> | deliver a human CTK approval into a run | web.ex:293 |
| GET | /api/ctk/review/:id | — | agent polls for a pending review (204 when none) | web.ex:315 |
| GET | /ctk | — | serve the CTK human-in-the-loop shell | web.ex:331 |
| GET | /ctk/*glob | — | serve CTK toolkit files (path-contained) | web.ex:335 |
exec on /api/run is a TRUST grant (host-brokered git/publish tools, OS
workdir for fs tools) — it grants no native execution, and is honored
ONLY for the trusted local case (single-tenant/desktop) or explicit
WB_AGENT_EXEC=1; never for arbitrary/multi-tenant callers
(web.ex:254-262).
Brandnana / brand-book pipelines (control plane)
| method | path | body / params | does | :SRC: |
| POST | /api/run-brand-book | {domain} | run the full brand-book pipeline async (202) | web.ex:342 |
| POST | /api/brandnana-ask | {request} | free-form brandnana agent run async (202) | web.ex:369 |
| GET | /api/brand-book/:slug | — | poll a brand-book run's stage + published URL | web.ex:405 |
| GET | /api/telemetry/:slug | — | per-run telemetry summary (states, tool calls, time, errors) | web.ex:416 |
| GET | /api/telemetry | — | cross-run rollup index | web.ex:730 |
RCP engine verbs for the wbx CLI (control plane, token-authed)
These mirror the escript's local Library.* / Toolkits.* calls but are
token-authed so the thin Rust CLI can drive a running engine. Tenant
comes from the credential, not a path (web.ex:435-437). Working-tree
bytes cross the wire base64'd in the :zip Workbooks.Bundle format
because the engine usually runs in a container on another machine
(web.ex:449-454).
| method | path | wbx verb | does | :SRC: |
| POST | /rcp/build | wbx build | compile a workspace's components → WASM | web.ex:440 |
| POST | /rcp/library/checkout | wbx checkout | borrow a library member (zip back to caller) | web.ex:455 |
| POST | /rcp/library/checkin | wbx checkin | pack a working tree back (zip in) | web.ex:473 |
| GET | /rcp/store | wbx store --list | list stored archive keys | web.ex:492 |
| POST | /rcp/store | wbx store | archive a workspace | web.ex:498 |
| GET | /rcp/fetch | wbx fetch | restore stored bytes (base64) | web.ex:656 |
| GET | /rcp/changes | — | the tenant repo's git log (newest first, 30) | web.ex:641 |
| POST | /rcp/key/:key_id | — | sealed-bundle key release (access-gated, fail-closed) | web.ex:122 |
RCP toolkit verbs (text in/out; task execution stays server-side gated
by WB_TOOLKIT_EXEC, web.ex:510-514):
| method | path | does | :SRC: |
| GET | /rcp/toolkit | list toolkits (text) | web.ex:516 |
| GET | /rcp/toolkit/show | show a toolkit / a skill (?id, ?skill) | web.ex:520 |
| GET | /rcp/toolkit/search | search toolkits (?q) | web.ex:530 |
| POST | /rcp/toolkit/verify | structural + capability checks | web.ex:534 |
| POST | /rcp/toolkit/eval | run a toolkit's eval suite server-side | web.ex:540 |
| POST | /rcp/toolkit/build | build a toolkit (?id, ?which) | web.ex:545 |
| POST | /rcp/toolkit/sign | sign a toolkit for the tenant | web.ex:555 |
| POST | /rcp/toolkit/install | install a toolkit DIRECTORY (zip b64; id-charset + zip-slip guarded) | web.ex:592 |
| POST | /rcp/toolkit/run | run a toolkit task (?id, ?task, args) | web.ex:626 |
| POST | /rcp/kernel/run | run a registered kernel (bytes→bytes, b64) | web.ex:563 |
Library, search, channels & browse (control plane)
| method | path | body / params | does | :SRC: |
| GET | /api/library/:tenant | — | the tenant's access graph (workspaces + members) | web.ex:422 |
| POST | /api/library/:tenant/query | {sql} | cross-workbook OQL query-through | web.ex:428 |
| POST | /api/search/:tenant | {query, mode, workbook?, k?} | semantic ∪ literal search | web.ex:720 |
| GET | /api/channels | — | list configured messaging channels | web.ex:741 |
| POST | /api/channels/send | {channel, peer, text, parse_mode?} | send a message | web.ex:747 |
| POST | /api/channels/approve | {channel, code} | allowlist a peer (DM pairing) | web.ex:768 |
| POST | /api/browse | {url\vert query, mode, as?} | fetch/crawl/search through the configured provider | web.ex:786 |
| GET | /api/browse/search | ?q, ?limit | keyless SERP for the desktop browser | web.ex:89 |
| GET | /api/agents/:slug/system_prompt | — | resident agent persona by slug (default if none) | web.ex:67 |
mode for /api/search/:tenant is one of hybrid (default) / semantic
/ literal (web.ex:723). /api/browse dispatches on mode:
fetch (default), crawl, search; as ∈ json (default) / org
(web.ex:849-872).
Identity, federation & ledger (control plane)
| method | path | body / params | does | :SRC: |
| GET | /.well-known/did.json | — | the engine's self-hosted did:web document | web.ex:669 |
| POST | /api/radicle/:tenant/publish | — | federate the tenant repo over Radicle (rad: id) | web.ex:675 |
| POST | /api/mirror/:tenant | {url} or {forge, repo?, visibility?} | mirror the tenant repo to any git host | web.ex:691 |
| GET | /api/ledger/:slug | — | verify a run's hash-chain + did:key signature | web.ex:713 |
Internal / desktop-forwarded (control plane)
| method | path | body / params | does | :SRC: |
| POST | /internal/secrets/refresh | {env: {KEY: val}} | desktop forwards API keys onto this runtime's env (token-authed) | web.ex:46 |
| GET | /socket/websocket | WS upgrade | the desktop bridge's Phoenix-Channels socket (v2 wire) | web.ex:79 |
| — | /gk/* | forwarded | groundskeeper voice-agent bridge (own credential) | web.ex:104 |
/internal/secrets/refresh is not public — Workbooks.Auth runs first,
so only the local shell holding the per-boot token can set env keys
(web.ex:39-45).
Catch-all
Any unmatched route on the control plane → 404 "not found"
(web.ex:821-823).
Content plane routes (Workbooks.PublicWeb)
Anonymous, GET-only. The app id is resolved from the request HOST via the
Workbooks.Domains registry (registered host wins, else the leftmost DNS
label) (public_web.ex:294-296). Every response carries an
x-served-by: workbooks-runtime header (public_web.ex:190-191) and HTML
bodies get an inline <!-- Served by the Workbooks runtime … --> marker
(public_web.ex:29, public_web.ex:286-292).
| method | path | does | :SRC: |
| GET | /health | liveness, 200 "ok" | public_web.ex:31 |
| GET | /_changes | the app's git log (newest first, 30) + keeper status | public_web.ex:39 |
| GET | /_activity | live agent activity (singleton or crew shape) | public_web.ex:64 |
| GET | /*_glob | serve the host's published static app (clean URLs) | public_web.ex:182 |
Static serving (public_web.ex:215-243): pages are clean-URL
(/learn/workbook → learn/workbook.html), directories default to
index.html, inbound .html URLs 301 to the clean form. Path-traversal
safe: .. segments rejected and the resolved path is contained within the
site dir (public_web.ex:274-284). Non-GET methods never match a get
clause → fall through to 404 (public_web.ex:181, public_web.ex:186-188).
The content plane has no Dock, no /w/:id/call, no commands/build/agents,
no secret access — none of those routes exist here (public_web.ex:8-16).
What is north-star / not yet here
Server-side compute for public apps and the full custom-domain / TLS
provisioning story are intended but not the current shipped behavior. The
public plane today is static content + read-only /_changes and
/_activity feeds.
See also
The Dock & capabilities — the membrane the runtime brokers.
Dock capabilities reference — the capability grants.
wbx CLI reference — the verbs that drive the RCP surface.
runtime/docs/RUNTIME-CONNECT-PROTOCOL.org— the RCP handshake spec (:SRC:for/.well-known/workbooks-runtime).