Skip to content

feat(computer): Windows 11 computers, composed and deployed with cua-driver on local Docker with KVM - #525

Merged
earakely-scale merged 32 commits into
mainfrom
edgararakelyan/fd-5311-windows-computers-compose-and-deploy
Nov 7, 2026
Merged

earakely-scale merged 32 commits into
mainfrom
edgararakelyan/fd-5311-windows-computers-compose-and-deploy

Conversation

@earakely-scale

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

Copy link
Copy Markdown
Collaborator

What this does

Adds Windows 11 computers. agent-env run with a computer env on windows-11 and cua-driver composes, probes and deploys a Windows desktop on a Linux machine with KVM, using local Docker. An agent drives it through cua-driver's own tools.

  • Base: the first compose on a machine downloads Microsoft's Windows 11 Enterprise evaluation ISO into the cache. It prints one notice line, takes no flag and asks no question. It then installs Windows unattended under the QEMU launcher and keeps the installed base in this machine's Docker. The base records when its 90-day evaluation expires, and a compose installs a fresh one once fewer than 7 days are left.
  • Composed disk: a thin layer on the base. The composer boots the base, installs the driver over SSH as [install.windows] says, shuts it down, and packs the result onto the base image, so every disk on a base shares one copy of it. Windows images stay on the machine that built them, the way a macOS VM bundle stays on its Mac: agent-env never pushes Windows anywhere.
  • Session: agentenv-session (pythonw, windowless) starts each layer's services when the session user signs in. It reads the deploy's settings from the NoCloud seed the launcher attaches.

