The Web Control Panel
forge docs serve runs a browser control panel for the current repo. Unlike the static site forge docs build produces, this one is interactive: it reads .forge/ live and can act on it — run verify, archive a change, clear a tripped gate, waive a finding.
forge docs serve # http://127.0.0.1:4321
forge docs serve --addr localhost:4000 # pick a port
forge docs serve --cwd ../other-repo # point at another repoShipped inside the binary
The panel is a Svelte app compiled to a static bundle and embedded in the forge binary with go:embed. There is no Node runtime at serve time and no network access — the binary serves the whole app itself.
Bound to loopback by default
serve binds 127.0.0.1:4321, so the panel is not reachable from other machines unless you deliberately override --addr.
--addr can expose a mutating surface
This panel changes state. Binding it to a non-loopback address puts those actions on your network. Keep the default unless you have a specific reason, and never expose it on an untrusted network.
What it can do
Reads are plain GETs. Everything that changes state is a guarded POST.
| Action | What it does |
|---|---|
| Run verify | Runs verify for a change — the same gate as the CLI, breaker included |
| Archive | Promotes a finished change (still blocked unless every task is done) |
| Reset breaker | Clears a tripped verify circuit-breaker |
| Reset budget | Clears the change's spend ledger |
| Waive / reset secscan | Accepts a reviewed secscan finding, or clears findings |
| Promote learning | Promotes continuous-learning candidates into memory |
| Change create / rename / delete / save | Manage change folders and edit their artifacts |
Clearing a gate is a human decision, not a bypass
Resetting the breaker or budget, and waiving a secscan finding, are exactly the human-gated steps the enforcement doctrine expects — a person deciding to continue after looking. The gates still evaluate the same deterministic evidence; the panel just gives you a button instead of a flag. It never lets the agent clear its own gate.
How mutations are protected
Every POST /api/actions/* route passes through the same guard, and all three checks must hold:
- POST only — a
GETcan never trigger an action. - Same origin — the
Origin(orReferer) host must match the server host. A mutation carrying neither header is rejected, not trusted. - Session token — a 16-byte token minted when the server starts. It is runtime-only and never written to disk; restarting
serveinvalidates it.
The token is injected into the served page as <meta name="forge-token" content="…">, and the app sends it back as the X-Forge-Token header. A POST without it fails closed:
$ curl -X POST http://127.0.0.1:4321/api/actions/verify
invalid token # HTTP 403Where it fits
| Surface | Command | Nature |
|---|---|---|
| Web control panel | forge docs serve | Interactive — reads and acts |
| Static spec-viewer | forge docs build | Offline HTML, read-only, publishable |
| Terminal panel | forge panel | TUI, observe-only |
All three render the same .forge/ state, so they never disagree — they differ only in how much they let you do. See the Spec-Viewer Dashboard for the static build.
Working on the panel
The Svelte source lives in web/; the compiled bundle is committed under internal/webui/dist/ because the binary embeds it.
make web # build the bundle into internal/webui/dist
make web-check # type-check (svelte-check)
make web-test # unit tests (vitest)
make web-verify # fail if the committed bundle is stale vs web/srcRebuild the bundle when you touch web/src
Because the bundle is committed and embedded, editing web/src/ without rebuilding would ship an outdated UI. make web-verify catches exactly that, and it runs as part of make verify-all.
Next steps
- Spec-Viewer Dashboard — the static, publishable build.
- The forge CLI — the full
forge docssynopsis. - Enforcement doctrine — why clearing a gate is a human act.
- The session timeline — the events the panel renders.
- Verify circuit-breaker — the gate the panel can reset.