Control UI (browser)
The Control UI is a small Vite + Lit single-page app served by the runtime gateway:- default:
http://<host>:18789/ - optional prefix: set
gateway.controlUi.basePath(e.g./fased)
/dashis the compact widget dashboard./chatis the live browser chat surface./agentsis the normal setup workbench: Setup, Models, Channels, Skills, Tools, Memory, Sessions, Services, Tasks (including Coordination), and Files for the selected Agent.- Agent Tasks shows saved work definitions first. Task templates create scheduled tasks; workflow/graph templates create review flows; run history is audit, not the task itself.
/wallet,/mining,/federation, and/marketplaceown their focused runtime workflows./extensionsowns global plugin/runtime lifecycle; channel account routing still belongs in Agents/Channels./notifications,/usage, and/logsare operator monitoring pages./configis Advanced. It now groups raw Config, Debug, and Nodes under one advanced surface.
Quick open (local)
If the Gateway is running on the same computer, open:- http://localhost:18789/ (preferred for browser passkeys and other secure-context checks)
- http://127.0.0.1:18789/ if you explicitly want the numeric loopback host
fased gateway.
Auth is supplied during the WebSocket handshake via:
connect.params.auth.tokenconnect.params.auth.password
fased dashboard opens and copies an auth-ready local URL using a fragment token
(#token=...). The UI stores the token and removes it from the browser URL after
load. If you open the page manually, paste the onboarding token into Control UI
settings. Passwords are accepted for the session but are not persisted.
Device pairing (first connection)
When you connect to the Control UI from a new browser or device, the Gateway requires a one-time pairing approval — even if you’re on the same Tailnet withgateway.auth.allowTailscale: true. This is a security measure to prevent
unauthorized access.
What you’ll see: “disconnected (1008): pairing required”
To approve the device:
fased devices revoke --device <id> --role <role>. See
Devices CLI for token rotation and revocation.
Notes:
- Local connections (
localhost/127.0.0.1) are auto-approved. - Remote connections (LAN, Tailnet, etc.) require explicit approval.
- Each browser profile generates a unique device ID, so switching browsers or clearing browser data will require re-pairing.
What it can do
The Control UI operates the self-hosted runtime without requiring a messaging channel first.- Dashboard: compact widget board for Agents, Usage, Wallets, Mining, and Fased Network state.
- Chat: browser chat for the selected Agent/session, with session switching, model overrides, stats, task/session panels, and streamed tool cards.
- Agents: setup workbench for Models, Channels, Skills, Tools, Memory, Sessions, Services, Tasks, Coordination, and Files.
- Wallet, Mining, Fased Network, Marketplace: focused runtime pages for the actions and records owned by those domains.
- Usage, Notifications, Logs: operator monitoring and recent activity.
- Advanced: Config, Debug, Nodes, exec approvals, raw RPC tools, and update actions.
Tasks and history boundary
Agent > Tasks is the saved-work control surface; domain pages keep authority. Agent > Tasks manages definitions and can open run history. Wallets, Mining, Marketplace, Channels, and Services still own actions that can sign, mine, deliver, send externally, or change external state.- Use Task for scheduled work.
- Use Trigger for external HTTP entrypoints.
- Use Workflow for a multi-step review or procedure.
- Use Graph for the visual editor over the same workflow JSON.
- Use Templates only as starters for saved definitions.
correlationId, and link back to source pages when a domain owns the
action.
Delivery defaults to an announce summary for isolated tasks. Use none for
internal-only runs, or webhook delivery when delivery.mode = "webhook" and
delivery.to is a valid HTTP(S) webhook URL.
Chat behavior
chat.sendis non-blocking: it acks immediately with{ runId, status: "started" }and the response streams viachatevents.- The Control UI subscribes to
sessions.changedand the selected session’ssession.messagestream, so the Sessions panel and active chat can update without waiting for manual refresh or polling. - Re-sending with the same
idempotencyKeyreturns{ status: "in_flight" }while running, and{ status: "ok" }after completion. chat.historyresponses are size-bounded for UI safety. When transcript entries are too large, Gateway may truncate long text fields, omit heavy metadata blocks, and replace oversized messages with a placeholder ([chat.history omitted: message too large]).chat.injectappends an assistant note to the session transcript and broadcasts achatevent for UI-only updates (no agent run, no channel delivery).- Stop:
- Click Stop (calls
chat.abort) - Type
/stop(or standalone abort phrases likestop,stop action,stop run,stop fased,please stop) to abort out-of-band chat.abortsupports{ sessionKey }(norunId) to abort all active runs for that session
- Click Stop (calls
- Abort partial retention:
- When a run is aborted, partial assistant text can still be shown in the UI
- Gateway persists aborted partial assistant text into transcript history when buffered output exists
- Persisted entries include abort metadata so transcript consumers can tell abort partials from normal completion output
Tailnet access (recommended)
Integrated Tailscale Serve (preferred)
Keep the Gateway on loopback and let Tailscale Serve proxy it with HTTPS:https://<magicdns>/(or your configuredgateway.controlUi.basePath)
tailscale-user-login) when gateway.auth.allowTailscale is true. Fased
verifies the identity by resolving the x-forwarded-for address with
tailscale whois and matching it to the header, and only accepts these when the
request hits loopback with Tailscale’s x-forwarded-* headers. Set
gateway.auth.allowTailscale: false (or force gateway.auth.mode: "password")
if you want to require a token/password even for Serve traffic.
Tokenless Serve auth assumes the gateway host is trusted. If untrusted local
code may run on that host, require token/password auth.
Bind to tailnet + token
http://<tailscale-ip>:18789/(or your configuredgateway.controlUi.basePath)
connect.params.auth.token).
Insecure HTTP
If you open the dashboard over plain HTTP (http://<lan-ip> or http://<tailscale-ip>),
the browser runs in a non-secure context and blocks WebCrypto. By default,
Fased blocks Control UI connections without device identity.
Recommended fix: use HTTPS (Tailscale Serve) or open the UI locally:
https://<magicdns>/(Serve)http://localhost:18789/(on the gateway host; preferred for browser auth features)
allowInsecureAuth does not bypass Control UI device identity or pairing checks.
Break-glass only:
dangerouslyDisableDeviceAuth disables Control UI device identity checks and is a
severe security downgrade. Revert quickly after emergency use.
See Tailscale for HTTPS setup guidance.
Building the UI
The Gateway serves static files fromdist/control-ui. Build them with:
ws://127.0.0.1:18789).
Debugging/testing: dev server + remote Gateway
The Control UI is static files; the WebSocket target is configurable and can be different from the HTTP origin. This is handy when you want the Vite dev server locally but the Gateway runs elsewhere.- Start the UI dev server:
pnpm ui:dev - Open a URL like:
gatewayUrlis stored in localStorage after load and removed from the URL.tokenis stored in localStorage;passwordis kept in memory only.- When
gatewayUrlis set, the UI does not fall back to config or environment credentials. Providetoken(orpassword) explicitly. Missing explicit credentials is an error. - Use
wss://when the Gateway is behind TLS (Tailscale Serve, HTTPS proxy, etc.). gatewayUrlis only accepted in a top-level window (not embedded) to prevent clickjacking.- Non-loopback Control UI deployments must set
gateway.controlUi.allowedOriginsexplicitly (full origins). This includes remote dev setups. gateway.controlUi.dangerouslyAllowHostHeaderOriginFallback=trueenables Host-header origin fallback mode, but it is a dangerous security mode.