---
title:  Task Dependencies
---

ControlFlow provides mechanisms to define dependencies between tasks and control their execution order. Tasks can have upstream dependencies, meaning they can only be executed after their dependent tasks have completed successfully.

In this way, tasks form a DAG of operations. ControlFlow can take advantage of this DAG to help agents optimize the execution of tasks, ensuring that they are run in the correct order and that the results of upstream tasks are available to downstream tasks.

Note that eager execution minimizes the ability of the framework to optimize the DAG; this is the tradeoff for the simplicity of eager execution. Lazy execution allows the framework to optimize the DAG, but it requires more explicit control from the user.

### Upstream dependencies

Tasks can not be completed until their upstream dependencies are complete. An upstream dependency is any task that the current task depends on.

There are two ways to define upstream dependencies:
- `depends_on`: A list of tasks that the current task depends on. The task will not be executed until all of its dependencies have been completed. This enforces a strict order of execution.
- `context`: Any values in context are considered inputs to the task, so the task will not be executed until any other tasks in the context have been completed.

When you call a `@task`-decorated function with arguments, those values are automatically added to its context. However, ControlFlow may not be able to infer all dependencies if you pass a task's result to another task (as is common in eager execution). 

ControlFlow ensures that tasks are executed in the correct order based on their dependencies. Tasks with unresolved dependencies will not be executed until all their upstream tasks have completed successfully.

### Parent and subtask dependencies

Another way to define dependencies is to use subtasks. Subtasks are tasks that are created within the context of another task and are automatically considered dependencies of that parent task. This means that a parent task can not be marked as complete until all of its subtasks have been completed.

A subtask can be created in two ways:
- By creating a task within the context of another task.
- By passing a task as the `parent` argument of another task.

In general, the context approach is more versatile since it works with both the functional and imperative APIs:

```python
from controlflow import flow, Task

@flow
def my_flow():
    with Task('Write a poem') as write_poem:
        verse_1 = Task('Write the first verse')
        verse_2 = Task('Write the second verse')
        verse_3 = Task('Write the third verse')
        title = Task('Write the title')

    return write_poem
```

In the above example, `write_poem` is the parent task, and `verse_1`, `verse_2`, `verse_3`, and `title` are subtasks. The parent task considers all subtasks as dependencies, so it will not be marked as complete until all subtasks have been completed. However, this also means that we only have to run the `write_poem` task to execute all subtasks, since tasks force their upstream dependencies to be run. That's why only the `write_poem` task is returned from the flow, where it will be run via eager execution.

