Prompt
Invoke
Evaluate
Repeat
tasks
. Tasks serve as discrete, observable checkpoints with well-defined objectives and typed results. When a task is assigned to an agent, the agent has the autonomy to take actions and make decisions to complete the task. Agents can mark tasks as either successful or failed, providing a clear signal to the system about the completion status. However, the system will continue to invoke the agent until the task is marked as complete.
run()
and run_once()
:
run()
: Executes the loop until the object is in a completed state. For tasks, this means running until that task is complete; For flows, it means running until all tasks within the flow are complete. At each step, the system will make decisions about what to do next based on the current state of the workflow.run_once()
: Executes a single iteration of the loop, allowing developers to step through the workflow incrementally. For example, developers could use this method to control exactly which agent is invoked at each step, or to provide ad-hoc instructions to the agent that only last for a single iteration.run()
Methodrun()
and run_once()
methods can be used to control the execution of the loop. First, the behavior of the various run()
methods:
t1.run()
would execute the loop until t1
is complete.t2.run()
would execute the loop until both t2
is complete, which would also require completing t1
because it is a dependency of t2
.flow.run()
would execute the loop until both t1
and t2
are complete.run()
tells the system to run the loop until the object is complete (and has a result available). It is the most common way to eagerly run workflows using the imperative API.
run_once()
Methodrun_once()
methods:
t1.run_once()
would execute a single iteration of the loop, starting with t1
.t2.run_once()
would execute a single iteration of the loop, starting with t1
.flow.run_once()
would execute a single iteration of the loop, starting with t1
.run_once()
always runs a single iteration, in all three cases it would focus on the first task in the flow, which is t1
. However, the behavior of these three calls in practice could be different. For example, you could call t1.run_once()
before t2
was created, in which case knowledge of t2
would not be included in the prompt. This could lead to different behavior than if you called t2.run_once()
, even though both methods would start by running t1
.
By offering these execution methods, ControlFlow gives developers the flexibility to either let the loop run autonomously or manually guide its execution, depending on their specific requirements.
@task
and @flow
decorators in the functional API, the run()
method is automatically called when the decorated function is invoked. This is because the functional API uses eager execution by default.Controller
. The Controller
automatically gathers all available information about the workflow, including the DAG of tasks, dependencies, tools, instructions, assigned agents, and more. It identifies tasks that are ready to run (i.e., all dependencies are completed), chooses an available agent, and compiles a comprehensive prompt.
Importantly, the Controller
generates tools so the agent can complete its tasks. Tools are only provided for tasks that are ready to run, ensuring that agents do not “run ahead” of the workflow.
The compiled prompt includes the task objectives, relevant context from previous tasks, and any additional instructions provided by the developer. This ensures that the agent has all the necessary information to make progress on the assigned tasks.
result_type
that defines the expected type of the result. Instead of relying on freeform conversational responses, agents must use special tools to provide structured outputs that conform to the expected type of the task.
Once a task is complete, you can access its result in your workflow and use it like any other data. This structured approach ensures that the results are reliable and consistent, making it easier to validate agent progress and maintain the integrity of the workflow.
instructions()
context manager. With instructions()
, developers can temporarily provide additional guidance to agents without altering the underlying task. These instructions are included in the compiled prompt for the next iteration of the loop.
flows
to address this challenge. Flows allow developers to define the overall structure of the workflow, specifying the order of tasks, dependencies, and data flow. By organizing tasks into flows, developers can create clear and maintainable workflows that are easy to understand and modify.
Creating a flow is simple: enter a Flow
context, or use the @flow
decorator on a function, then create tasks within that context. At a minimum, ControlFlow will ensure that all tasks share common context and history, making it easier for agents to make informed decisions and generate meaningful results.
In addition, there are various ways to create explicit task dependencies that the system will enforce during execution:
depends_on
when creating a task, you can ensure that the task will only run after its dependencies have completed.context
when creating a task, you can provide additional context that will be available to the agent when the task is run, including the results of other tasksparent
when creating a task, you ensure that the parent will only run after the child has completed. This is useful breaking up a task into subtasks.Flow
maintains a shared history and context that is accessible to all agents. This shared context ensures that agents have a common understanding of the workflow state and can build upon each other’s results.
By creating nested flows, you can let agents have private conversations that are not visible to the parent flow. Subflows inherit the parent flow’s history, so this is a good way to let agents have “sidebar” conversations to solve a problem without creating noise for all the other agents.