Skip to content

feat(computers): a ubuntu-24.04 computer in a multi env follows its gateway's clock (FD-5476) - #649

Merged
earakely-scale merged 13 commits into
mainfrom
edgararakelyan/fd-5476-hb16-computer-base-the-clock-follower
Nov 12, 2026
Merged

earakely-scale merged 13 commits into
mainfrom
edgararakelyan/fd-5476-hb16-computer-base-the-clock-follower

Conversation

@earakely-scale

@earakely-scale earakely-scale commented Nov 12, 2026 •

Copy link
Copy Markdown
Collaborator

Part of FD-5420: a computer's own clock follows the gateway's universe clock. This is FD-5476, the computer's side: the follower, the clock page, the real-time marker and the zone in ubuntu-24.04, and clock/v1 sync_time on every driver.

With this, a ubuntu-24.04 computer in a multi env follows its gateway's clock from the moment it's deployed. Every session program reads universe time; agent-env's own servers and the follower stay on real time.

What changes

  • The base starts as root.
    • Its init sets /etc/localtime to the computer's zone, makes the clock page and file for the follower's user agentenv-clock, then starts root's supervisord (root.conf).
    • Root's supervisord runs the follower as agentenv-clock and the session's supervisord as the session user, which runs every session program as before.
    • The container form's new start_as = "root" tells the composer to end the composed image as root. It's stored only when it's root, so every other computer is stored as before (the golden wire test holds).
  • Every process preloads the clock library and the TLS shim (/etc/ld.so.preload), built in a stage of the base's Dockerfile so no compiler is left in the image. clock.conf gets the session user's and the page owner's uids, so only the session user's programs are faked.
  • The library moved into the base's folder (src/agent_env/computers/artifacts/ubuntu-24.04/clock/), since an image builds only from its own folder. The clock tier, its workflow and the docs follow it. FD-5700 makes it an image of its own later, so another base needs no copy.
  • The follower (clock/clock-follower, standard library only):
    • long-polls /clock/watch under the computer's name and read token, and acknowledges each version with its next watch;
    • measures the gateway's clock against its own from four stamps, and writes each state to the page with clockpage.py;
    • reads its config every quarter second, during a watch too, and follows a changed one at once;
    • reports in a status file that holds no token.
  • The computer's card is the contract. The driver serves clock/v1 sync_time where the follower is, with the parameters every server's sync_time takes and sync_env_clock already sends (env_get_time_url, timezone), and no new one.
    • It checks the task's zone against the one the computer booted with, refusing with the setting to change, and answers once the follower shows the gateway's current version, or says why it doesn't. It reads that version from the gateway's watch at the follower's URL, naming no follower, which the gateway answers at once and counts as no one's acknowledgement.
    • The gateway already treats a computer whose card serves sync_time as a follower, with its control token as its read token (feat(gateway): computers follow the clock through a watch, and clock writes wait for them (FD-5471) #614).
  • Core writes the follower's config (the gateway's watch URL as the computer's sandbox reaches it, the computer's name, its read token) through the sandbox's root channel, never through the driver.
    • It's root's and the follower's alone (root:agentenv-clock, 0640), written beside its place and renamed into it.
    • Core writes it when a multi env deploys the computer, once the gateway is up, and again after a reset, then waits on the computer's sync_time, so a deploy or reset returns with the computer on the universe's clock. One that can't follow is a warning naming why, and the deploy or reset goes on: a task that never arms the clock needn't care, and arming it refuses such a computer, as sync_env_clock calls the same sync_time.
    • The record keeps the URL and name (metadata.computer.clock), never the token.
  • The real-time marker: the composer puts AGENTENV_REAL_TIME=1 on agent-env's own servers only, the driver's (whose recorder runs in it) and an app's. What they start, the agent's commands included, is on universe time.
  • Disk forms: core's root channel reaches the launcher, not the guest, so it can't configure a disk computer's follower. A disk computer's driver doesn't serve sync_time, and it stays on real time (FD-5488).

Behaviour to know about

  • docker exec into a computer now runs as root by default. The tests that assumed the session user now say so.
  • Site routing on a container sandbox whose exec runs as the image's user (Modal's or CoreWeave's container sandboxes) used to be refused ("deploy it on a VM sandbox"). The image now runs as root, so the root channel's check passes there.
  • A multi env's deploy, or a computer's reset, logs a warning naming why when the computer's follower can't follow the gateway; arming the clock then refuses that computer with the same reason.

Testing

On EC2 hosts with local sandboxes, x86_64 and Graviton arm64. On arm64, base_computer_test.py (11/11), computer_clock_test.py and clock_desktop_test.py (2/2) pass, the first two again on the head with Greptile's fixes. The table is the x86_64 run.

