arizuko

arizukocomponents › 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

Full list and defaults in reference/env.

Go deeper