arizukocomponents › gated

gated

What it is

gated is the gateway daemon. One Go binary that owns messages.db, runs the poll loop that turns inbound rows into agent runs, serves the HTTP API channel adapters register against, and hosts the per-group MCP unix sockets the in-container agents talk back through.

It is the only daemon that runs SQL migrations. Other daemons connect to the same SQLite file but never write to the schema.

Why it exists

Adapters (Telegram, Slack, WhatsApp, web…) need somewhere to post inbound messages and pick up outbound replies. Agent containers need an MCP socket to call tools and submit turns. A scheduler needs a place to drop future messages. All those needs collapse to one writer with one schema.

Without gated nothing has a backend. Adapters cannot register, the poll loop never fires, no container ever spawns, the dashboard reads stale rows, and the schema drifts the moment two daemons each try to migrate it. gated centralises the writes so the rest of the system can stay thin.

The loop

On startup gated opens $STORE_DIR/messages.db, runs every migration in store/migrations/, brings up the HTTP server, and enters the gateway poll loop. Each tick:

  1. Read messages rows newer than the last cursor.
  2. Resolve each row to a group via the routes table.
  3. If the route is #observe, store-only. Otherwise enqueue a container run for that group.
  4. The container talks back over its MCP socket; agent output lands as outbound rows; the adapter picks them up via its registered callback.

How it fits

adapters (teled, slakd, webd, …)
        |  POST /v1/messages          (signed by CHANNEL_SECRET)
        v
      gated  ---->  messages.db  (single writer, WAL)
        |
        |  spawn per-group container
        v
      arizuko-ant container
        |  MCP over unix socket
        v
      gated /v1/outbound  ---->  adapter callback URL

Inputs: HTTP from adapters on $API_PORT; MCP from agent containers on per-group unix sockets under $HOST_DATA_DIR/ipc/<folder>/. Outputs: spawned arizuko-ant containers, outbound rows in the DB, HTTP callbacks back to each registered adapter.

Hard deps: a writable data dir, Docker (for the container runner), and $CONTAINER_IMAGE built and present locally.

Cold-tier resources

Beyond the message bus, gated owns the slow-changing config rows — groups, acl, acl_membership, routes, web_routes, scheduled tasks, secrets, network rules. Each is one Go struct registered with the resreg engine. From that single struct the engine serves four surfaces that cannot drift, because they all read the same fields:

Declared foreign keys keep the rows honest: deleting a group cascades to its web_routes and route_tokens, so URL routes pinned to a removed group go with it. See reference/openapi for the per-daemon endpoints and reference/cli for the manifest commands.

Standalone usage

Yes. gated is a plain Go binary that needs a data dir and an env file. The arizuko CLI generates a compose file that runs gated next to its adapters, but the daemon does not require compose.

# build
make build              # produces ./gated

# seed a data dir
mkdir -p /srv/data/myinstance
cat > /srv/data/myinstance/.env <<EOF
CHANNEL_SECRET=$(openssl rand -hex 32)
STORE_DIR=/srv/data/myinstance/store
HOST_DATA_DIR=/srv/data/myinstance
CONTAINER_IMAGE=arizuko-ant:latest
API_PORT=8080
EOF

# run
cd /srv/data/myinstance && /path/to/gated

gated reads .env from its working directory (via core.LoadConfig), so always cd into the data dir before launching it. GET /health returns 200 once the API server is up and the DB is reachable.

Key env vars

Full list and defaults in reference/env.

Go deeper