Skip to main content

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:

SlotWhen it is usedSupported actions
outputAfter a step has executednavigate
waitWhile a step is paused for user inputnavigate, 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:

  1. Custom surface: authored by a developer with new Surface(...) and reusable by key.
  2. Node default: selected from the defaults registered for that node type and slot.
  3. Generic default: the final platform fallback. The current built-ins are default.json for output and default.resume-form for 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 dev creates a draft for the active developer session. The draft is removed when the session ends.
  • culvii deploy publishes 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: