Surfaces & Transports
A surface is the transport a participant communicates over — a browser widget, a phone call, a WhatsApp thread, a video meeting. Surfaces are orthogonal to how a run starts and to how a voice conversation feels. This page explains the model so you can pick the right combination. New workflows default to the widget surface — build first, then change the surface on the root Workflow node if you need voice, phone, messaging, or an internal surface.
The four axes
Channel configuration is described by four independent axes. Keeping them separate is what makes the model predictable:
| Axis | Where it lives |
|---|---|
| 1. How a run starts | The workflow's triggers[] — inbound_message, webhook, cron, or a widget open. Inbound phone calls arrive as a provider webhook. |
| 2. Transport (family) | Segment 1 of the participant's surface — widget, phone, messaging, inbox, dashboard, reserved meeting. |
| 3. Mode / platform | Segment 2 of the surface — widget/voice, widget/form, messaging/telegram, phone/outbound. |
| 4. Voice style & provider | The participant's config — style, provider, hold_timeout. |
widget/voice for the browser and the phone family for telephony. How the voice conversation behaves is the config.style.Transport families
| Surface | Description |
|---|---|
| widget | Browser chat widget — the default, rich-UI surface. |
| widget/form | Browser widget rendered as a single form. |
| widget/voice | Browser voice. Supports all three voice styles via config.style. |
| inbox · dashboard | Internal operator surfaces for team members reviewing or actioning runs. |
| phone · phone/outbound | Telephony over the voice provider (vobiz). Inbound answers a call; outbound dials a number. Continuous and live styles only. |
| messaging/{sms,whatsapp,telegram,slack,discord,teams,email} | Text transports. The reply drives workflow progression; the run suspends between messages and resumes on inbound. |
| meeting/{google_meet,teams,zoom} | Reserved — video meeting transports. Defined but not yet routable. Room-as-single-participant semantics; continuous and live styles only. |
messaging/teams (a Teams DM/channel) and meeting/teams (a Teams video meeting) are different transports that happen to share a vendor.Voice styles
Any voice-capable transport picks a style in its config. The style — not the surface name — determines how turn-taking works:
| style | Behaviour |
|---|---|
| push_to_talk | Button-gated capture. Requires rich UI, so it is available on widget/voice only. |
| continuous | Voice-activity-detected half-duplex turn taking. Works on widget/voice, phone, and meeting. |
| live | Full-duplex streaming (Gemini Live). Provider is gemini. Works on widget/voice, phone, and meeting. |
{
"name": "caller",
"bind": "run_initiator",
"surface": "widget/voice",
"config": { "style": "live", "provider": "gemini", "voice": "Kore" }
}The live style additionally supports latency tuning in config. Defaults are already tuned for snappy turn-taking; override them only when you need different trade-offs:
| Field | Behaviour |
|---|---|
| thinking_budget | Per-turn reasoning-token cap. Defaults to 0 (thinking disabled) on native-audio models — the biggest lever on end-of-speech → response latency. |
| silence_duration_ms | Server-side VAD silence window before end-of-turn is declared. Default 500. Raise for slow, deliberate speakers. |
| end_of_speech_sensitivity | "high" (default) | "low" | "default". How eagerly the provider decides the user finished speaking. |
| start_of_speech_sensitivity | "high" | "low" | "default" (default). How eagerly speech onset is detected. |
| prefix_padding_ms | Milliseconds of audio retained before detected speech start. |
| model | Voice model id. Half-cascade live models respond faster than native-audio ones at some cost in expressiveness. |
| recording | Optional browser Live recording policy. Requires enabled, storage: "chatterfly", WAV format, announcement or affirmative consent, notice text, and a positive retention_days value. External consent is reserved for a future signed host assertion. |
style: "live" (Gemini Live) is a supported runtime for Agent nodes using mode: "converse". The effective participant surface selects one persistent Gemini Live session; do not author a separate Live Agent mode. The participant config owns model, voice, language, and VAD settings, while the Agent owns its goal, turn limit, output schema, and handback policy. The push_to_talk and continuous styles continue to use the turn-based converse runtime. Persistent Live converse remains capability-gated; validation reports a targeted rollout warning when it is unavailable for the selected surface or tenant.| Assessment source | Interpretation |
|---|---|
| Gemini Live observation | Qualitative, model-derived, and uncalibrated. Suitable for bounded observations with explicit uncertainty; not a standardized acoustic or pronunciation score. |
| Calibrated assessment provider | A separate processor with provider-specific score definitions, versioned provenance, and validation evidence. Keep its measurements distinct from Gemini observations. |
| Retained evidence | Existing same-run timeline, artifact, or recording evidence referenced by opaque ID. Raw audio and unbounded transcript text are not copied into checkpoints. |
Connections vs. surfaces
Connections and surfaces are the two ways a workflow touches the outside world, and they are easy to confuse. The distinction is about direction and whether the run waits:
How a participant talks to a running workflow: a widget, a phone call, a WhatsApp thread. When a participant replies on a surface, that reply drives the workflow forward. The run pauses at a human-in-the-loop step and waits for input.
How a workflow calls an external service: an API request, an MCP tool call, sending an email. The workflow fires and continues — it gets a result and moves to the next node without waiting for a human.
The same vendor can be both. A messaging/telegram surface is where a participant chats with the run and their replies advance it; a telegram connection is a bot token a Notify node uses to fire a one-way alert and move on. One waits for a human; the other does not.
Triggers vs. surfaces
A trigger decides when a run begins; a surface decides how a participant talks to it during the run. They are independent. An inbound phone call (delivered as a webhook from the telephony provider) starts a run whose caller participant is bound to the phone surface — but the same workflow could just as easily be started by a cron trigger that places a phone/outbound call.
Hold policy & async
The engine is always asynchronous at its core. Whether a surface can hold a synchronous leg open while it waits for the next step is a capability, not a workflow setting. Text transports gracefully suspend and resume on the next inbound message; phone and live legs end after config.hold_timeout seconds; email is high-latency and never holds a leg.
Node permissions
Node types and features are trait-gated per surface. The save-time validator checks every node against the capability traits of its effective participant's surface and reports errors (must fix) or warnings (feature degrades gracefully):
| Node / feature | Requires | Severity |
|---|---|---|
| Editor · CreateDocument · ShowDocument | rich_ui_capable (widget, inbox, dashboard) — error | |
| Input: FileUpload | supports_file_upload (rich-UI surfaces + media messaging) — error | |
| Input: Record | rich_ui_capable — error | |
| Input: Location | rich_ui_capable — warning (degrades to text entry) | |
| Form-mode Workflow group | form_capable — error; form groups cannot nest | |
| voice blocks / voice_defaults | voice_capable — warning (ignored elsewhere) | |
| voice.dtmf keypad fallback | supports_dtmf — warning | |
| Non-default input variant | rich_ui_capable — warning (degrades to plain prompt) |
The authoritative registry — every surface with its traits, voice styles, config fields, and the full permission table — is served at GET /api/management/surfaces (CLI: chatterfly surfaces, MCP: list_surfaces). Config keys that don't apply to the chosen surface draw a save-time warning.
Per-node overrides
A participant declares a default surface. A Workflow group node may carry a surface override for its subtree — the bare mode formis resolved against the active participant's family (e.g. widget → widget/form) and is only valid on form-capable surfaces. Bare form is never a participant surface. A WorkflowCallnode can override a participant's surface for a single sub-workflow invocation via participant_surfaces, using a full surface id (e.g. messaging/sms).
Support matrix
| Transport | Styles supported |
|---|---|
| widget/voice | push_to_talk · continuous · live |
| phone | continuous · live |
| meeting/* (reserved) | continuous · live |
| messaging/* | text only (no voice style) |
For outbound calls to external services — the other half of how a workflow touches the world — see Connections. For the design rationale and migration details see Workflows & DSL.
