Skip to main content

Collect structured data with LiveKit Agents

Voice and chat agents are increasingly used for workflows that aren't open-ended: qualify a lead, run patient intake, gather booking details, confirm a service request, follow up with a survey. The goal is not to have a freeform conversation; it's a structured record you can act on at the end of every call.

Prompt-only agents struggle with this use case. To reliably collect a defined dataset and submit the result somewhere deterministic, developers usually end up stitching together tool calls for structured extraction, end-of-call detection, and result delivery.

Guided data collection flows are now a first-class pattern with LiveKit Agents, and there are two ways to build one:

  1. In code, using Tasks and TaskGroups in the Agents SDKs.
  2. In your browser, using Data collection mode in Agent Builder.

Both paths produce the same kind of agent: one with a defined objective that keeps the conversation on track, captures the user's responses, and yields control when the work is done. Agent Builder compiles down to the same Tasks and TaskGroups primitives the SDKs expose, so picking one path now doesn't lock you out of the other later.

Path 1: Tasks and TaskGroups in the Agents SDKs

AgentTask is the primitive for structured conversational work in the Python and TypeScript Agents SDKs. A task is a focused, reusable unit that takes control of the session, completes a specific objective, and returns a typed result to the agent that started it. Tasks have full support for tools, just like an agent.

Here is a simple task that asks for recording consent and returns a boolean:

1
from livekit.agents import AgentTask, function_tool
2
3
class CollectConsent(AgentTask[bool]):
4
def __init__(self, chat_ctx=None):
5
super().__init__(
6
instructions="""
7
Ask for recording consent and get a clear yes or no answer.
8
Be polite and professional.
9
""",
10
chat_ctx=chat_ctx,
11
)
12
13
async def on_enter(self) -> None:
14
await self.session.generate_reply(
15
instructions="""
16
Briefly introduce yourself, then ask for permission to record
17
the call for quality assurance and training purposes.
18
Make it clear that they can decline.
19
"""
20
)
21
22
@function_tool
23
async def consent_given(self) -> None:
24
"""Use this when the user gives consent to record."""
25
self.complete(True)
26
27
@function_tool
28
async def consent_denied(self) -> None:
29
"""Use this when the user denies consent to record."""
30
self.complete(False)

An agent runs the task by awaiting it, and uses the returned value to decide what to do next:

1
from livekit.agents import Agent
2
3
class CustomerServiceAgent(Agent):
4
def __init__(self):
5
super().__init__(
6
instructions="You are a friendly customer service representative."
7
)
8
9
async def on_enter(self) -> None:
10
if await CollectConsent(chat_ctx=self.chat_ctx):
11
await self.session.generate_reply(
12
instructions="Offer your assistance to the user."
13
)
14
else:
15
await self.session.generate_reply(
16
instructions="Inform the user that you cannot proceed."
17
)

For multi-step flows, the framework provides TaskGroup, an ordered sequence of tasks that share conversation context, support backtracking to earlier steps, and return all their results together when the group finishes:

1
from livekit.agents.beta.workflows import TaskGroup
2
3
task_group = TaskGroup(chat_ctx=self.chat_ctx)
4
5
task_group.add(
6
lambda: IntroTask(),
7
id="intro_task",
8
description="Collects name and introduction",
9
)
10
task_group.add(
11
lambda: CommuteTask(),
12
id="commute_task",
13
description="Asks about commute flexibility",
14
)
15
16
results = await task_group
17
task_results = results.task_results
18
# {
19
# "intro_task": IntroResults(name="...", intro="..."),
20
# "commute_task": CommuteResults(can_commute=True, commute_method="subway"),
21
# }

TaskGroups handle the messy parts of real conversations. If a user wants to correct an earlier answer mid-flow, the group can regress to that task while keeping the rest of the context intact. When the group finishes, its conversation is summarized and passed back to the controlling agent so the session continues naturally.

