arizuko

arizuko › components › routd

routd

routd picks the folder for each message, checks the action, serves the agent tools and delivers the reply.

routd routes each message, checks it and delivers the reply

routd is the switchboard. A message comes in from any channel. routd decides which group folder it belongs to, checks that the sender is allowed, runs the turn and sends the reply back out.

routd is the routing plane: the state machine of the conversation. It is the only appender of messages, and no other daemon writes to the message log. It resolves the route table, with its topic, sticky and reply rules, to a target folder. It runs the loop that claims each turn and sends it to runed. It delivers the replies of the agent to the channel adapter that the message came from.

routd owns its tables and creates them. It reads no table of another daemon. Data from other daemons, such as identity from authd and session logs from runed, arrives over HTTP.

Without routd, a message has no folder, no check and no tools

An inbound message is text and a sender. Three things must happen before the agent sees it, and routd does all three.

Routing puts the message in a folder

A Telegram DM, a Slack thread and a web chat must each land in the correct group folder. The route table holds the rules. Examples are a rule by topic kind, a sticky thread that stays with one folder, and a reply that follows its parent. Without routd, a message has no folder to run in.

Authorization checks the action

routd is the grant authority. It owns the acl and acl_membership tables. It runs the gate for each action, auth.Authorize, on every request scoped to a folder, over MCP and REST alike. The question is not “who are you”, which authd answers. The question is “can you do this to these parameters”. A handler that resolves a jid or folder binds it to the folder of the caller here, so a request can never reach across folders.

The MCP host serves the agent tools

The agent talks to arizuko over an MCP socket, and routd hosts that socket in its own process with ServeTurnMCP. routd derives the grant rules of the folder and connects the socket to its store and to the channel deliverer. It serves the conversation tools of the agent, such as reply, send and like. runed only bridges the socket into the guest over vsock and runs no MCP server of its own.

Routing, authorization and the MCP host share one daemon because all three need the same data: the message log and the folder of each turn. In three daemons, three copies of that state would drift apart.

routd sits between the adapters and runed

channel adapter (teled / slakd / webd / …)
        │  POST /v1/messages   (service:<daemon> ES256 bearer)
        ▼
      routd   resolve routes   ──► target folder
        │     authz gate       (auth.Authorize on acl / acl_membership)
        │     append message   (sole appender ──► the message store)
        │
        ├── POST /v1/runs ──►  runed   (boots the guest)
        │                       │
        │     agent MCP socket  │ in-process; runed bridges it over vsock
        │   ◄── reply/send/like ┘
        ▼
      channel deliverer (chanreg)  ──► back out to the adapter
Inputs
Inbound messages from channel adapters on POST /v1/messages, each with a service:<daemon> ES256 token. The tool calls of the agent over the MCP socket in the process. Claims of due tasks from timed.
Outputs
Run requests to runed on POST /v1/runs. Delivered replies through the chanreg deliverer to the adapter that owns the JID prefix.
Dependencies
runed at $RUNED_URL to run turns. The JWKS of authd to verify tokens offline: routd verifies and never signs. Channel adapters register their delivery URL and their JID prefixes with POST /v1/channels/register.

routd recovers stranded tasks at startup

At startup, routd re-arms scheduled tasks that a crash left in firing, so a restart during a turn does not strand a cron task. An operator uses the route table and its endpoints to point a channel, a thread or a public /chat/<token>/ URL at a folder. The endpoints are /v1/routes, /v1/web_routes and /v1/route_tokens/*.

routd runs alone but does nothing alone

routd runs as a daemon against its own tables, but alone it does nothing. It has nothing to route until a channel adapter registers, and nothing to run until runed answers. Run it with a reachable runed and authd.

cd /srv/data/myinstance
export DATA_DIR=/srv/data/myinstance
export LISTEN_ADDR=:8080
export RUNED_URL=http://runed:8080
export AUTHD_URL=http://authd:8080
export AUTHD_SERVICE_KEY=$(grep ^AUTHD_SERVICE_KEY .env | cut -d= -f2)
export WEB_HOST=localhost:8080
./routd

Without AUTHD_URL, routd builds no token verifier and its API is open. Use that only for local development, never in production. GET /health returns 200 when the process is up. Turns that stay unclaimed in the loop, or a runed that is unreachable at dispatch, show a problem.

routd does not start guests, sign tokens or serve HTML

runed starts guests, authd signs tokens, and webd serves HTML and SSE. routd routes, authorizes, appends and delivers. It is the conversation plane and nothing more.

Six pages cover the details