Changes

  • Artifact: WindowsOS (os.type = "windows", version, edition, display dwm). A Windows computer has the disk form only, on amd64 only. A disk's init is supervisord on Linux and agentenv-session on Windows.
  • Built-in base: windows-11, with its alias.
  • Recipe (computers/disk/windows/): autounattend.xml, setup.ps1 and agentenv-session.py. setup.ps1 installs OpenSSH first, so a failed setup can be read back over SSH, and reports failures through a setup-failed marker.
  • Launcher: a Windows guest's seed is a removable USB drive, since Windows mounts a FAT filesystem with no partition table only on removable media. The Enter-pressing loop stops when the launcher exits.
  • Display: a Windows computer's display is 1920×1080. A resolution setting or env display of any other size is refused when the env or the deploy is checked, before anything boots. On a live boot, Windows kept the size it was installed at whatever size the VGA offered, so changing it needs ChangeDisplaySettingsEx at sign-in, which is a follow-up.
  • Machine images: a machine image is keyed on its disk image's id as well as its name, so a base installed afresh under its old tag gets a new machine image instead of reusing the old one.
  • Launcher build mode:
    • VM_KEEP keeps the overlay across reboots and after power-off.
    • VM_MEDIA attaches the installer ISO, booted first, and presses Enter past its prompt.
    • VM_FILES attaches the answers as a removable FAT drive.
  • windows_disk: installs the base, composes layers over SSH, and packs both. The composer gets a Windows disk path (_composed_windows_disk).
    • A disk whose evaluation has fewer than 7 days left is pending again, so the next compose installs a fresh base.
    • A service's command splits as Windows quotes it.
    • Each build's work folder is named for its key and cleared of whatever a killed build left behind.
    • agentenv-session keeps supervising when a service can't start, and writes the reason to that service's log.
  • Platforms: boot.native_links and first_native now take the computer's OS type into account. Local Docker with KVM boots windows/amd64 on an amd64 host. A Windows boot defaults to 4 CPUs and 8 GB.
  • Install model: [install.windows] (WindowsInstall) takes files, downloads checked by sha256, a requirements file for the layer's venv, PowerShell run scripts (run elevated) and session services. It has no apt.
  • cua-driver on Windows:
    • It talks to the daemon over a named pipe (\\.\pipe\agentenv-cua-driver); the wire format is the same line-delimited JSON as on Linux.
    • It installs the 0.38.0 release zip, checked by sha256, and pins the exe's sha256.
    • It waits out a busy pipe (the C runtime reports one as EINVAL) and the moment between instances.
    • known_failures.windows P4: cua-driver refuses a few clicks in the first burst on a just-opened window (foreground_unavailable).
    • mcp-windows.json was generated from cua-driver mcp running in a signed-in Windows session: 63 tools and 11 skill resources.
  • Protocol:
    • platform.detect() reports windows, version 11 and display dwm on Windows.
    • run_in_session and write_files default on Windows too, so computer-exec and data/add work there. A command runs in a process group of its own with no console window, and a timeout kills it and its children with taskkill.
    • The probe runs its commands on Windows as Python one-liners that write bytes (so no CRLF), uses taskkill, and checks P9 by account and group. The Linux and macOS commands are unchanged.
  • Tests:
    • Unit: test_computer_windows.py (compose, reuse, recompose once the image is gone, refusals, base rules, install models) and the launcher's build-mode tests.
    • Integration: tst/integration/computer/windows/ and windows_test.py, behind a new capability, windows_media (the ISO is in this machine's cache). It is allowed as a skip in the slow job, so CI doesn't download 7 GB and install Windows on every PR.

Not in this PR

  • Apps on Windows. A Windows computer composes its driver alone for now, and composing an app is refused with a reason.
  • Modal VM sandboxes (W7). Modal's export copies only disk.img, so modal_vm doesn't list windows/amd64.
  • --windows-iso PATH. For now, put the ISO in the cache folder.
  • A smaller base. The base image is 28.4 GB (W0's was 16 GB): setup's leftovers stay allocated in the qcow2, and every machine image copies it. A TRIM before shutdown (discard=unmap, rotation_rate=1, Optimize-Volume -ReTrim) and then qemu-img convert would cut it.
  • Showing file extensions. Windows hides known file extensions, so Notepad titles hello.txt as "hello". Whether the base should show them is for review.

Where this differs from FD-5311's plan (for review)

  • Composed images stay local. They live in this machine's Docker and are never pushed to the image store, the way VM bundles stay on their Mac. That keeps Windows off any registry and avoids pushing about 30 GB. The plan said "in the user's own image store".
  • No builder API on SandboxProvider. windows_disk drives this machine's Docker directly (launcher containers with KVM), so the Lume builder methods weren't lifted onto SandboxProvider.
  • Different disk init. The disk's init is agentenv-session, not logon, and there's no access = "ssh" field: composing over SSH is how Windows is built, not something the artifact declares.
  • Firewall off. The base turns the firewall off (QEMU's user network reaches only the forwarded ports) instead of opening each layer's ports, and Windows services are session-only.
  • One version. WindowsOS takes version (11) and a required edition; Server 2025 isn't modelled yet.

Verified

All of this ran on an EC2 c8i.4xlarge with nested KVM (agent-environments-admin, us-east-2), on this branch.

  • Base install: Microsoft's evaluation ISO (26200.6584, 25H2) installs unattended in 757 s until the session user is signed in, and about 22 min including packing. The base's evaluation expires 2027-02-05.
  • Compose: about 2 minutes. SSH answers 17 s after the base boots, then provisioning, shutdown and packing run. The layer is about 1.2 GB.
  • Gate 3, the probe at full: 47 passed, 1 failed and 1 not applicable. The failure is P4's burst of 16 clicks, cua-driver's declared known failure. P3 colours, P4 clicks, P5 refusals, P6 typing and keys, P7 scroll and drag, and P9 one unprivileged account all pass on Windows.
  • agent-env run tst/integration/computer/windows:
    • --task probe: deploys in 25–33 s, the probe passes, and the sandbox is torn down.
    • --task reset: the probe passes before and after a reset in place.
    • --task notepad, Claude Code with Sonnet 5.5: the agent opens Notepad, types the line and saves Documents\hello.txt, and the verifier reads the file back. The last two trials passed (84 s and 70 s, $1.66–$2.96 each). Three earlier trials also saved the file; my first verifier, which read Notepad's UI, missed it.
  • Chaos:
    • Three Windows computers at once: all deploy and probe.
    • A deployed computer killed mid-probe: the run fails and tears down.
    • The compose container killed mid-provisioning: the compose fails with the reason, and a rerun composes.
    • agent-env SIGKILLed mid-compose: the next compose removes the orphaned compose container and its work folder.
    • Reset in place passes.
  • tst/integration/computer/windows_test.py, run with pytest on the box: probe passed and reset passed. Notepad's agent saved the file and its verifier passed (72 s); the test's assertion named the wrong label, which I fixed afterwards. The run took 40 min, including a fresh base install of 16 min.
  • Unit tests: 13,803 passed (tst/unit and the protocol's tests, on the box). CI is green.

Seen and left as follow-ups:

  • Under three concurrent Windows VMs, P6 typing was once interrupted by Windows moving the foreground (foreground_lost).
  • A verifier step whose computer dies mid-request can hang, since env_outcome_verifier has no timeout. This isn't Windows-specific.

🤖 Generated with Claude Code

earakely-scale and others added 27 commits November 6, 2026 23:26
…s Docker (WIP)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e's Docker (WIP)

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…boots it without a pull

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ndows keeps them

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t-effort

The previous commit's edit replaced an empty match and multiplied the script; this is
the script before it with that change made as meant.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s evaluation

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… task in the Windows bundle

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… Windows mounts

Windows mounts a FAT filesystem with no partition table only on removable media, so the
seed on an IDE disk never got a letter and agentenv-session never found CIDATA.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… its manifest as UTF-8

On Windows, Python reads a file in the code page by default, so the manifest's non-ASCII
schema text read back differently from what the daemon lists.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ows it, its extension hidden

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ose commands fit it

run_in_session and write_files default on Windows too: a command in a process group of its
own with no console window, killed with its children by taskkill on a timeout. The probe
runs its commands on Windows as Python one-liners that write bytes, taskkill and an account
check by name and group, leaving Linux's and macOS's as they were.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…gh computer-exec

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s has

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ed as Windows 11 on dwm

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… what a killed build left

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…the probe reads a file's owner on Windows

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nt pipe instance

The C runtime's open reports a busy pipe as EINVAL, not ERROR_PIPE_BUSY, and between one
instance taken and the next made the pipe is briefly absent (ENOENT). 20 bursts of 32
concurrent clicks: none refused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ent pipe, and fails soon on none

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…is a known failure on Windows

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…known failure, as gate 3 does

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 7, 2026 11:13
earakely-scale and others added 5 commits November 7, 2026 03:30
…, quoted commands, display size, supervision

- A Windows disk whose evaluation has fewer than 7 days left is composed again, so its base
  is installed afresh.
- A machine image is of its disk image's id, so a base installed afresh under its tag gets a
  machine of its own rather than the old one's.
- A Windows service's command splits as Windows quotes it.
- The launcher sizes a Windows guest's display from its resolution setting at each boot.
- agentenv-session keeps supervising when a service can't start, saying why in its log.
- Named polling intervals; an integration test of the reset task.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…, as the state fixture moves the cwd

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…the step's id

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…erwise before it boots

Sizing the VGA from the resolution setting didn't change it: on a live boot at 1280x800, with
the VGA's maximum capped there too, Windows kept the 1920x1080 it was installed at. So the
launcher keeps 1920x1080, and a resolution or display of another size is refused when the
env or the deploy is checked, rather than after the boot.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@earakely-scale
earakely-scale merged commit fc54dd5 into main Nov 7, 2026
36 checks passed
@earakely-scale
earakely-scale deleted the edgararakelyan/fd-5311-windows-computers-compose-and-deploy branch November 7, 2026 14:54
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