For common patterns, the framework also includes prebuilt tasks like GetEmailTask, GetAddressTask, GetDtmfTask, and WarmTransferTask, tuned for noisy voice transcription and the formats they collect. Drop them into a TaskGroup alongside your own tasks for reliable collection without writing the field-handling logic yourself.

Use Tasks and TaskGroups when you want fine-grained control, need to reuse the same collection step across multiple agents, or are composing structured collection into a larger code-first agent.

Path 2: Data collection mode in Agent Builder

Data collection mode in Agent Builder is a browser-based experience for the same use case. Instead of scripting questions or wiring up flowcharts, you declare fields: pieces of information that the agent needs to gather before the session ends. Fields can map to a one-turn answer or take a full sub-conversation. Fields can return scalars, nested objects, or arrays. An attendees field, for example, can yield an array of attendee records, each with their own sub-fields.

Based on these inputs, Agent Builder composes the underlying TaskGroups for you. You focus more on the outcome of the conversation and less on the nitty-gritty details of how your agent asks questions.

How it works

When you create a new agent, you pick a conversation type: Open ended or Data collection. The choice changes the Agent Builder layout and defaults, but the agent underneath is the same; you can still export, deploy, and extend it the same way.

Selecting a conversation type when creating a new agent in Agent Builder.

After selecting Data collection mode, you can either start from scratch or pick from a prebuilt template (patient intake, lead qualification, or customer feedback). Each template comes with suggested fields that you can edit, reorder, or replace to meet your needs.

Editing fields for a Data collection agent in Agent Builder.

The Agent Builder UI maps directly to the behavior the agent will follow: an overall instructions layer, an optional welcome step, your ordered list of fields, and an ending step that handles result delivery and session termination. Authoring the agent feels like walking through the conversation.

Result delivery is part of the feature

A data collection agent is only useful if the output can leave the conversation in a structured way. At the end of the flow, Agent Builder submits a structured payload to the configured endpoint. A representative response looks like this:

1
{
2
"body": {
3
"job_id": "AJ_qsV4cAeZCDPF",
4
"room_id": "RM_hNJarY6HwLaY",
5
"room": "p_1m03vlvq2ym:ab_542mhzi1w12:preview-ysuY",
6
"started_at": "2026-03-24T00:19:44.514403Z",
7
"ended_at": "2026-03-24T00:21:02.729760Z",
8
"summary": "The caller followed up with a customer one week after service...",
9
"results": {
10
"customer_identification": {
11
"customer_full_name": "John Doe",
12
"service_order_number": "334"
13
},
14
"service_type": {
15
"service_type": "plumbing"
16
}
17
}
18
}
19
}

The results object is what you build against. Map it to your intake form, CRM, database, or whatever downstream system owns the data.

Configuring the call ending step for a Data collection agent in Agent Builder.

You can also generate a call summary if you want one, but your integration shouldn't depend on parsing it. Once the submission succeeds, the session ends automatically. No end-call tool wiring or custom cleanup is required.

Preview your agent directly in Agent Builder so you can iterate on models and voices used, field sequencing, and your structured data output before deploying to LiveKit Cloud.

Choose a path to get started

Structured data collection is now a first-class pattern in LiveKit, in code or in the browser. You declare the fields you need, the agent handles how to gather them, and LiveKit Agents handles end-of-call detection, retries, and result delivery. Whichever path you start on, you end every session with a predictable, structured record:

  • Start in Agent Builder when you want a fast visual loop, a templated starting point, or a way to iterate in the browser before writing any code.
  • Start in code when you want fine-grained control, need to reuse the same collection step across multiple agents, or are composing structured collection into a larger code-first agent.

You can also switch later. Agent Builder agents compile down to Tasks and TaskGroups so exporting to code is easy.

Start building today with Agent Builder or Tasks and TaskGroups in the Agents SDKs and share feedback or questions in our community forums.