Skip to content
Blogs

Reproducible Environments with Virtual Clocks

Controlling time, who said it was easy

Get started with AgentEnv Framework by following our docs.

RL training frontier models on environments demands reproducibility. Reproducibility is the capability of an environment to be deployed in the exact same state, whether you deploy it yesterday, today, or 10 years from now, and to change only in ways you control, such as the passing of time. Agents will still act differently from run to run; the environment shouldn't be why.

Enterprise RL Environment

where agents learn to do real work

What goes into a realistic enterprise RL environment. Hover or tap an ingredient to read more.

In our experience building RL environments at Scale AI, uncontrolled time is one of the biggest reasons an environment isn't reproducible. In this blog we will cover the failure modes in uncontrolled time and the efforts we take to control time when building our environments.

1. Problems with uncontrolled time

An RL task is only good insofar as it is realistic, difficult, and fair. Fair implies a task is solvable -- without controlling time a task can drift from solvable today to unsolvable tomorrow. In the example shown below: we deploy the engineering startup environment from our RBAC post today, tomorrow, and 10 years from now and it drifts from being solvable today to unsolvable simply by the passage of time in the real world. The root cause: the environment uses the sandbox's machine clock -- as it changes with the passage of time so does the solvability of the task.

One environment on three dates

Ready

The same task, agent and grader

The same environment, task, and agent, runs on three different days driving three different outcomes

2. Ways to control time

Once you realize your environment has to control time, the obvious approach comes to mind first: set the clock of the machine the environment runs on, the OS kernel's system clock that every program on it asks in the end. Dig a little deeper and you discover that most programs ask the kernel through the C library (clock_gettime(), gettimeofday()), where a shim like faketime can step in and answer with the date you choose. Higher up the stack still, you can pin the time in the application itself: a clock of the environment's own that its servers ask in their code. The layers for controlling time are shown below:

Three ways to control time

Tradeoffs

Three ways to control time, each at a different layer, and what each costs

AgentEnv controls time at the application layer for three reasons. You can't always control the kernel: a container shares it with its host. The lower layers never reach the agent, which runs in a sandbox of its own. And AgentEnv's open protocol makes the application layer simple: the gateway holds the clock, one task step arms it, and each server asks it with a few lines of code.

3. Controlling time with AgentEnv's virtual clock

Every AgentEnv Environment gateway holds a virtual clock as the single source of truth on what time it is, and the servers in the environment use the virtual clock through AgentEnv's open protocol.

Episode after episode with the virtual clock on and off: on, every episode starts at 2026-10-15 08:00 and scores the same; off, every one reads your clock

To use the gateway's clock in your environment, give each server a sync_time extension under urn:agentenv:clock/v1. When a task arms the clock, the server is handed the gateway's time URL, and from then on it reads the time there instead of from its machine, as this email server does when it stamps the emails it sends:

email/server.py
from datetime import datetime, timezone

import httpx
from agentenv_protocol import AgentEnvEnvironment, extension, tool


class EmailEnv(AgentEnvEnvironment):
    ...

    @extension(uri="urn:agentenv:clock/v1",
               description="Sync this server to the gateway's clock.")
    async def sync_time(self, env_get_time_url: str) -> dict:
        self.env_get_time_url = env_get_time_url
        return {"now": await self._now()}

    async def _now(self) -> str:
        if self.env_get_time_url:
            async with httpx.AsyncClient() as client:
                resp = await client.get(self.env_get_time_url, timeout=10)
            if resp.status_code == 200:
                return resp.json()["virtual_time"]
        return datetime.now(timezone.utc).isoformat()

    @tool()
    async def send(self, to: str, body: str) -> dict:
        """Send an email, stamped with the environment's time."""
        self.sent.append({"to": to, "body": body,
                          "sent_at": await self._now()})
        return {"sent": True}

It is important for agents and the environment to agree on what time it is. The gateway exposes a get_time tool. Agents use the get_time tool to get the same time the servers do. A single source of truth for what time it is for both the agent and the environment.

Three agents and seven servers ask the gateway for the time at once, and every one of them gets the same answer

With AgentEnv Framework it is very simple to integrate the virtual clock into your task. Simply add the sync_env_clock task step as the task DAG below shows:

task.json · 4 steps

Environmentdeploy_envstartupAgentdeploy_agentassistantRunprompt_agentq3Gradeenv_outcome_verifiercheck

clock · sync_env_clock

The step to add

Arms the gateway’s clock at 2026-10-15 08:00, running ×60, and syncs the 7 servers that follow it. It runs right before the prompt, so virtual time starts with the agent.

{"id": "clock","type": "sync_env_clock","env_id": "startup","virtual_time": "2026-10-15T08:00:00Z","virtual_seconds_per_real_second": 60,"depends_on": [{"task_step_id": "assistant"}]}
Run logclock not armed
    Use the sync_env_clock step to control time with a virtual clock

    Realistic tools and data are only part of a reproducible environment; the other part is controlling time. Without it, a task that is fair today can be unsolvable tomorrow, and scores measure the calendar instead of the agent. Controlling time isn't easy, AgentEnv Framework makes it easy.

    Try AgentEnv Framework

    It's open source, and one install away. Its hello task runs on your machine with no Docker, model or cloud account.

    pip install agentenv-framework

    Then follow a cookbook to build a complete task, from an empty folder to a graded run.

    Acknowledgements

    Written by Edgar Arakelyan, Razvan Dumitru, Alina Trinh, Mateusz Cikowski, and Ying Liu.