Expand description
The web UI: magi’s queue and run history, readable from a phone.
The terminal is the wrong surface for the two things an operator actually does between runs — file a task and check whether the last competition landed. Both happen away from the desk, so they get an HTTP surface: a handful of JSON routes and three embedded files.
§One binary
index.html, app.css and app.js are compiled in with include_str!.
There is no --assets-dir and no filesystem fallback, because a UI that
reads its own front end from disk breaks the moment the binary is copied
somewhere else — which is exactly what cargo install magi-cli does. No
JS toolchain, no CDN, no remote font: everything the phone needs arrives
from this process.
§No authentication
There is none, deliberately, and the startup log says so. The tailnet is
the security boundary: --bind auto resolves to this machine’s Tailscale
address, so the UI is reachable from the operator’s own devices and from
nothing else. Anyone who can open the URL can file and hold tasks, which is
why binding to 0.0.0.0 is not offered and why the fallback when Tailscale
is missing is loopback rather than every interface.
§Change notification
A phone must not poll a full run list on a mobile link. GET /api/events
is a server-sent stream carrying nothing but two revision numbers — the
newest modification time in the queue and under the runs directory — so the
client refetches only what moved. The browser’s own SSE reconnection covers
a sleeping phone; there is no session to lose.
§Reading state must never take the server down
A corrupt run.json is skipped in the list and explained with a 500 on the
detail route. No handler unwraps a filesystem or parse result: a single bad
file left by a killed run would otherwise turn the whole history into a
blank page.
§Agent-authored HTML, rendered anyway
Everything else here refuses to put API data into the document: app.js
builds nodes and sets textContent, and even an href from a run record is
laundered first. A confirmation panel breaks that rule on purpose - an
agent asking the owner to approve a merge needs a diff and a table, not one
line of prose - and the only reason it is acceptable is that the panel is
never part of this document.
It is served by [question_panel] and [question_asset] and rendered in an
<iframe sandbox> carrying exactly allow-popups and
allow-popups-to-escape-sandbox: no allow-scripts, no
allow-same-origin. So no script in a panel runs, and the frame cannot
reach the parent document, the cookie jar or localStorage. On top of that
both routes send [PANEL_CSP], which denies every network destination, so a
panel cannot phone home through a remote image or a beacon either - the two
things it may load, images and inline CSS, are the two things free
formatting actually needs. Assets come from the question’s own directory and
never from the network, and their content types come from a closed
whitelist, so an agent cannot get markup rendered outside the frame by
naming a file .html.
§A conversation turn is not a filesystem read
Every other route here is disk work, which is why [blocking] exists.
POST /api/talks/{id}/say is the exception: it spawns an agent CLI and
waits tens of seconds for a sentence. It is a plain await holding no lock
and no executor thread, and concurrent turns on one talk are refused rather
than queued - see Ui::begin_talk_turn.
§Independent scheduler
/api/loop starts and controls a detached scheduler. Replacing or closing
the web process does not stop that scheduler or its task/chat workers.
Stop ends scheduling; explicit park additionally asks task workers to stop
at their next node boundary. Updating never requests a park.
Structs§
Enums§
- Bind
- Which address to listen on.
Constants§
- DEFAULT_
PORT - Default port. Chosen high and memorable; nothing else in the fleet uses it.
Functions§
- resolve_
bind - Resolve
--bindto an address, plus a warning when the answer is not what the operator asked for. - runs_
unreadable - Runs on disk under
runswhose state this build cannot parse - almost always a schema bump, occasionally a run killed mid-write. - serve
- Serve the UI independently from scheduler and execution workers. A restart releases its listener before starting the successor.