What “voice control” means here
While a call is live, the agent can:- Open a form (optionally with pre-filled values) when the conversation calls for it
- Read every field value as the user fills it in — debounced ~250 ms
- Guide the user verbally (“It looks like your email is incomplete”)
- Advance a stepper or skip a step based on what the user said
- Receive a confirmation when the form is submitted, so it can continue the conversation naturally
Lifecycle
Topics
| Topic | Direction | When | Payload type |
|---|---|---|---|
form.{form-id} | Agent → Widget | Agent opens the form | Pre-fill values object |
form.state | Widget → Agent | Every ~250 ms while open; once on close | form_state |
form.state | Widget → Agent | Submit failure | form_submit_failed |
voice.user_text (default) | Widget → Agent | After successful submit | {form_id}_submitted |
voice.agent_handoff | Agent → Widget | Mid-call agent transfer | { agent_name } |
Agent opens the form
The LLM calls a tool named after the form’sid:
form.book-demo (hyphens preserved here) |
| Payload | { "name": "Alice", "email": "alice@acme.com" } |
Any argument matching a field name is pre-filled. Empty arguments ({}) open the form blank.
To trigger this from the system prompt
Live state from widget to agent
Topic:form.state. Published every ~250 ms while open:
| Field | Meaning |
|---|---|
is_open | true while panel visible; false once closed |
step_index / total_steps | Stepper position (0-based). 0 / 1 for single-page |
values | Current field state, keyed by field name |
fields | Schema of fields on the current step only — labels, types, options, required flags |
fields to phrase contextual prompts (“What’s your last name?” rather than “next field?”), and values to detect blanks or invalid entries.
Prompt instructions that play well with form state
Submission confirmation
Topic:voice.user_text (default — overridable per form via confirmation_topic).
text as user speech and continues naturally — e.g. “Perfect, I’ve got your details. Someone will reach out to alice@acme.com before June 15th.”
Override the type if your prompt keys off a specific event:
Submission failure
Ifsubmit_url returns non-2xx or the network fails, the widget publishes:
| Topic | form.state |
Implementing the protocol in a custom UI
If you’re building your own front-end without the widget, subscribe to the same topics from the LiveKit room:Publish state while the user fills the form
Publish confirmation after submit
On close without submit
Send one finalform.state with is_open: false:
Customising data-channel behaviour
Override topics and event types per form:| Field on form def | Default | Purpose |
|---|---|---|
topics | [] | Extra topics that should also open this form |
confirmation_topic | "voice.user_text" | Where to publish submission confirmation |
confirmation_type | "{form_id}_submitted" | type field on confirmation payload |
Related
- Defining the form schema: Form Handling
- Pre-filling fields from metadata or tool args: Form Pre-fill
- All data-channel topics summarised: Widget → Events
