Surfaces overview
A surface is a versioned UI definition authored alongside workflows and agents. It describes how the console can render a workflow step's output or a paused step without embedding UI code in the workflow.
Surfaces are regular @culvii/kit resources. You create them in .culvii.ts files and the same
culvii dev and culvii deploy commands that collect workflows and agents also collect surfaces.
There is no separate surface command.
Surface slots
Every surface belongs to one slot:
| Slot | When it is used | Supported actions |
|---|---|---|
output | After a step has executed | navigate |
wait | While a step is paused for user input | navigate, node.resume, node.stop |
Assigning a wait surface through the Kit automatically sets the step's
params.pauseAfterExecution to true. Assigning an output surface to the wait slot, or a wait
surface to the output slot, is rejected.
Three surface sources
A workflow step can use one of three surface sources:
- Custom surface: authored by a developer with
new Surface(...)and reusable by key. - Node default: selected from the defaults registered for that node type and slot.
- Generic default: the final platform fallback. The current built-ins are
default.jsonfor output anddefault.resume-formfor waits.
The execution response gives the renderer all candidates at once:
customSurface
→ fallbackSurface
→ remainingSurfaces
→ defaultJsonSurface
The renderer tries them in that order. If the custom definition is unavailable, inactive, invalid,
or the caller does not have ui:view, customSurface is null and rendering starts from the
fallback.
Scope, drafts, and versions
- A surface key is unique within a tenant.
culvii devcreates a draft for the active developer session. The draft is removed when the session ends.culvii deploypublishes a new immutable version when the definition changes.- An unpinned reference follows the current published version, or the active session draft during development.
- A pinned reference keeps using its explicit version.
- An inactive surface family cannot render any version; the renderer receives fallbacks instead. This is a whole-family on/off switch, not a per-version one; see below.
Unlike workflows and agents, there's no per-version "active" concept for surfaces. A workflow or agent can have several deployed versions but only one active at a time, and activating a new one deactivates the old. Surfaces don't work that way: while the family is active, every deployed version stays independently resolvable, pinned or not, no matter how many newer ones ship after it. What a surface does have is that one family-level switch: turn the whole family inactive and every reference to it, pinned or unpinned, stops resolving to any version and falls back to the default surface instead. See Workflow and agent lifecycle for the contrast.
Next: