I’m working with LiveKit Agents and currently structuring a workflow so that each workflow task creates a new agent. The reason is that each task needs a different context, instructions, and set of tools.
Is this the recommended approach?
More specifically:
Is it best to create a new agent with session.updateAgent for each task when the tools and context differ?
Or is it better to keep one agent/session and update the context, instructions, or available tools as the workflow progresses?
Are there performance, state continuity, or lifecycle concerns with creating multiple agents during a single call/session?
How do others usually structure multi-step workflows where each step has different responsibilities?
I’m trying to understand the cleanest LiveKit Agents architecture for this pattern.
I would also like to shoot in a tip from own experience to evaluate agent flow. I started off with a multiagent approach which caused a bunch of headache when I wanted each agent to maintain the same information. The agents started reintroducing themselves and a bunch of handoff errors between agents. It worked, but it was a bit off.
Agent tasks might be a good solution if you have a step by step process. That helped me a lot at least.
Hey Marius, thanks for your advice, I am currently experimenting with agent tasks. Can you share your thoughts on the beta task group feature, is this any good for you? Does anyone else have any experience with this feature?
Tasks can only be awaited from on_enter, on_exit, or inside tool function bodies. Awaiting from anywhere else raises RuntimeError. All tasks in a group share conversation context, with config knobs for summarize_chat_ctx (default true), return_exceptions (default false), and an on_task_completed callback.
@marius.bjornoy’s point about multi-agent reintroduction headaches might be addressed by the shared conversation context, though I haven’t tested that pattern.
Hi @Robbe! You may be interested in TaskGroups, which allow for a flexible sequential workflow of tasks. You can read more about it in our docs here!
We also have an example of this via a survey intake agent, here. If you have a specific use case you would like to see an example of, please feel free to let us know! If it is a common use case and we don’t have an existing example of it, we can work on it.
Thanks Tina, that is very helpful! Is it possible to order the tasks in the taskGroup in a specific way and regress to certain tasks in the group when needed? My specific use-case is for booking/rescheduling/canceling workflows that collect fields in an ordered way, for example for booking: booking type (if more than one defined in the dashboard) > date and time > phone number > name > email. Each step allows regress to different other steps when needed. What would be the optimal LiveKit-native way to do this?
Currently TaskGroup allows for regression when the user expresses the need to, for example if the user was in the email collection stage and wanted to change their phone number, the LLM will automatically regress back to the phone number stage. Once that is completed, the user will correctly return to the email collection stage. The regression chain can queue multiple stages as long as they were visited previously.
Hey Tina, another question came up during my implementation of AgentTasks: Is it normal to set toolChoice: "none" in onEnter for workflow tasks? I did this because, at times, the LLM tried to advance the conversation without any new user input, which caused infinite progression/regression loops.
I’m currently thinking this could be caused by either: 1. the model being too eager to infer the next step from context, or 2. bad tool descriptions / suboptimal workflow instructions that make transitions too ambiguous.
Do you think setting toolChoice: "none" in onEnter is the right safeguard here, or should this be solved more at the task/tool-description level?
Each task calls generateReply with task-specific instructions, and transitions fire only when a save-tool runs with the user’s data passed as required parameters (this.complete() inside the tool’s execute body).
The tasks docs Tasks and task groups | LiveKit Documentation don’t explicitly prescribe required vs optional parameters or address the auto-advance problem directly, but the Python example pattern (strengths_summary: str as a typed parameter) and the JS survey example both rely on the same mechanic: the LLM physically can’t call the completion tool until it has the user-provided value, which forces it to ask.
So the structural fix for the auto-advance loops is closer to your option 2, at the schema level rather than just descriptions: make the completion tool’s user-provided fields required parameters. toolChoice: “none” solves the loop but blocks the legitimate save-tool too, so you’d need to flip it back on at the right moment per task (gets fiddly across boundaries). Required-parameter tool design is what both canonical examples do.
Hi @Robbe, for these situations I would recommend adding a ToolFlag to ignore the advancing function in onEnter. There is an example here, where we ignore the function that regresses tasks upon entering the AgentTask in the case of being eagerly called. I think it can be model dependent as well, but this guard should sufficient in all cases.