App
A generated application surface over your deployment. Declare which agents, workflows, views, and databases it exposes; Swirls composes the interface at deploy time.
What it is. An application over your deployment, generated from the primitives it exposes. You declare what the app is for and what it may see. Swirls composes the layout and renders it with live data.
Use it when you want a portal or dashboard over your system without hand-building a frontend. Chat with an agent, browse a view, kick off a workflow, read a database, all on one page.
Works with agents, workflows, views, and databases through the expose block. Apps render in the cloud dashboard for members of your org.
A deployment already describes a complete system. An app block declares an application over it: a description that says what the app is for, and an expose list that says which primitives it may see. On deploy, Swirls composes a layout from the exposed primitives and your description, then renders it in the dashboard with live data. Add a view block to your files, push, and the app grows a page. The interface stays in sync with the system because it is generated from it.
Declaring an app block
Two things make app syntax different from every other block:
- The name is a quoted string, and hyphens are allowed:
app "client-portal"is valid. - Every field binds its value with a space, never a colon. Write
description "...", notdescription: "...". This applies to everything inside the block, includingbrandfields andexposemembers.
| Field | Required | Description |
|---|---|---|
description | Yes | Quoted string. The generation prompt: what the app is for and who uses it. The more specific it is, the better the layout. |
expose | Yes | Block naming the primitives the app may see. At least one member. |
brand | No | Block with presentation hints: accent and logo. |
agent triage {
label: "Triage"
model: "openai/gpt-4o-mini"
}
workflow refund_request {
label: "Refund"
root {
type: code
code: @ts { return { ok: true } }
}
}
app "client-portal" {
description "Support portal for the Acme account team: chat with the
triage agent, see open tickets, kick off a refund."
expose {
agent triage
workflow refund_request
view open_tickets
database tickets { access read }
}
brand {
accent "#B33A2B"
logo "https://example.com/logo.png"
}
}The expose block
expose is the app's authority boundary. Anything not listed is invisible to the app: the layout cannot reference it, and the app's data layer refuses to fetch it. There are four member kinds, each naming a primitive declared anywhere in your workspace:
| Member | References | The app gets |
|---|---|---|
agent <name> | A top-level agent block | A chat surface bound to that agent |
workflow <name> | A top-level workflow | A launcher with a form derived from the workflow's input schema, plus recent runs |
view <name> | A top-level view | A live table over the view's rows |
database <name> | A top-level database | Read-only tables and record detail |
A database entry may carry an access modifier: database tickets { access read }. read is the default and the only access level today, so the modifier is optional; it exists so the attenuation is visible in the file.
References are validated at compile time against your whole workspace, the same way a view's stream references are. A name that doesn't resolve, a duplicate entry, a missing description, or an empty expose block is a deploy-blocking error.
Brand
brand carries presentation hints, kept deliberately small:
| Field | Description |
|---|---|
accent | Accent color, as a CSS color string. |
logo | URL of a logo image. |
The generated layout applies them; you never specify fonts, spacing, or component styling.
How generation works
The layout is a build artifact, produced once per deploy, not composed on page load. The same deploy always shows the same app.
- Deploy. Your
appblock ships with the deployment like every other primitive. - Generate. After the deploy lands, Swirls composes the layout from your
descriptionand the definitions of the exposed primitives: workflow input schemas, view row shapes, database schemas, agent instructions. The app page shows a generating state in the meantime. - Validate. The generated layout is checked before it is stored. It may only use the built-in component set, and it may only bind to primitives in your
exposelist. A layout that fails validation is regenerated with the errors in context; if generation still fails, the app shows the failure and the reason, and your previous layout keeps serving. - Carry forward. When a deploy changes nothing the app can see, the previous layout is reused as is. The layout regenerates only when the app block or an exposed primitive actually changed.
When a deploy does regenerate the layout, the app page shows a diff against the previous deployment, so you can see what changed and which primitives caused it.
What a layout can contain
Layouts compose from a fixed component set, which is what keeps generation predictable:
- Agent chat for each exposed agent, with session history.
- Tables over exposed views and database tables, with record detail.
- Workflow launchers: an input form derived from the workflow's input schema, and a timeline of recent runs.
- Stat cards and charts summarizing exposed data.
- Text and structure: markdown, sections, tabs, grids.
Viewing your app
Apps live in the Build group of the project sidebar in the cloud dashboard. The list shows each app with its generation status; the app page renders the layout with live data, flowing through the same authenticated APIs as the rest of the dashboard. Apps are visible to members of your org.
Reserved fields
domain and audience parse inside an app block but are rejected by the validator: they are reserved for hosted apps, where an app serves external audiences at its own domain. Files that use them fail loudly with a clear diagnostic rather than deploying with silently ignored fields.