| type | how-to | |
|---|---|---|
| topic | web-ui | |
| audience |
|
Reyn has two interfaces: the TUI (terminal) and the Web UI (browser). They connect to the same agent and share the same session — you can switch between them freely.
reyn chatThe TUI gives you a > prompt. Type requests, see responses inline. It's the fastest way to start.
In a second terminal:
reyn webThen open http://localhost:8080 in any browser.
The Web UI shows conversation history, running workflow status, and richer output rendering (tables, code blocks, markdown). Use it when you want a more readable view or when sharing a screen.
reyn web --port 9000 # change port
reyn web --host 0.0.0.0 # accept connections from other machines (LAN)Security note: The default
127.0.0.1binding accepts connections from localhost only. A non-loopback bind (like--host 0.0.0.0) refuses to start unless a bearer token is configured (web.auth.tokeninreyn.yaml) — the gateway never exposes itself to the network unauthenticated.
reyn chat --connect attaches a thin terminal client to a reyn web server someone else is running (or one you started earlier) — the server holds the session; your terminal just streams the conversation and relays your input.
reyn web --host 0.0.0.0 --port 8080On the default loopback bind (127.0.0.1), reyn generates a launch token and prints it in the startup URL (http://127.0.0.1:8080/?token=...). On a non-loopback bind, reyn refuses to start unless you've configured web.auth.token in reyn.yaml — copy that token (or the printed URL) before connecting from elsewhere.
pip install reyn[web] # once, for the httpx client dependency
reyn chat --connect http://<host>:8080 --token <secret> [agent_name]agent_nameis optional and picks which agent on the server you attach to (same as localreyn chat <agent_name>).--tokencan be omitted ifREYN_WEB_AUTH_TOKENis already set in your environment.- The connection is plain HTTP + Server-Sent Events (AG-UI) — there's nothing else to open or forward besides the one port.
Replies, tool activity, and status stream in as they happen on the server. A human-in-the-loop prompt (a permission ask, a clarifying question) can be answered from the remote terminal exactly like a local one — your answer is delivered to the server by id, so it lands correctly even with other clients attached to the same agent.
Multiple clients see each other's turns. If two or more clients (a local reyn chat and one or more --connect terminals, or several --connect terminals) attach to the same agent, everyone sees the full conversation — not just the agent's replies. Whoever types a message or answers a human-in-the-loop prompt, every OTHER attached client sees that line too, tagged with who sent it (e.g. user [alice]:) when more than one identity is attached; a single attached client shows the plain line with no tag.
- Same inline CUI, streamed status bar. On an interactive TTY,
--connectrenders the same inline CUI as localreyn chat, including the main status bar —model/agent/cost/ctx%chips and the working indicator — with those values streamed live from the server. (--cui, a non-TTY, or piped output still falls back to the plain console style, exactly like local.) - Status-bar dropdowns, the
taskchip, and pickers are local-only. The streamed chip values render, but opening a chip's dropdown (the cost/context detail, the/modelclass picker, the agent / task tree, the…overflow toggles) shows an empty panel on a remote attach — that detail is session-local and not on the wire. Thetaskchip shows0(the task count is not streamed). A closed-set human-in-the-loop prompt (a permission[y]es/[n]o) is answered by typing the choice on the input line rather than through the ↑↓ region picker. /rewindis a text list, not the picker. Locally/rewindopens an interactive ↑↓ checkpoint picker; over--connectit prints the same checkpoints as a plain text list instead (the picker is a local region, not carried on the wire — rewind with/rewind <seq>).- No local file access.
--connectis a pure transport client — it never touches a local session, workspace, or tool. Everything runs on the server's machine.
- For same-machine thin-client use, prefer a UNIX domain socket instead of a token:
reyn web --uds /path/to/socket— the connection is authenticated by OS peer credentials, no token needed. - Any network bind (anything other than loopback) always requires
web.auth.tokenand runs over TLS (self-signed by default; reyn prints the certificate fingerprint to pin on first connect). - Treat the printed token/URL like a password — anyone who has it can act as the operator.
Both interfaces talk to the same agent. Starting a task in the TUI and then watching it in the browser works — both views update as the workflow runs.
Terminal A Terminal B Browser
────────────── ────────────── ──────────────────
$ reyn chat $ reyn web http://localhost:8080
> write a report (serving...) [live progress view]
- TUI:
Ctrl+Dor/quit - Web server:
Ctrl+Cin the terminal wherereyn webis running
The two processes are independent. Stopping one does not affect the other.
The default interactive reyn chat (any TTY) is an inline CUI — the conversation
prints into your terminal's own scrollback, with a status bar and an input box
below it. There's no separate panel to toggle; the status bar's chips (model,
agent, task, cost, ctx, and a more chip for tool/mcp/skill/pipe/
hook/cron) are always visible and drill down in place.
If the agent halts on a persistent durability failure (extremely rare — a dead
disk or an unwritable state directory), the ALWAYS-VISIBLE status bar shows a
⚠ HALTED — <reason> banner ahead of the usual chips the moment it happens, so
you learn it even if you're not actively typing. The plain --cui renderer
shows the same message in its bottom toolbar. This is a notification only —
the agent has already stopped accepting new operations synchronously, whether
or not you see the banner.
| Key | Action |
|---|---|
Enter |
Send the current prompt — always, even with the completion menu open (it never accepts a suggestion on your behalf) |
Shift+Enter |
Insert a newline |
Ctrl+J |
Insert a newline — the guaranteed-works fallback on any terminal, for pasting or writing a multi-line prompt |
↑ |
On the first line: focus a pending question, else the sent-message queue. Otherwise move the cursor up |
↓ |
On the last line: focus the menu row below the input. Otherwise move the cursor down |
Type / at the start of the input to open the command menu, or : at the start
of the input to open the skill menu — both list everything with nothing else
typed. A : later in the line needs to start a word and be followed by at
least two characters, so an ordinary 12:30 or http://x never opens a menu.
Once a command's name is settled by a space, the menu switches to completing that
command's argument — so /model lists your configured model classes and
/image lists matching files.
Most commands take arguments but have no list to offer. For those, that same
space shows the command's usage line instead — /visibility puts
↳ usage: /visibility on|off <tool|mcp|category> <name> above the input, so you
can see what to type without breaking off to run /help visibility. A usage
hint is only a hint: it takes no keys, so ↑, Tab and Esc keep their normal
meanings while it is up. Commands that take no arguments at all (/cost,
/list, /quit, …) show nothing.
While the menu is open with suggestions in it, it takes over the arrow keys:
| Key | Action |
|---|---|
↑ / ↓ |
Move the highlighted suggestion |
Tab |
Accept the highlighted suggestion |
Esc |
Dismiss the menu. It stays dismissed while you keep typing that same word — start a new / or : word to bring it back |
Enter |
Send what you typed (never accepts a suggestion) |
With the menu closed, ↑, ↓, Tab and Esc all behave exactly as in the
table above. Over --connect, everything that comes from the command registry —
/ command names and usage lines — still works, because every client has that
registry; argument suggestions and : skill completion are silent, since both
read session-local state that is not on the wire.
Press ↓ from an empty input to focus the status bar, then:
| Key | Action |
|---|---|
← / → |
Move between chips (or between sub-bar chips, once more is open) |
Enter |
Open the focused chip's detail view (or, for an actionable one like model, apply the selected row) |
↑ / ↓ |
Navigate rows inside an open detail view; at the top, ↑ closes it and returns focus to the input |
Esc |
Close the open detail view / sub-bar |
| Key | Action |
|---|---|
Ctrl+C |
Cancel the in-flight turn (a second Ctrl+C quits) |
Ctrl+D / Ctrl+Q |
Quit (also /quit) |
Two ways to interact directly with the conversation history, not just the input line — details in feature-map: Textual TUI conversation-pane interaction and AG-UI transport reference:
| Key | Action |
|---|---|
Ctrl+F |
Open the in-conversation search bar. Incremental, case-insensitive substring match; Enter/↑ = older match, Shift+Enter/↓ = newer, both wrapping; Esc closes the bar and returns focus to the input. Searching moves the same cursor Shift+Tab uses, and closing the bar leaves it on the match you found — so Shift+Tab picks up from there |
Shift+Tab |
Move focus into the conversation pane itself, arming a per-entry highlight on the newest entry; Esc returns focus to the input |
c |
(with the conversation pane focused) Enter copy mode — a vim-style text cursor over the content, for selecting and copying part of a reply. hjkl move, v/V select, y yanks, Esc leaves; */n/N search the selection. These are the library's own keys |
Once the pane holds focus (via Shift+Tab), the cursor moves with ↑/↓/PageUp/PageDown/Home/End:
| Key | Action |
|---|---|
Enter / Space |
Copy the cursor's entry text to the clipboard |
r |
Send a bare /rewind (the same as typing it) — not a jump to that specific entry |
The turn_started → turn_settled event pair (consumed by
ChatRenderer.on_chat_event) is the sole signal that drives the
turn-in-progress ("thinking…") indicator — there is no separate, manually
managed status line. An earlier version had one, and it double-displayed
against the event-driven indicator (both showing at once) and could leave an
orphaned blank line behind once cleared. Relying on the single event pair
instead of a second, hand-maintained status line is what keeps the indicator
consistent with the renderer's own state.
Slash commands are documented in the
reyn chatreference.
reyn web hosts several surfaces at once. By default it exposes just what a
browser/CLI operator needs: the AG-UI chat transport, the Web UI
(the browser shell), the REST /api control plane, /health, and
the resource-fetch routes. Two broad machine-integration surfaces —
A2A and MCP — are off by default and must be opted in explicitly:
reyn web --enable a2a # turn on the A2A JSON-RPC endpoint
reyn web --enable a2a --enable mcp # turn on both (repeat the flag per surface)
reyn web --disable api # turn off a surface that's on by defaultThe same toggles are settable in reyn.yaml under web.surfaces (see the
reyn.yaml reference § web.surfaces)
so an operator running the same project repeatedly doesn't need to repeat
the CLI flags every launch. A --enable/--disable flag on the command
line always wins over the config file.
The web server can also expose an A2A JSON-RPC endpoint for programmatic access and agent-to-agent communication — opt-in, start the server with --enable a2a (see above):
reyn web --enable a2aPOST http://localhost:8080/a2a/agents/<agent-name>
This is useful for scripting, CI pipelines, or connecting Reyn to another agent system. See concepts/a2a for the protocol details.
Authentication applies here too. The A2A, MCP, and REST (
/api) surfaces are gated by the same transport-tier auth as the browser / thin client: on a non-loopback bind they require the token (?token=orAuthorization: Bearer); a same-machine UDS bind uses OS peer credentials instead. See thereyn webreference § Authentication.
Port already in use
ERROR: [Errno 48] Address already in use
Another process is on port 8080. Use --port to pick a different one:
reyn web --port 8081Can't connect from another device
By default the server binds to 127.0.0.1 (localhost only). Run with --host 0.0.0.0 to accept LAN connections, and configure web.auth.token in reyn.yaml first — a non-loopback bind refuses to start without one.
--connect says "authentication required / rejected by the server"
Pass --token <secret> (the token reyn web printed on launch, or your configured web.auth.token), or set REYN_WEB_AUTH_TOKEN in the environment.
A2A / MCP requests 404
A2A and MCP are off by default (secure-default; see Choosing which surfaces are hosted above). Start the server with --enable a2a / --enable mcp, or set web.surfaces.a2a.enabled: true / web.surfaces.mcp.enabled: true in reyn.yaml.
- Reference: CLI / chat — TUI slash commands,
--connect/--tokenflags - Reference: AG-UI transport — the wire protocol
--connectand the browser both use reyn.yamlreference § web.surfaces — the full per-surface secure-default table and precedence rules- Reference: reyn.yaml § web.auth — token / TLS / transport-tier config
- Concepts: A2A — agent-to-agent protocol