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:

AxisWhere it lives
1. How a run startsThe 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 / platformSegment 2 of the surface — widget/voice, widget/form, messaging/telegram, phone/outbound.
4. Voice style & providerThe participant's config style, provider, hold_timeout.
Family = transport, not modality: Voice is not a family of its own. It is a mode of a transport: widget/voice for the browser and the phone family for telephony. How the voice conversation behaves is the config.style.

Transport families

SurfaceDescription
widgetBrowser chat widget — the default, rich-UI surface.
widget/formBrowser widget rendered as a single form.
widget/voiceBrowser voice. Supports all three voice styles via config.style.
inbox · dashboardInternal operator surfaces for team members reviewing or actioning runs.
phone · phone/outboundTelephony 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.
Platform ≠ family: A platform can appear under more than one family. 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:

styleBehaviour
push_to_talkButton-gated capture. Requires rich UI, so it is available on widget/voice only.
continuousVoice-activity-detected half-duplex turn taking. Works on widget/voice, phone, and meeting.
liveFull-duplex streaming (Gemini Live). Provider is gemini. Works on widget/voice, phone, and meeting.
json
{
  "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:

FieldBehaviour
thinking_budgetPer-turn reasoning-token cap. Defaults to 0 (thinking disabled) on native-audio models — the biggest lever on end-of-speech → response latency.
silence_duration_msServer-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_msMilliseconds of audio retained before detected speech start.
modelVoice model id. Half-cascade live models respond faster than native-audio ones at some cost in expressiveness.
recordingOptional 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.
Gemini Live and Agent converse mode: 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 sourceInterpretation
Gemini Live observationQualitative, model-derived, and uncalibrated. Suitable for bounded observations with explicit uncertainty; not a standardized acoustic or pronunciation score.
Calibrated assessment providerA separate processor with provider-specific score definitions, versioned provenance, and validation evidence. Keep its measurements distinct from Gemini observations.
Retained evidenceExisting same-run timeline, artifact, or recording evidence referenced by opaque ID. Raw audio and unbounded transcript text are not copied into checkpoints.
Recording and privacy responsibilities: Enabling Live converse does not enable recording. Browser recording is opt-in and opens neither the microphone nor Gemini until the configured notice or affirmative consent step completes. Chatterfly stores participant audio and only assistant audio confirmed as played by the widget. Recordings use tenant-scoped access, an explicit retention period, audited URL minting and deletion, and remain independent from qualitative speech assessment evidence.

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:

Surface — inbound, advances the run

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.

Connection — outbound, never blocks

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 litmus test: Ask: does a reply advance the run?If a participant's response is what moves the workflow to its next step, that is a surface. If a node calls out, takes the result, and proceeds immediately, that is a connection (a tool).

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.

Provider identity is not a surface: A widget, form, or voice participant may authorize a Zoho connection, but the surface never selects the provider actor. The connection's static, caller, participant, transferred, or broker authority pins actor, organization, account, region, and scopes independently of the transport. User-attributed mutations require a private run; see the Zoho connection guide.

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 / featureRequiresSeverity
Editor · CreateDocument · ShowDocumentrich_ui_capable (widget, inbox, dashboard) — error
Input: FileUploadsupports_file_upload (rich-UI surfaces + media messaging) — error
Input: Recordrich_ui_capable — error
Input: Locationrich_ui_capable — warning (degrades to text entry)
Form-mode Workflow groupform_capable — error; form groups cannot nest
voice blocks / voice_defaultsvoice_capable — warning (ignored elsewhere)
voice.dtmf keypad fallbacksupports_dtmf — warning
Non-default input variantrich_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

TransportStyles supported
widget/voicepush_to_talk · continuous · live
phonecontinuous · 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.