arizuko › components › webd
webd
What it is
In plain terms, webd is what puts the agent in a web browser. It serves the chat box on a web page and streams each reply back as the agent writes it.
webd is the web channel adapter. It registers with routd as channel web (JID prefix web:) and pushes per-turn agent output to browsers over Server-Sent Events (a one-way live stream from server to browser). It serves three URLs: the public chat widget at /chat/<token>/, the webhook ingest at /hook/<token>, and the authed operator panel at /panel/<folder>. Tokens come from the route_tokens table; legacy /slink/* URLs 301-redirect to /chat/*.
From routd’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.
Why it exists
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, push agent output to the page, and look like one more channel to routd. Splitting that into its own adapter keeps routd free of HTML and puts every web client (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.
The transport
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.
How it fits
browser / curl / MCP client
| HTTP + SSE
v
proxyd (auth for /api/*, /mcp ; strips identity on /chat/*)
|
v
webd --POST /v1/messages--> routd (CHANNEL_SECRET signed)
^ |
| v
+--POST /send---------- arizuko-ant container
(routd calls webd back; webd fans to SSE subscribers)
Inputs: HTTP from browsers and MCP clients; HTTP callbacks from routd (/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 routd’s /v1/messages.
Hard deps: a reachable routd at $ROUTER_URL, and routd’s routd.db (webd’s /api/* chat reads still hit that DB directly today; they migrate to /v1/messages when that ships).
Standalone usage
Yes, as a process — but useless without routd. webd does not store messages; it forwards them. Run it on its own only when pointing at a running routd.
# with routd 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.
Key env vars
WEBD_LISTEN— HTTP listen address (default:8080).WEBD_URL— URL routd should call back on (defaulthttp://webd:8080).ROUTER_URL— where routd is reachable (defaulthttp://routd:8080).CHANNEL_SECRET— HMAC shared with routd 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.
Go deeper
webd/README.md— full HTTP surface, token contract, planned/v1/*routes.- components/route tokens — the public chat + webhook URLs webd hosts.
- concepts/routing — how routd dispatches to the agent.
specs/5/J-sse.md— SSE streams and the chat-MCP transport.