reyn web¶
Start the Reyn FastAPI gateway server (HTTP + SSE).
Synopsis¶
Options¶
| Flag | Default | Description |
|---|---|---|
--host HOST |
127.0.0.1 |
Interface to bind. Use 0.0.0.0 for LAN access. |
--port PORT |
8080 |
TCP port. |
--reload |
off | Hot-reload on source changes (development only). |
--log-level LEVEL |
info |
critical / error / warning / info / debug / trace. |
--default-design SLUG |
unset | Sets REYN_WEB_DEFAULT_DESIGN for the OpenUI shell. |
--enable SURFACE |
unset | Opt a surface in (repeatable — --enable a2a --enable mcp). Surfaces: agui / webui / health / api / resources (secure-default ON), a2a / mcp (secure-default OFF). |
--disable SURFACE |
unset | Opt a surface out (repeatable). Precedence: --enable/--disable > gateway.surfaces config > secure-default — see reyn.yaml § gateway.surfaces. |
Requirements¶
FastAPI/Uvicorn/Starlette/websockets are core dependencies (#5051) — no separate install step. Exits 1 with an install hint if one is missing anyway (a stale environment installed before #5051).
Endpoints¶
| Path | Protocol |
|---|---|
/agui/chat/<name>/events |
AG-UI chat stream over SSE (server→client) — the single UI transport for the local CUI, the remote thin client, and the openui browser |
/agui/chat/<name> |
AG-UI turn submit / HITL answer / cancel / seize / heartbeat (client→server POST) |
/a2a/agents |
A2A agent discovery — opt-in, --enable a2a |
/a2a/agents/<name> |
A2A JSON-RPC 2.0 per agent — opt-in, --enable a2a |
/mcp/sse, /mcp/messages |
MCP-over-SSE — opt-in, --enable mcp |
/api/* |
REST (agents / skills / runs / budget / permissions) |
/agents/<name>/tool-results/<artifact> |
HTTP fetch for path_ref bodies (resources) |
A2A and MCP are broad machine-integration ports (peer agents / external LLM
clients reaching into this process) and are off by default — every other
surface above is on by default. See --enable/--disable and
reyn.yaml § gateway.surfaces.
Authentication¶
Every functional surface is authenticated by the same transport-tier model —
not just the AG-UI chat routes. A request to /api/*, /a2a/*, /mcp/*, or a
resource-fetch route is resolved to a connection identity before it reaches the
handler; an unauthenticated request is refused with 401. Authentication is
uniform across the surfaces (one operator token), so the same token that drives
the browser / thin client also authorizes the REST control plane and the A2A /
MCP surfaces.
The tier determines what is required:
- Same-machine UDS (
--uds PATH) — identified by OS peer credentials; no token needed. - Loopback / network TCP — the bearer token is required, presented as
?token=<secret>or anAuthorization: Bearer <secret>header. A non-loopback bind refuses to start withoutgateway.auth.tokenconfigured (fail-closed); a loopback bind generates an ephemeral token at startup and prints it in the launch URL.
Open (unauthenticated) surfaces are only the non-sensitive ones: the OpenUI
shell assets (/, /static/*, /web/designs/*) — the browser loads them
before it has the token, then supplies the token on the API calls it makes —
and /health. Webhook plugin routes (/webhook/*) do their own HMAC
verification and are not double-gated. A CORS preflight (OPTIONS) is answered
without a token.
See reyn.yaml § gateway.auth for the token / TLS / transport-tier configuration and AG-UI transport for the chat surface's per-handler details.
Event-loop tripwire (always on)¶
The server runs the same loop watcher the inline CUI has had since #3539
(reyn.runtime.loop_tripwire, lifted out of the CUI by #5898): a 50 ms tick
that measures how late the event loop wakes up. A tick more than 250 ms late
is a stall — one WARNING line on reyn's own log per stall episode
(reyn:web: the interface was unresponsive for N.Ns …) and one more when it
recovers. When a log file handler is installed, a per-tick faulthandler
dead-man's switch also dumps the main thread's Python stack into that log,
mid-stall — so "what was the server doing" is answered by the process
itself, not reconstructed afterwards with sample. No setting is needed;
REYN_PROF_DUMP=<path> additionally records a detail trace (see the module
docstring). Nothing here changes the loop's behaviour: it is observation only.
Why it exists (#5894 ②): the loop was blocked for 7 minutes by CPU work
proportional to the size of the conversation history (token counting, JSON
serialisation, hashing), during which no HTTP request returned and no
server-side record was written. #5898 moved those stages off the loop
(asyncio.to_thread) — a request during one of them now returns — and armed
this watcher so any remaining stall is visible with the shipped config.
A related second-order effect is also closed: a client's heartbeat is
timestamped at the server's receive time, so a server stall longer than the
liveness timeout used to read as every client going silent. The sweeper now
credits its own lateness (SurfaceManager.sweep_dead(stall_credit_s=…)) —
a client that kept sending while the server was blocked stays attached.
Exit codes¶
| Code | Meaning |
|---|---|
0 |
Server stopped cleanly (Ctrl+C). |
1 |
Missing extras or bind error. |