# Virtual Clock (https://www.agentenvframework.com/docs/environments/virtual-clock)

> The virtual clock for ensuring environments are timeless

## An Agent on Virtual Time

An animation of an agent working in `office` while the gateway’s virtual clock is armed at Monday 09:00. You can pick the speed (`1`, `60` or `3600` virtual seconds per real second) and whether `email` follows the clock. At `60`:

1. The gateway’s clock is armed at Monday 5 October, 09:00, and runs 60 times faster than real time. The agent’s machine says Wednesday 30 September.
2. The agent asks the env for the time with `get_time`, and gets Monday morning, not its own machine’s date.
3. `email_search` finds Dana’s mail from 08:12 that Monday: the inbox was loaded to lead up to the clock’s 09:00.
4. `send` stamps the reply’s `sent_at` with the virtual time, because `email` reads the gateway’s clock.
5. The trajectory stamps every call with the virtual time as well as the real one.
6. Asked again, `get_time` is 11 virtual minutes on: the clock moved while the agent worked, and every run starts over at 09:00.

The agent’s calls and what they return:

```json
get_time {} → {"current_time": "2026-10-05T09:03:06Z"}
email_search {"query": "Q3"} → {"emails": [{"from": "dana@example.com", "subject": "Q3 numbers", "received_at": "2026-10-05T08:12:00Z", …}]}
send {"to": "dana@example.com", "subject": "Re: Q3 numbers", "body": "Thanks, reviewing them this morning."} → {"sent": 1, "sent_at": "2026-10-05T09:08:42.180000Z"}
get_time {} → {"current_time": "2026-10-05T09:14:18Z"}
```

When `email` doesn’t follow the clock, `send` returns `{"sent": 1, "sent_at": "2026-09-30T14:02:19.703000+00:00"}` instead. The calls’ `tool_call` entries in `/trajectory`:

```json
{"event_type": "tool_call", "tool_call": {"function_name": "get_time", "arguments": {}}, "event_id": "event_1", "timestamp_utc": "2026-09-30T14:02:14.099000+00:00", "virtual_time": "2026-10-05T09:03:05.940000Z"}
{"event_type": "tool_call", "tool_call": {"function_name": "email_search", "arguments": {"query": "Q3"}}, "event_id": "event_3", "timestamp_utc": "2026-09-30T14:02:16.899000+00:00", "virtual_time": "2026-10-05T09:05:53.940000Z"}
{"event_type": "tool_call", "tool_call": {"function_name": "send", "arguments": {"to": "dana@example.com", "subject": "Re: Q3 numbers", "body": "Thanks, reviewing them this morning."}}, "event_id": "event_5", "timestamp_utc": "2026-09-30T14:02:19.699000+00:00", "virtual_time": "2026-10-05T09:08:41.940000Z"}
{"event_type": "tool_call", "tool_call": {"function_name": "get_time", "arguments": {}}, "event_id": "event_7", "timestamp_utc": "2026-09-30T14:02:25.299000+00:00", "virtual_time": "2026-10-05T09:14:17.940000Z"}
```

An agent's machine has a date of its own, but the environment decides what time it is. Once the
gateway's clock is armed, the agent reads the time with `get_time`, the servers that follow the clock
stamp their data with it, and the trajectory records it, so every run happens on the same Monday
morning.