Test What it shows Result
Unit The follower against a stand-in for the gateway's watch: it follows and acknowledges each version, follows a changed config within 1.5 s during a held watch, names a gateway it can't reach, and refuses a config that isn't one. Core writes the config root-only and renames it into place, keeps it in the record without its token, rewrites it on a reset and waits on sync_time. The multi env has only following computers follow. The composer puts the marker on agent-env's servers only and ends a root-starting base's image as root. The driver serves sync_time only where the follower is and not on a disk guest; its zone check and its reasons. The base's Dockerfile, init, root's supervisord and the library's settings name the follower, its config, status and user as agentenv_protocol.computer.clock does, and the disk recipe writes the file the driver takes for a disk guest. 390 + 59 pass, and the 4 new checks
base_computer_test.py Who runs what: root only init and its supervisord, agentenv-clock only the follower, the session user everything else. The zone at boot. As the session user, 10 ways at the clock refused: writing or replacing the page, writing the clock file or adding beside it, reading or listing the follower's config, killing the follower, stopping it through root's supervisord, changing what's preloaded or the library's settings. Only session programs read universe time; a marked program reads real time and what it starts universe time; root and the follower's user read real time. A follower killed with kill -9 is back within 3 s (about 1 s on the hosts; 2.03 s once on a busy CI runner). 11/11
computer_clock_test.py (new) A multi env deploys the desktop computer; its follower follows the gateway from the deploy. sync_env_clock arms the clock 182 days back, calling the computer's sync_time. An advance waiting for the computer (wait_followers_s) is acknowledged. A follower killed with kill -9 is back within 3 s and acknowledges the next advance. A reset brings the page and the follower back on the current version. The session's date matches the universe within 5 s each time (within 0.1 s in the run before the reset fix). pass
clock_desktop_test.py Now on the base's own library: Chrome, the driver and a shell follow 30 jumps, a rate change and a freeze; curl and Python reach a live site through the TLS shim. 2/2
computer_env_test.py The composed desktop boots and passes the conformance probe at full, and runs only the processes above. pass
multi_env_computers_test.py Regression: computers in a multi env, now cards serving the clock. 4/4
tst/clock The clock tier, from the library's new folder. 31/31

On Modal (modal_vm), at the head with the warning change. The bundle was registered into throwaway S3 and ECR stores, composed on a Modal VM, and deployed in a multi env on modal_vm:

Check Result
Gate 3 probe container ready (1 min 47 s), disk guest ready (4 min 22 s)
Multi env deploy 53 s; the follower follows the gateway's public Modal URL
Arming through sync_env_clock 0.21 s; the computer's sync_time synced; the session within 0.21 s of the universe
An advance waiting for the computer acknowledged in 0.07 s; the session within 0.08 s
kill -9 of the follower back in 1.01 s; the next advance acknowledged; the session within 0.09 s
Reset 5 s; the session within 0.07 s; the follower on the current version
Teardown clean, and no follower warnings

The first run found one real problem, now fixed: a reset returned before the fresh computer's follower had taken its config, so for a fraction of a second the session read real time. Core now waits on the computer's sync_time after writing the config, at deploy and after a reset.

Follow-up

FD-5709: gate 1 warns when a driver's server would run on universe time. The composer marks a driver's server for real time only when its service runs -m agentenv_protocol.computer.serve, as every shipped driver's does.

Docs

The computer clock page should cover: the base starting as root, who runs what, the follower and its config, sync_time on a computer, the real-time marker, and that a disk computer stays on real time for now.

🤖 Generated with Claude Code

RetriggerView in GreptileConfidence Score: 5/5

The PR appears safe to merge; no blocking issues remain.

Summary

Ubuntu computers follow their gateway's clock while agent-env's own servers stay on real time.

  • Ubuntu-24.04 session programs follow the gateway clock while agent-env servers stay on real time.
  • Computer drivers expose a clock sync call that checks the gateway follower.
  • Multi-env computers follow their gateway’s clock after deploy and reset.
  • Computer images can keep a root start setting through composition.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  Core["Core"] -->|"Writes protected config"| Follower["Clock follower"]
  Gateway["Gateway clock"] -->|"Watch replies"| Follower
  Follower -->|"Writes"| Page["Clock page"]
  Page -->|"Universe time"| Session["Session programs"]
  Driver["Driver sync_time"] -->|"Checks current version"| Gateway
  Driver -->|"Checks applied version"| Follower
Loading

Reviews (10) · Last reviewed commit: "style(computers): wrap the base test's c..." · Reviewed by Greptile

earakely-scale and others added 7 commits November 12, 2026 11:25
…teway clock (FD-5476)

The base starts as root: root's supervisord runs the clock follower as agentenv-clock and the session's supervisord as
the session user. Every process preloads agent-env's libfaketime and the TLS shim, built in a stage of the base's
Dockerfile from clock/, now in the base's folder. The driver serves clock/v1 sync_time where the follower is; core
writes the follower's config through the root channel when a multi env deploys the computer and after a reset; the
composer marks agent-env's own servers real time.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…xfwm4, and the clock test arms without a zone

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tainer is gone

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…process it is

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…o follow the gateway, through its sync_time

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@earakely-scale
earakely-scale requested a review from a team as a code owner November 12, 2026 20:03
…'s current version, a composed computer keeps start_as

sync_time read only that the follower was connected, so right after an arm it could answer before the follower showed
the new version; it now reads the gateway's version from its watch, naming no follower, as any reader may. The composed
computer copies the base's whole container form, so a computer composed onto it starts as root too. The follower's
retry settings are named; the restart is timed inside the container; the base's build context is pinned with clock/.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@earakely-scale

Copy link
Copy Markdown
Collaborator Author

@greptileai review

… warning at deploy and reset, refused at arming

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
earakely-scale and others added 4 commits November 12, 2026 13:27
…e and message, not its body

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ere, and the base's files are checked against them

Core reads a sync_time refusal through the protocol client's ExtensionCallError instead of parsing the body itself.
The integration tests take the follower's path, config, status and user from agentenv_protocol.computer.clock, and a
unit test checks the ubuntu-24.04 base's Dockerfile, init, root's supervisord and the library's settings name them as
the protocol does, and that the disk recipe writes the file the driver takes for a disk guest. The computer tests'
items env, multi env helper and container params live in their conftest.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rd's next wake and a busy runner take

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@earakely-scale
earakely-scale merged commit cd27bc8 into main Nov 12, 2026
40 checks passed
@earakely-scale
earakely-scale deleted the edgararakelyan/fd-5476-hb16-computer-base-the-clock-follower branch November 12, 2026 22:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant