Skip to main content

Goals

  • Understand when and where to use the asynchronous client features
  • Use the async client to efficiently serve environments that use sandboxes
  • Use the async client to run multiple agents in parallel

Prerequisites

Basics

Previously, we’ve seen basic examples using the synchronous client. For example, interacting with an environment:
This simple synchronous interface is problematic when we need to interact with OpenReward features concurrently, such as when running an evaluation over a suite of environments and tasks. To remedy this, an asynchronous version of the OpenReward client is provided, called AsyncOpenReward. Using AsyncOpenReward, the above example would become
Currently, code using AsyncOpenReward is required to be run inside of
if __name__ == "__main__". This restriction may be lifted in the future.

Serving Sandbox Environments

Many environments use sandboxes to remotely execute agent actions inside of a virtual machine. When creating such an environment, it is critical to use the asynchronous client. On the backend, your environments are wrapped in a FastAPI server and can process many requests concurrently. Without the use of AsyncOpenReward, requests that execute long-running sandbox actions in your environment can block all other connections, drastically reducing concurrency of your environment and increasing costs.
Do not use the synchronous sandboxes API inside of an environment
Instead, always use the asynchronous sandboxes API

Running Agents in Parallel

When using OpenReward for things like agentic reinforcement learning or evaluation, it is often desirable to run many agents in parallel. To do this, a common design pattern is to define the agent to accept an asynchronous session object, allowing the use of builtin python asyncio library operations to easily run agents in parallel. For example, the interface to the agent could look something like
We can then run many agents in parallel using asyncio.gather like

Fetching a Subset of Tasks

For large environments, you may want to fetch only a subset of tasks rather than the full list. get_task_range accepts start and stop parameters following Python range/slice conventions — start is inclusive, stop is exclusive, and both support negative indices (resolved relative to the total number of tasks) and None (defaults to 0 and num_tasks respectively). The ordering of tasks returned is guaranteed to be consistent across calls, so you can safely partition work across workers using non-overlapping ranges.