Every instance in the [gateway topology](https://www.agentenvframework.com/docs/environments/gateway-topology.md) has one virtual clock,
`urn:agentenv:clock/v1` on its [environment card](https://www.agentenvframework.com/docs/environments/environment-card.md#env-topology-extensions).
The gateway provides it out of the box; a server needs code only to [follow it](#follow-the-clock-in-a-server).
It stays off until something arms it.

## Arm It

`PUT /clock/set-time` starts the clock at `virtual_time`, running `virtual_seconds_per_real_second`
virtual seconds for every real one:

```bash title="Terminal"
curl -sS -X PUT http://localhost:61854/clock/set-time -H 'content-type: application/json' \
  -d '{"virtual_time": "2026-10-05T09:00:00Z", "virtual_seconds_per_real_second": 3600}'
```

```json title="200"
{
  "armed": true,
  "t0": "2026-10-05T09:00:00Z",
  "virtual_seconds_per_real_second": 3600.0,
  "virtual_time": "2026-10-05T09:00:00.144000Z"
}
```

`virtual_time` is RFC 3339, with seconds and a `Z` or `±hh:mm` offset. The speed defaults to `1`,
real time; `0` freezes the clock, and `86400`, a virtual day every real second, is the most. A
malformed time or a speed outside that range answers 400 and leaves the clock as it was.

## How It Advances

Each read is `t0` plus the real seconds since the arm, times the speed. So the clock moves on its
own, whether or not anything calls a tool, and reading it never changes it. Arming it again starts
it over from the new `virtual_time`, which is how every run can start at the same moment.

## Read It

```bash title="Terminal"
curl -sS http://localhost:61854/clock/time
curl -sS http://localhost:61854/clock/state
```

```json title="GET /clock/time"
{"virtual_time": "2026-10-05T21:30:00.047160Z"}
```

```json title="GET /clock/state"
{
  "armed": true,
  "t0": "2026-10-05T09:00:00Z",
  "virtual_seconds_per_real_second": 3600.0,
  "virtual_time": "2026-10-05T21:30:43.247160Z",
  "env_get_time_url": "http://gateway:18765/clock/time"
}
```

While the clock is off, `/clock/time` answers 404 with `{"ok": false, "error": "clock not armed"}`,
and `/clock/state` answers `{"armed": false}`. `env_get_time_url` is where the env's servers read
the clock, from inside the instance.

## What the Agent Sees

Arming adds a `get_time` tool to the agent's tools, and clearing takes it away. It takes no
arguments and returns the virtual time to the second, as `{"current_time": "2026-10-05T21:30:00Z"}`;
its description tells the agent that the clock of the machine it runs on is not this environment's.
If one of the env's servers has a tool of its own named `get_time`, arming answers 409.

## The Trajectory

While the clock is armed, every entry in `/trajectory` carries a `virtual_time` beside the real
`timestamp_utc`, so a run at 3600 times real speed still reads as a working day:

```json title="GET /trajectory"
{"event_type": "tool_call", "tool_call": {"function_name": "get_time", "arguments": {}}, "event_id": "event_1", "timestamp_utc": "2026-09-30T14:02:11.482113+00:00", "virtual_time": "2026-10-05T21:30:00.047160Z"}
```

## Clear It

Clearing turns the clock off, and does no harm to a clock that is already off.

```bash title="Terminal"
curl -sS -X POST http://localhost:61854/clock/clear
```

```json title="200"
{"ok": true, "armed": false}
```

## Follow the Clock in a Server

The gateway's clock doesn't change what time your server thinks it is. A server that stamps its
records, as `send` does here, follows the clock by advertising `sync_time` under
`urn:agentenv:clock/v1` and reading the URL it is handed:

```python title="email/server.py"
from datetime import datetime, timezone

import httpx
from agentenv_protocol import AgentEnvEnvironment, environment_card, extension, tool


@environment_card(name="email")
class EmailEnv(AgentEnvEnvironment):
    def __init__(self) -> None:
        self.inbox: list[dict] = []
        self.sent: list[dict] = []
        self.env_get_time_url: str | None = None

    @extension(uri="urn:agentenv:clock/v1", description="Sync this server to the gateway's virtual 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, subject: str, body: str) -> dict:
        """Send an email."""
        sent_at = await self._now()
        self.sent.append({"to": to, "subject": subject, "body": body, "sent_at": sent_at})
        return {"sent": len(self.sent), "sent_at": sent_at}
```

## Time Triggers

A trigger with `"when": {"type": "time", "at": ...}` fires when the virtual clock reaches `at`, an
RFC 3339 time or a duration from `t0` such as `PT8H`. The gateway checks its time triggers once a
real second and after each watched tool call, so at `86400` one can fire up to a virtual day late.
[Triggers](https://www.agentenvframework.com/docs/environments/triggers.md) covers the rest.

## Waits on the Clock

A server's own extensions can run on the clock too. On a synced server that implements it,
`urn:agentenv:set-async-wait/v1` with `scale_to_virtual` measures a submitted job's wait in virtual
seconds, so a 60-second job at speed `60` is ready after about a real second. [Async Waits](https://www.agentenvframework.com/docs/environments/useful-env-extensions.md#async-waits)
covers the extension.

## In a Task

In a task, the `sync_env_clock` step arms the clock and syncs the servers that follow it, as
[Env control](https://www.agentenvframework.com/docs/tasks/more-steps.md#env-control) describes.