The AppSpec model
The AppSpec is the declarative document an app publishes — returned by app.describe. It is what an agent reads first to learn the app's shape: what screens exist, which elements are drivable, what flows are advertised, and what events the app can raise. The same shape works across every participating app, so an agent never has to reverse-engineer the UI or read app-specific bindings.
Screens
A screen is a named view or page within the app. Elements are grouped by the screen they live on, and the screen Id is the navigation target passed to app.navigate.
Id— the navigation target (e.g.settings).Name— operator-visible label.Purpose— operator-visible description of what the screen is for.
Elements
An element is one drivable UI control. Each element declares the actions it supports and whether its value is secret. A secret element is pre-redacted everywhere — read, get, drive, and audit — so its value never leaves the app.
| Field | Meaning |
|---|---|
Key | <screen-id>.<element-id> for screen-scoped controls (e.g. settings.api-host), or app.<element-id> for cross-screen controls such as title-bar buttons. |
Type | The control kind — "TextBox", "Button", "ComboBox", "Link", etc. |
Purpose | Operator-visible description of what the control does. |
Validation | Validation rules the value must satisfy. |
SupportedActions | A subset of {set_value, get_value, invoke, focus, highlight, click_link, press_key}. |
IsSecret | When true, the router pre-redacts the element's value everywhere (read / get / drive / audit). |
Flows
A flow is a named multi-step task declared in the spec — for example, navigate to a screen, set a value, then invoke a button. Spec-declared flows are immutable per app version. (The operator-recorded equivalent, RecordedFlow, is a distinct concept on purpose.)
Events
An AppEvent declares something the app can emit, consumable by an agent via app.poll_events. The spec also lists the app.* action catalogue (each carrying a Risk level that drives consent and ACL gates) and the config keys the app exposes.
Code example
An app builds its spec with AppSpec.Build(...), declaring screens and one AppElement per drivable control:
var spec = AppSpec.Build(
"AI Server", "2.1.0",
screens: new[]
{
new AppScreen("home", "Home", "Landing page."),
new AppScreen("settings", "Settings", "Configure the server."),
},
elements: new[]
{
new AppElement("settings.bind-address", "settings", "TextBox", "Bind address.",
Array.Empty<string>(), DefaultValue: "127.0.0.1",
SupportedActions: new[] { "set_value", "focus" }),
new AppElement("settings.api-key", "settings", "TextBox", "Provider API key.",
Array.Empty<string>(), null,
SupportedActions: new[] { "set_value" }, IsSecret: true),
new AppElement("home.refresh", "home", "Button", "Refresh status.",
Array.Empty<string>(), null,
SupportedActions: new[] { "invoke" }),
});
How an agent reads it
The AppSpec is immutable per app version — the screens, elements, and flows are fixed for a given AppVersion. An agent fetches it by calling app.describe, then drives the app through the rest of the app.* vocabulary. See the MCP tool reference for every tool the spec advertises.