Web UI
drydock ui serves a small web app over the broker socket: the same board,
approval gate, diff, and history you get from the CLI, in a browser. It binds to
loopback only and is gated by a one-time token.
Launch
drydock ui # prints: UI ready: http://127.0.0.1:7878/#t=<token>
drydock ui --open # also open it in your default browser
drydock ui --port 8080 # bind a different loopback port (default 7878)
drydock start must already be running; the UI is a thin client over the same
broker socket the CLI uses and keeps no state of its own. Open the printed URL;
the token rides in the #t= fragment, so it is never sent as a query parameter,
written to server logs, or leaked in Referer headers. The page reads it from
the fragment and sends it as a bearer token on every API call.
What's in it
- Board: every live task as a card. Running tasks show turn count, a live cost estimate and the current action; a task awaiting you floats to the top with a prominent approval block showing its broker-metered spend.
- Review: open a task for its Diff and Logs (the agent transcript)
in tabs. Approve push stays disabled until you've opened the diff, the
same review-before-approve gate as the CLI; Deny takes a confirm.
Escor a backdrop click closes the overlay. - Submit: start a task: repo, instruction, agent (
claude/codex/gemini/opencode), and an optional model. The repo URL is validated as you type and recent repos are remembered. - History: past runs from the audit dir: outcome, cost, and duration, each
with its diff and logs. Outcome is
ok (N turns)for a pushed or no-diff run, or a distinctdenied/cancelled/push failed/errorline otherwise, see Push outcomes. The board's "Just finished" rail marks the same split with an icon: ✓ for pushed/ok/no-diff, ✕ for error/push failed, and a neutral∅for denied/cancelled (neither succeeded nor failed: the task just didn't run).
Where the cost figures come from
Two different numbers, deliberately labelled differently.
The push gate shows spent: $X (broker-metered) — the credential gateway's
own metering, computed host-side from the proxied response bodies. That is the
figure to make an approval decision on. Where the lane carries no USD metering
at all (subscription, or an openai_compat lane with no prices) it says so
rather than showing $0.00, and where the broker has no figure — a task resumed
after a restart whose previous process left no terminal row — it says unknown
rather than inventing one.
The running card's ~$0.05 is parsed from the agent's own output stream and
is an estimate, marked with ~. The agent's stdout is untrusted text, so it is
useful as a progress signal and is never presented as measured spend. The
history table follows the same rule: a cost the broker measured is plain, and a
cost that exists only because an agent reported it carries a ?.
On the board, when exactly one task is at a gate: R review · A approve · D
deny. ⌘/Ctrl+Enter submits the form; ? lists the shortcuts.
Trust brief panel
Opening a review renders the task's trust brief above the diff: the same
broker-observed evidence drydock inspect <id> prints, so you can weigh the
diff without leaving the overlay. The panel shows the repo and base commit
(with sensitive / auto-approve chips where set), the runtime (agent,
vendor, model, image), the effective policy (budget, timeout, policy snapshot
hash), egress rules, broker-metered spend, and a diff summary — hash, size,
file/line counts, and any FLAG rows for structurally risky changes
(binaries, symlinks, exec bits, dependency manifests, lockfiles, CI
workflows, git metadata, submodule gitlinks). When an
execution profile
or verification is
configured, its block appears too: overall status, the VMs' capability
posture, and per-command exit codes and durations (the setup block first —
setup runs before the agent).
Everything in the panel is what the broker observed — none of it is the agent's own account of what it did. It is read-only, fetched from the same loopback-only, token-gated API as the diff, and a task recorded before briefs existed simply shows "no trust brief recorded"; the diff still loads.
Security
The server is loopback-only (127.0.0.1) and token-gated: every API
call must carry the token minted at launch. It drives the same broker socket the
CLI does, so the approval gate, audit trail, and egress rules are
unchanged: the UI never widens what a task can reach or push. See the
threat model for the guarantees it inherits.
Every response also carries a strict Content-Security-Policy
(default-src 'self' — no inline script, no external loads, no framing)
plus X-Content-Type-Options: nosniff and X-Frame-Options: DENY. That is
defense-in-depth behind the loopback bind and token, not a substitute for
them.
--no-token removes the gate for a trusted single-user machine. drydock prints a
warning when you use it, because then any local process or web page can submit
tasks, approve pushes, and kill tasks through the server. Don't pair it with
anything that exposes the port beyond loopback.