Web UI¶
agent6 web serves a browser front-end for driving agent6 from a desktop or a
phone: watch a run stream, steer it, approve prompts, answer questions, read the
transcript, and browse, create, run, and watch state machines.
The same UI on a phone (single column, bottom nav):
Run it¶
agent6 web # serve the hub on http://127.0.0.1:8901
agent6 web <run-id> # open a run on load
agent6 web <machine> # open a machine instance on load
--host / --port override the [web] config for one
invocation. Stop it with Ctrl-C.
What you can do¶
- Hub: every run (mode, status, last activity, cost) and machine instance; start new work (run / plan / ask); run an authored machine or create one; prune merged run branches.
- Run dashboard (live over SSE): the task graph, the model's streamed
reasoning, tool calls and results, the event log, the latest commit diff, and a
budget bar. Steer the run, merge its branch, approve
run_commandprompts, and answerask_userquestions inline. - Transcript: the full provider-agnostic conversation.
- Machines: the state overview, the path taken, and the current agent
state's live reasoning. Steer, approve, and answer the current agent state's
prompts inline (same controls as a run); send a message to a waiting machine
(a
pokepayload the next tool reads); and seemachine.notify/end as ephemeral banners and OS notifications. - Config: every setting with its value and source, filterable, click a row to set it. Secrets are never shown.
The layout reflows: multi-pane on a wide screen, a single column with a bottom nav on a phone.
Notifications and installing (PWA)¶
The page installs as an app (a phone home-screen icon or a desktop window). Click
🔔 Notifications on a machine view to grant permission; a machine.notify
message or a machine finishing then pops an OS notification — foreground on any
device, and backgrounded on desktop (a backgrounded phone won't wake, which is
expected). A notification never clears or blocks the send/answer inputs: one
popping mid-type keeps your text and focus. For a phone in your pocket, point the
operator notify hook [machine.notify].on_event (see config.md) at
a push service you already use.
How it talks to the server¶
The page reads the same wire form as agent6 watch --json:
curl -s localhost:8901/api/hub # runs + machines + machine files
curl -s localhost:8901/api/run/<id> # a run's state, as JSON
curl -s localhost:8901/api/run/<id>/transcript # the conversation turns
curl -s localhost:8901/api/machine/<name> # a machine's state, as JSON
curl -s localhost:8901/api/config # effective config (no secrets)
curl -sN localhost:8901/api/run/<id>/events # SSE: a fresh snapshot per change
curl /api/run/<id> returns exactly what agent6 watch <id> --json prints.
Writes are small JSON POSTs (/api/new, /api/run/<id>/{steer,approve,answer,merge},
/api/machine/<name>/{poke,steer,approve,answer}, /api/runs/prune,
/api/config, /api/machine/{create,run}) that only ever drive the typed spawn /
answer-file contracts, never arbitrary execution. A machine's approve/answer/
steer land in the current agent state's per-state dir; poke drops a signal
(with an optional message/data payload) on the instance. The machine name and
every answer id are validated to a single path component, so a request cannot
traverse out of the instance dir.
Remote access (Tailscale)¶
The server binds 127.0.0.1 by default and has no app-level auth. For remote
access, put Tailscale in front of the loopback bind:
agent6 web # keep it on 127.0.0.1:8901
tailscale serve --bg 8901 # HTTPS + WireGuard, reachable on your tailnet
The tailnet (WireGuard) identity is the access control: only devices on your
tailnet reach it, over an encrypted tunnel, and tailscale serve terminates
HTTPS. agent6 itself handles no tokens or passwords.
Binding a non-loopback address exposes the write surface, spawning runs and
answering prompts, to anyone who can reach the port. It is refused unless you
opt in, whether the host comes from [web].host (needs
[web].allow_non_loopback = true) or --host (needs --allow-non-loopback), so
a copied config or command cannot silently expose you. Prefer tailscale serve
in front of a loopback bind over any raw non-loopback bind.