Skip to content
Download PDF

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.

sh
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 repo

Shipped 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.

ActionWhat it does
Run verifyRuns verify for a change — the same gate as the CLI, breaker included
ArchivePromotes a finished change (still blocked unless every task is done)
Reset breakerClears a tripped verify circuit-breaker
Reset budgetClears the change's spend ledger
Waive / reset secscanAccepts a reviewed secscan finding, or clears findings
Promote learningPromotes continuous-learning candidates into memory
Change create / rename / delete / saveManage 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:

  1. POST only — a GET can never trigger an action.
  2. Same origin — the Origin (or Referer) host must match the server host. A mutation carrying neither header is rejected, not trusted.
  3. Session token — a 16-byte token minted when the server starts. It is runtime-only and never written to disk; restarting serve invalidates 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:

text
$ curl -X POST http://127.0.0.1:4321/api/actions/verify
invalid token          # HTTP 403

Where it fits ​

SurfaceCommandNature
Web control panelforge docs serveInteractive — reads and acts
Static spec-viewerforge docs buildOffline HTML, read-only, publishable
Terminal panelforge panelTUI, 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.

sh
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/src

Rebuild 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 ​