arizuko › components › webd
webd is the web channel adapter. It registers with gated as channel web (JID prefix web:), serves the public chat widget at /chat/<token>/ + the fire-and-forget webhook ingest at /hook/<token> plus the authed operator panel at /panel/<folder>, and pushes per-turn agent output to browsers over Server-Sent Events. Tokens come from the route_tokens table; legacy /slink/* URLs 301-redirect to /chat/*.
From gated’s point of view it is a normal channel adapter, same protocol as teled or slakd. From a browser’s point of view it is an HTTP server with one open SSE stream per active chat.
Telegram and Discord come with their own clients. The web does not. To talk to an arizuko group from a browser, something has to host HTML, accept a POST, fan agent frames out to the page, and look like one more channel to gated. Splitting that out into a dedicated adapter keeps gated free of HTML and keeps every chat surface (built-in widget, JS SDK, third-party MCP client) on one code path.
Without webd there is no browser chat, no slink widget, no operator MCP-over-HTTP. The dashboard would still work because dashd is a separate daemon, but end users could only reach the agent through a platform adapter.
Web chat runs over plain HTTP, no WebSockets. The browser POSTs a message and gets back a turn_id. It then opens an SSE stream at the round’s URL and reads event: message frames as the agent produces them; the stream closes on event: round_done. Dropped connections reconnect with Last-Event-Id and replay missed frames.
browser / curl / MCP client
| HTTP + SSE
v
proxyd (auth for /api/*, /mcp ; strips identity on /slink/*)
|
v
webd --POST /v1/messages--> gated (CHANNEL_SECRET signed)
^ |
| v
+--POST /send---------- arizuko-ant container
(gated calls webd back; webd fans to SSE subscribers)
Inputs: HTTP from browsers and MCP clients; HTTP callbacks from gated (/send, /typing, /v1/round_done) authenticated by CHANNEL_SECRET; signed identity headers forwarded from proxyd under PROXYD_HMAC_SECRET. Outputs: HTML pages and SSE frames to the browser, signed POSTs to gated’s /v1/messages.
Hard deps: a reachable gated at $ROUTER_URL, and the same SQLite file gated owns (webd’s /api/* chat reads still hit the DB directly today; they migrate to /v1/messages when that ships).
Yes, as a process — but useless without gated. webd does not store messages; it forwards them. Run it on its own only when pointing at a running gated.
# with gated already up on the same data dir
cd /srv/data/myinstance
WEBD_LISTEN=:9001 \
WEBD_URL=http://localhost:9001 \
ROUTER_URL=http://localhost:8080 \
CHANNEL_SECRET=$(grep ^CHANNEL_SECRET .env | cut -d= -f2) \
WEB_HOST=localhost:9001 \
/path/to/webd
On startup webd calls POST $ROUTER_URL/v1/channels/register with name web, caps send_text + typing, and its own callback URL. If registration fails the process exits. GET /health returns 200 once registered.
WEBD_LISTEN — HTTP listen address (default :8080).WEBD_URL — URL gated should call back on (default http://webd:8080).ROUTER_URL — where gated is reachable (default http://gated:8080).CHANNEL_SECRET — HMAC shared with gated for /v1/* + /send + /typing.PROXYD_HMAC_SECRET — verifies identity headers forwarded by proxyd for /api/*, /x/*, /mcp.WEB_HOST, ASSISTANT_NAME — rendered into the chat widget and issued URLs.Full list and defaults in reference/env.
webd/README.md — full HTTP surface, token contract, planned /v1/* routes.specs/5/J-sse.md — SSE streams and the chat-MCP transport.