Surface overlays

A surface is an overlay of routes that is active only while a runtime condition holds, and that shadows the built-in route for the same path. Normal routes are registered at a unique (method, path) and built-ins always win; a surface is evaluated by a middleware before route matching, so it can replace a path another module already owns — for example the site module's / — for as long as its condition is true.

The motivating case is first-run onboarding: while no LLM provider is configured, Agent OS serves a setup wizard at / instead of the marketing site. As soon as a provider is configured the overlay disappears, with no module-graph rebuild and no restart beyond the one the wizard itself triggers.

Configuration

Surfaces are declared in config/surfaces/*.yaml:

id: onboarding
when: no_llm            # always | no_llm | llm
priority: 100           # higher wins between matching surfaces
paths: ["/", "/index.html", "/site/"]
page: onboarding.html   # HTML file, relative to this YAML …
# … or, instead of `page`:
# redirect: /platform
Field Meaning
id Identity; a file with the same id replaces the embedded default.
when Condition: always, no_llm, or llm (an LLM is configured).
priority Tie-break when several surfaces match one path (higher first).
paths Path patterns; :param and *rest wildcards, exact otherwise.
page HTML file served verbatim when the surface matches.
redirect 302 target (mutually exclusive with page).

A built-in onboarding surface ships with its page compiled into the binary, so a fresh distribution works with no config. Add a config/surfaces/<id>.yaml (and page: file) to customise it, or to add your own overlays — for example a maintenance page:

id: maintenance
when: always
priority: 50
paths: ["/", "/:page"]
page: maintenance.html

Conditions

Conditions are a small, named vocabulary evaluated against a runtime state snapshot (not a general expression language), so config stays auditable:

Precedence and safety

First-run onboarding

When the server starts without an LLM, the built-in onboarding surface serves a wizard at /, /index.html and /site/. The wizard posts to:

Both endpoints are public by design — the whole point is to let a fresh, unconfigured server be set up. The connect endpoint refuses with 409 once a provider exists, so it cannot be used to silently reconfigure a running server (and it is never shadowed by an overlay).

config/llm.yaml overrides the provider: block of server.yaml at load time; server.yaml itself is never rewritten, so its comments survive.

Admin UI

The admin shell (menu Platform) exposes two pages owned by the host:

Both APIs are admin-gated (the gate opens when auth is not configured) and the pages are never shadowed by a surface.

Disabling

Delete the config/surfaces/<id>.yaml (or change its when) and restart. The embedded onboarding surface is active whenever no LLM is configured. To keep the normal site even in that state, override it with when: llm, which is active only when an LLM exists (i.e. never during first run):

id: onboarding
when: llm

Or simply configure a provider.