Execution Basics
The Runner
classes are the core components for building task agents in Encord.
They provide a simple interface for defining agent logic and moving tasks through the Encord project workflows.
The Runner
s manage the execution of agent logic on tasks within specific workflow stages.
They are responsible for:
- Connect directly to your Encord project via the Encord SDK
- Provide function decorators to associate your agent logic with workflow stages
- Manage retries and error handling
- Handle task fetching and updates
- Optimize performance through batched updates and data loading
The following sections go through the different components of the Runner
s and how to use them.
Stage Decorators
Consider a workflow with three stages: start
, agent
, and complete
. The Runner
objects from encord_agents.tasks.runner
is used to define the logic for agent stages (the purple nodes in the workflow).
In this example, the Runner
is responsible for implementing the logic for the Agent 1
stage.
The @runner.stage
decorator connects your functions to specific stages in your Encord workflow. For the workflow above, the logic for the Agent 1
stage can be defined in the following manner:
The agent function must return the next stage for the task. This can be specified using either the pathway name or a UUID
.
If None
is returned, the task remains in its current stage and will be reprocessed during the next execution of that stage.
You can also define multiple stages in a single runner:
If you define multiple stages, the execution logic depends on the type of Runner
used. For example, if a Runner
is configured with two stages:
=== “Runner”
The Runner
executes the tasks in the order in which the stages were defined in the runner.
That is, the tasks are processed in stage_1
first and in stage_2
next.
=== “QueueRunner”
The QueueRunner
gives you control over the task queues for each stage.
Please refer to the QueueRunner documentation for more information.
Optional arguments
When you wrap a function with @runner.stage(...)
, you can include the label_row_metadata_include_args: LabelRowMetadataIncludeArgs
argument. This argument passes directly to the Encord Project’s list_label_rows_v2
method, enabling you to read client metadata associated with a task. If you need to update metadata, use the dep_storage_item
dependency.
Here is an example:
When developing your agent in a REPL environment (like Jupyter notebooks), re-running cells or snippets can be useful. By default, the library raises an error upon re-execution of the following snippet because it prevents defining multiple agents for a given stage.
If you want to overwrite the function associated to a given stage, this can be done by:
This ensures the definition for a given stage updates on each re-run and preserves the original order of execution for stages.
Dependencies
The Runner supports dependency injection, similar to FastAPI. Dependencies are functions that provide common resources or utilities to your agent functions.
Built-in Dependencies
Example
The library provides multiple commonly used dependencies. See the References section for an explicit list. Below, we demonstrate how an agent function obtains both label rows from “twin projects” and a frame iterator for videos simply by specifying these as dependencies.
In the function body above, the task
, lr
, twin
, and frames
variables are automatically injected with their respective dependencies. This provides the flexibility to inject only necessary dependencies, allowing you to focus solely on your agent’s logic.
Annotations
You can access three core object types in your stage implementation without extensive type annotations: AgentTask
, Project
, and LabelRowV2
.
If you type any parameter of your stage implementation (e.g., the my_agent
function) with one of these types, the system automatically injects the corresponding object, matching the task at hand.
For example:
Here, the project
variable automatically receives the workflow Project instance corresponding to the project_hash
defined or executed by the runner.
Similarly, access the task
and label_row
(associated with the given task) like this:
The remaining dependencies must be specified with a encord_agents.tasks.dependencies.Depends
type annotation using one of the following two patterns.
Custom Dependencies
Dependencies can be any function that has a similar function declaration to the ones above.
Specifically, functions that have parameters typed with AgentTask
, Project
, LabelRowV2
, or other dependencies annotated with Depends
.
Your custom dependencies should not include any default values or additional arguments directly. Instead, wrap the dependency in a function to pass additional arguments or set default values:
This approach allows you to set default values for your dependency arguments when you call the wrapper function.
You can create your own dependencies that can also use nested dependencies in the following manner:
Optional Pre-Execution Agent Validation
To add validation that your runner is suitable for your Project, such as checking for an appropriate Ontology or workflow stages, pass an additional pre_execution_callback
parameter when defining your Runner.
You can execute the script with: python agent.py --project-hash=<your-project-hash>
. Before execution, we fetch the Project and run validation, which has access to the entire runner object. This validation occurs just before fetching and executing tasks, allowing you to perform arbitrary checks, including referencing agents, as shown above.
Running the Runner
There are two different types of runners with different use-cases. They also have two slightly different execution interfaces.
Refer to the following pages for more information:
Runner
: This is a simple sequential runner to run the agent functions one after the other. It is easier to debug and understand. Use this for simple workflows or for testing out functionality before you scale it with theQueueRunner
.QueueRunner
: This is a more advanced runner that allows you to run the agent functions in parallel. It is useful when you have a lot of tasks to process and you want to speed up the processing time via parallel execution.
Comparison between Runner
and QueueRunner
The key differences between QueueRunner
and the sequential Runner
are:
Feature | Runner | QueueRunner |
---|---|---|
Execution Model | Executes tasks sequentially in a single process | Designed for distributed execution across multiple processes |
Project Hash | Optional at initialization | Required at initialization |
Function Wrapping | Executes your function directly with injected dependencies | Additionally wraps your function to handle JSON task specifications |
Execution Control | Handles task polling and execution | You control task distribution and execution through your queue system |
Scaling | Not suitable for scaling | Suitable for scaling |