Control task execution and manage how agents collaborate.
run
function. The arguments for run
are identical to the Task
constructor, including an objective, agent assignments, and more. By default, the task will be run to completion and its result will be returned, or an error will be raised if the task fails.
@task
-decorated functions@task
decorator creates a task from a function. This approach is less common than using the run
function, but can be useful for quickly “packaging” task definitions as reusable logic. To run the task at any time, simply call the function with appropriate arguments. This will create a new task and run it to completion, returning the result.
run_tasks
function. All of the tasks will be run as part of the same flow (if you are not already operating in a flow context), so they share context and history throughout execution.
Here is an example of running a list of tasks:
run
method that executes the task and returns its result (or raises an error if the task fails):
Popcorn
, which is a good, general-purpose strategy in which each agent ends its turn by picking the agent that should go next.
TurnStrategy | Description | Ideal when… | Keep in mind… |
---|---|---|---|
Popcorn | Each agent takes a turn, then picks the next agent to go next. | All agents are generally capable of making decisions and have visibility into all tasks. | Requires one extra tool call per turn, to pick the next agent. |
Moderated | A moderator agent always decides which agent should act next. | You want a dedicated agent to orchestrate the others, who may not be powerful enough to make decisions themselves. | Requires up to two extra tool calls per turn: one for the agent to end its turn (which could happen in parallel with other work if your LLM supports it) and another for the moderator to pick the next agent. |
RoundRobin | Agents take turns in a round-robin fashion. | You want agents to work in a specific sequence. | May be less efficient than other strategies, especially if agents have varying workloads. |
MostBusy | The agent with the most active tasks goes next. | You want to prioritize agents who have the most work to do. | May lead to task starvation for less busy agents. |
Random | Invokes a random agent. | You want to distribute the load evenly across agents. | Can be inefficient; may select agents without relevant tasks. |
SingleAgent | Only one agent is ever invoked. | You want to control the sequence of agents yourself. | Requires manual management; may not adapt well to dynamic scenarios. |
run()
call. Here, we use a round robin strategy to ensure that each agent gets a turn in order:
Moderated
strategy to have a more powerful model orchestrate some smaller ones. In this example, we invite an “optimist” and “pessimist”, both powered by gpt-4o-mini
, to debate the meaning of life. A “moderator” is tasked with picking the next agent to speak. Note that the moderator is also the only completion_agent
, meaning it’s responsible for marking the task as successful.
Orchestrator
directly.
Orchestrator
to coordinate agentic activity and complete the work. The orchestrator is ultimately responsible for creating the core agentic loop. In each iteration, an agent (or more specifically, an LLM) is invoked and all available information — the tasks, the flow, the agents, and the history of the conversation — is used to compile an appropriate prompt.
In the case of a single task and a single agent, this process is very straightforward, because there is no ambiguity about which LLM to invoke on each iteration. However, as the number of tasks and agents grows, the orchestrator loop becomes more complex.
The orchestrator will always consider not only the tasks that were passed to it, but also all of those tasks’s dependencies and relationships as well. There are three types of relationships it considers to build a universe of relevant tasks:
name_task.run()
) and then pass its result to the poem task. The best way to structure your workflow will depend on your specific use case and preferences.RoundRobin
turn strategy is a better choice. However, “opening” the loop like this is a good choice when you want to dynamically select the next agent based on the results of the previous turn, or you want to run some custom logic between turns.
max_agent_turns
argument limits the number of agentic turns that can be taken in a single orchestration session. This limit is enforced by the orchestrator, which will end the turn early if the limit is reached.
A global default can be set with ControlFlow’s orchestrator_max_agent_turns
setting.
max_llm_calls
argument limits the number of LLM calls that can be made during a single orchestration session. This limit is enforced by the orchestrator, which will end the turn early if the limit is reached. Note that this is enforced independently of the max_agent_turns
limit.
A global default can be set with ControlFlow’s orchestrator_max_llm_calls
setting.
max_llm_calls
parameter, which limits the number of LLM calls that can be made during the task’s lifetime. A task will be marked as failed if the limit is reached and the task is not complete. The call count is incremented any time an LLM is invoked and the task is both “ready” and assigned to the active agent.
Here, we force a task to fail by limiting it to a single LLM call but requiring it to use a tool (which typically requires two LLM calls: one to use the tool and one to evaluate the result):
max_llm_calls
on the task results in the task failing if the limit is reached. Setting max_llm_calls
on the orchestrator only exits the loop early, but does not otherwise affect task behavior.Orchestrator
directly.
The orchestrator is instantiated with the following arguments:
tasks
: a list of tasks that it is responsible for orchestrating. Note it will also consider all of the tasks’ dependencies and subtasks, but these are the tasks that determine whether it is finished.flow
: the flow in which to run the tasks. If not provided, a new flow will be created.agent
: an initial agent to invoke. If not provided, the turn_strategy
will be used to select the next agent.turn_strategy
: the turn strategy to use to select the next agent. The default is Popcorn
.run()
method to step through the loop manually. If you call run()
with no arguments, it will continue until all of the provided tasks are complete. You can provide max_llm_calls
and max_agent_turns
to further limit the behavior.
Handler
interface, which defines methods for various events that can occur during task execution, including agent messages (and message deltas), user messages, tool calls, tool results, orchestrator sessions starting or stopping, and more.
ControlFlow includes a built-in PrintHandler
that pretty-prints agent responses and tool calls to the terminal. It’s used by default if controlflow.settings.pretty_print_agent_events=True
and no other handlers are provided.
AgentMessage
event will be handled by the handler’s on_agent_message
method. The on_event
method is always called for every event. This table describes all event types and the methods they are dispatched to:
Event Type | Method |
---|---|
Event (all events) | on_event |
UserMessage | on_user_message |
OrchestratorMessage | on_orchestrator_message |
AgentMessage | on_agent_message |
AgentMessageDelta | on_agent_message_delta |
ToolCall | on_tool_call |
ToolResult | on_tool_result |
OrchestratorStart | on_orchestrator_start |
OrchestratorEnd | on_orchestrator_end |
OrchestratorError | on_orchestrator_error |
EndTurn | on_end_turn |
Handler
class and implement the methods for the events you’re interested in. Here’s a simple example that logs agent messages: