Skip to content

feat(computer): the computer env type and the composer (container forms) - #358

Merged
earakely-scale merged 23 commits into
mainfrom
edgararakelyan/fd-4356-m06-computer-env-type-and-the-composer-container-forms
Oct 28, 2026
Merged

earakely-scale merged 23 commits into
mainfrom
edgararakelyan/fd-4356-m06-computer-env-type-and-the-composer-container-forms

Conversation

@earakely-scale

@earakely-scale earakely-scale commented Oct 27, 2026 •

Copy link
Copy Markdown
Collaborator

M0.6 of the CUA env epic (FD-4345): the computer env type and the composer for container forms, FD-4356. It follows the reviewed design, CUA Env M0.6: Computer Env Type and the Composer, with its 14 choices confirmed. It builds on #354 (M0.1), merged in v0.9.1369.

What's in it

  • The computer env type (ComputerEnv, env/envs/computer/). An env.toml names a base computer and a driver, and declares what makes the env itself:

    • display, network and [computer_settings], the base computer's settings by name (decided in review: one table per layer, so a driver's or an app's settings get tables of their own when they arrive); it builds every form its base has, so it names none (decided in review: forms could only ever be ["container"], and a deploy picks a form through env_params, M0.7);
    • apps is accepted only empty until computer app envs arrive (M1.1, M1.3).
    type = "computer"
    computer = "ubuntu-24.04"
    driver = "reference"
    display = { width = 1600, height = 900 }
  • Gate 1 at put. The base, the driver and the declaration are matched before anything is stored, and every mismatch is refused at once:

    • the driver's supports against the base's OS, and the arches the two share;
    • a driver with no install for the OS, or a system service on a base whose supervisord runs as the session user;
    • a base image in no registry and with no build context;
    • display outside 1 to 32767 per side, or beside a resolution setting;
    • an invalid network;
    • a setting the layer doesn't declare, or of another type.
  • The composer (composer.py) builds one OCI image per form and arch with this machine's Docker.

    • The base comes first: its registry image pinned by digest, or, for a base only stored as its build context (a checkout's), built here for that arch.
    • The driver's files, a venv of its requirements.txt, and its supervisord programs go on top.
    • The other arch is built through Docker's emulation and marked emulated. Without emulation that pair fails alone.
    • Each image is tagged agentenv-computers:<key> in the image store. The key hashes its Dockerfile, its whole build context, the base, the form, the arch and the composer's source, so any env with the same inputs reuses the image without building it.
    • In a checkout whose protocol is installed from source, the composer builds that source into the driver's venv, so an SDK change and the driver compose and test together. A release resolves it from PyPI at the release's pin.
  • What a build records. Each pair's status goes into the env's metadata, composed, as it finishes: unverified with its key, image, packages installed (from pip list) and whether it was emulated, or failed with its stage, reason and a FileArtifact of the build log. The arches built are written as the env's computer, <env id>__computer, a computer artifact over one index of their images. It is written through ComputerArtifact.put, so M0.1's inspection reads each arch back.

  • agent-env env build --id <id> [--version <n>] builds what a put left: failed pairs, and pairs a put that stopped left building. It reuses any image of the same key, prints each pair, and exits 1 if one failed. Builds end unverified; booting and probing them is M0.7, and until then a computer env's deploy is refused, naming its statuses.

  • A driver declares its install (computer_driver env.toml): port, and [install.<os>] with files, python (a requirements file) and services. The reference driver gains a requirements.txt (protocol, mss, python-xlib) and a session service run by python -m agentenv_protocol.computer.serve driver.py:ReferenceDriver, a new SDK entry point that serves a driver class from its file.

  • Aliases in every entry's references. As decided while building this, the planner resolves an installed bundle's alias in an env's, an agent's or an artifact's references as it does in a task's steps. It rewrites the field to the target's id and pulls the target in, for entries pulled from other bundles too. The rule that an entry's reference to an alias is refused goes away. This is what lets a computer env write computer = "ubuntu-24.04" and driver = "reference".

  • A bundle run says it's building, when it is. Writing an env hands the run's build callback to its from_toml on the AuthoringContext (on_build), and the composer calls it once, just before it first builds an image, so a run prints its "building with docker" line only when something builds, as for image entries; reusing every image prints nothing. Each pair's result is logged, and a failed pair is a warning.

Where this differs from the design

  1. The build key hashes the rendered Dockerfile and the whole build context, beside the base, form, arch and composer source, instead of a list of named inputs. It covers every input the doc named, and also the driver's programs and install, which a key over its files alone missed (a unit test caught it).
  2. Settings are one flat table per layer, [computer_settings] for the base (decided in review), not a table keyed by layer: only the base declares settings, and a layer key named nothing the env didn't already hold.
  3. A base stored only as its build context is built here for each arch, natively or emulated. The doc assumed a registry base, which a checkout doesn't have.
  4. Progress is the bundle run's one "building with docker" line plus a log line per pair (INFO; failures are warnings), not a printed line per pair at default verbosity.
  5. The composed computer's id is <env id>__computer, after derive_id, not <env id>-computer. Its settings' defaults carry the env's values and its display.
  6. Gate 1 has no placement or permission checks. The driver env has neither field yet: every driver runs inside the computer and needs no OS permission.
  7. A failed build's log is a FileArtifact, not an object url.
  8. No data_paths on the env (decided in review). What a computer's data/get returns is the reader's choice: it names the paths it reads (data/get takes paths, M0.7), and with none, the driver returns its OS's standard user folders and declares them on its card. snapshot_env takes computer_file_paths with M1.8.
  9. No [tools] hide on the env (decided in review). A task that wants fewer tools disables them with modify_env_tool_access, which works on a computer env through its driver's disable-tool/v1, as on any env.

Testing

  • Unit. 75 tests cover:

    • the env type, gate 1 and the composer, with Docker and the registry faked;
    • the driver's install format and the serve entry point;
    • the bundle folder.

    Line coverage of that code is 96% (composer 95%). The whole tier passes at f9cda77, rebased on main after the review changes (11,196 tests).

  • Integration (tst/integration/computer, real Docker):

    • on the laptop (arm64) at a1ad43a, and on EC2 (amd64) at f530255: 17 passed and 1 skipped on each;
    • on the laptop at 8b42cbb, after the review changes, with the bundle env tests: 30 passed and 1 skipped.
    • The skipped test is the agent run, which needs a model endpoint. Neither shell held a model key; the agent run below was made separately.
  • Chaos on the laptop (arm64, with amd64 emulated): 16/16 at f530255.

    • Measured: arm64 builds natively in 15 s and amd64 emulated in 68 s; a composed image is about 756 MB. The reference driver's probe passes at full (51 checks, three times), and the computer boots to ready in 1.6 s.
    • Interrupted: a put killed mid-build leaves its pairs building, and env build finishes them.
    • Unresolvable: a requirement that can't resolve fails its pair at build, with the log stored, and no computer is written.
    • Concurrent: two puts of the same key at once both finish, and the image pulls.
  • Chaos on EC2 (amd64, with arm64 emulated).

    • At f530255:
      • measured: 9/9. amd64 builds natively in 22 s and arm64 emulated in 210 s. A second env reuses both images. The probe passes at full ×3 and boot takes 1.8 s.
      • interrupted: 2/2. unresolvable: 2/2. concurrent: 3/3.
    • Reruns at a1ad43a, each composing a fresh driver copy:
      • No emulation, Docker restarted after binfmt is removed: the arm64 pair fails at build with "this machine can't build linux/arm64: its Docker has no emulation for it", and amd64 builds.
      • No emulation, stale: binfmt is removed but Docker isn't restarted, so buildx still lists linux/arm64 and the build runs. Its exec format error is reported as "this machine can't run linux/arm64 programs: its Docker has no emulation for it", with the log stored. The f530255 run had reported only "the build failed", which is why 1f2feea maps it.
      • Refused push: with the registry read-only, both pairs fail at push (405) and the put still exits 0. Once it takes pushes again, env build builds both, unverified.
  • An agent on the composed computer (laptop, at f530255): claude-code drove the env's composed arm64 computer and created a folder through the desktop's file manager. The task's outcome check passed in 30.7 s, and its recording had no missing segments.

  • Greptile: 15 findings over six reviews, all fixed and answered on their threads:

    • a stale computer after a failed write (current(), images);
    • signing in to a private base;
    • reusing before building the base;
    • executable bits;
    • the labels of reused images;
    • MAX_PORT;
    • composed images going to the env's own image store;
    • signing in before the index is joined;
    • a named file mode;
    • composing under the stored id of an env reached by an alias;
    • two builds of one version keeping each other's pairs;
    • reading data_paths once, a field since removed (see below);
    • an entity-only plan (write_installed) resolving installed aliases, as a run's does;
    • an empty forms, a field since removed;
    • an entity-only plan leaving the bundle's tasks out of its alias pass, so an unrelated task's default agent can't block it.

    Each finding from the last two rounds has a unit test that fails against the code before its fix.

🤖 Generated with Claude Code

RetriggerView in GreptileConfidence Score: 5/5

The PR appears safe to merge within the supplied stack, with no outstanding findings.

Summary

Adds ComputerEnv, checks its base and driver before saving, and builds reusable images for each supported architecture.

  • Computer envs build reusable images from a base computer and driver.
  • Computer drivers declare how their files and services run inside an image.
  • Bundle entries can use installed aliases in their references.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A[Computer env declaration] --> B[Check base and driver]
  B --> C[Save env]
  C --> D[Stage driver files safely]
  D --> E{Cached image exists?}
  E -->|Yes| F[Reuse image]
  E -->|No| G[Build and push image]
  F --> H[Record each result]
  G --> H
  H --> I[Save composed computer]
Loading

Reviews (19) · Last reviewed commit: "fix(computer): refuse a driver file name..." · Reviewed by Greptile

Comment thread src/agent_env/cli/env/build.py Outdated
Comment thread src/agent_env/env/envs/computer/composer.py
Comment thread src/agent_env/env/envs/computer/composer.py
Comment thread src/agent_env/env/envs/computer/composer.py Outdated
Comment thread src/agent_env/env/envs/computer/composer.py Outdated
Comment thread src/agent_env/env/envs/computer_driver.py Outdated
Comment thread src/agent_env/env/envs/computer/composer.py Outdated
@earakely-scale
earakely-scale force-pushed the edgararakelyan/fd-4347-m01-computer-artifact-type-and-an-ubuntu-2404-base-container branch from 77a90dc to 3e2870d Compare October 27, 2026 22:31
@earakely-scale
earakely-scale force-pushed the edgararakelyan/fd-4356-m06-computer-env-type-and-the-composer-container-forms branch from f530255 to 1f2feea Compare October 27, 2026 22:34
Comment thread src/agent_env/env/envs/computer/composer.py Outdated
Comment thread src/agent_env/env/envs/computer/composer.py
@earakely-scale
earakely-scale force-pushed the edgararakelyan/fd-4356-m06-computer-env-type-and-the-composer-container-forms branch from 1f2feea to a1ad43a Compare October 27, 2026 22:49
Comment thread src/agent_env/cli/env/build.py Outdated
Comment thread src/agent_env/env/envs/computer/composer.py Outdated
Comment thread src/agent_env/env/envs/computer/env.py Outdated
Base automatically changed from edgararakelyan/fd-4347-m01-computer-artifact-type-and-an-ubuntu-2404-base-container to main October 28, 2026 00:44
@earakely-scale
earakely-scale force-pushed the edgararakelyan/fd-4356-m06-computer-env-type-and-the-composer-container-forms branch from 15457ac to 144a07a Compare October 28, 2026 00:47
Comment thread src/agent_env/bundle/plan.py Outdated
Comment thread src/agent_env/env/envs/computer/gate.py Outdated
@earakely-scale
earakely-scale force-pushed the edgararakelyan/fd-4356-m06-computer-env-type-and-the-composer-container-forms branch 3 times, most recently from e532a49 to ec1ef0a Compare October 28, 2026 05:10
@socket-security

socket-security Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedpypi/​python-xlib@​0.339710010010070
Addedpypi/​mss@​10.2.0100100100100100

View full report

Comment thread src/agent_env/bundle/plan.py Outdated
@earakely-scale
earakely-scale force-pushed the edgararakelyan/fd-4356-m06-computer-env-type-and-the-composer-container-forms branch from 8b42cbb to f9cda77 Compare October 28, 2026 16:55
Comment thread src/agent_env/env/envs/computer/composer.py Outdated
earakely-scale and others added 10 commits October 28, 2026 10:28
…not only a task's

An env, an agent or an artifact naming another bundle's alias is pointed at
what the alias names and pulled in, as a task's step is, with the field
rewritten to its id: a computer env names its base and driver as
ubuntu-24.04 and reference. Entries pulled in before the aliases are read
are pointed too.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…port it serves on

A computer_driver env.toml gains port and [install.<os>]: the folder's files
copied under the driver's prefix, a requirements file installed into a venv
beside them, and the services the base's init runs. The reference driver
declares a session service serving its class from a venv of its
requirements.txt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…per form and arch

A computer env names a base computer and a driver, with its display, network,
hidden tools, settings and data paths. Gate 1 matches them before anything is
stored and refuses every mismatch at once. The composer then builds each form
and arch with this machine's Docker, the other arch through its emulation:
the base, built here from its context when no registry has it, with the
driver's files, venv and supervisord programs on top. Each image is tagged in
the image store by a key of its Dockerfile and build context, so any env with
the same inputs reuses it. Statuses go into the env's metadata as each pair
finishes, the built arches are written as the env's computer over one index,
and agent-env env build resumes what a put left. Builds end unverified; M0.7
boots and probes them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…be the image at full

The integration tier composes a bundle's computer env naming ubuntu-24.04 and
reference, boots this machine's arch and runs the conformance probe at full
three times, and checks an env differing only in display reuses the image.
The packages a build installs are read from pip list, so the protocol, when
installed from a checkout's source, shows its version.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…es in every entry

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…y, and keep what reuse and retries need

- A base stored as its build context is built FROM a tag fixed by its arch,
  so the key is found and an image reused before the base is built.
- The image store's credentials sign Docker in to a registry base before a
  build pulls it.
- Scripts and ELF binaries in a driver's files stay executable in the image;
  the store keeps no file modes.
- Each build labels its image with what it installed and whether it was
  emulated, read back from its arch's config when another env reuses it.
- The env's computer records the images it was written over, and env build
  writes it again when they aren't every pair built.
- The port limit is named.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…as no emulation for it

Docker can still list a platform after its emulation is removed, until it
restarts, so the check before a build passes and the build fails running a
program. That failure's log names it, and the pair now says so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…and sign in before joining them

An @Local env's images and index go to the local registry under namespace
routing, as a docker_image's do. Docker is signed in to the store before the
index is joined, so reusing every image, or an env build that only writes the
computer again, can join images in a private registry. The executable mode
gets a name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
earakely-scale and others added 12 commits October 28, 2026 10:29
… into the stored record

An env got through an installed alias composes into the store and under the
ids of the env the alias names. Each pair's result is merged into the stored
composed table, read again when another build wrote it first, so two builds
of one version keep each other's pairs, and a built pair isn't replaced by
another build's building or failed. data_paths is read once.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n tools every computer offers

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… dicts of their ids

As an MCP server env holds its image and a multi env its children: from_dict
loads them, to_dict writes the same {id, version} as before. The composer takes
them from the env.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
What a computer's data/get returns is the reader's to name, not the env's: the
env drops data_paths, and an env.toml naming it is refused as an unknown key.

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

Its forms field could only ever be ["container"], the default, and a deploy already picks a form
through env_params. The composer builds each form the base has (gate.forms); an env.toml naming
forms is refused as an unknown key. An env's choice of forms comes back with a second form (M4).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
write_installed plans through plan_entities, which skipped alias resolution, so an installed computer
env naming ubuntu-24.04 and reference failed to write on a fresh store. It now resolves them as a run
does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ot settings by layer

Only the base declares settings, so the layer key always named the env's own computer. A computer env
keeps the base's values in computer_settings ([computer_settings] in an env.toml), checked against
the base's declared settings; a driver's or an app's get tables of their own when they arrive.
SettingValue names what a setting holds. The refusals of shapes never released go.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
[tools] hide duplicated modify_env_tool_access, which disables tools on a computer env through its
driver's disable-tool/v1 per role and per task, as on any env. ComputerEnv loses hidden_tools, and
gate 1 its tools check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t by a flag on Env

Env.builds_at_put said before every computer env's put that it was building, though a rerun reuses
every image. The run's build callback now reaches the env's from_toml on the AuthoringContext
(on_build), and the composer calls it once, just before it first builds an image, as image entries
already do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…out of its alias pass

Writing an env or artifact alone resolved aliases for every task too, so a default agent naming a
refused alias blocked a write no task runs for. Tasks and evals are left as they are when nothing
runs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s its resolution at build

The display only set the composed computer's default, so a base whose resolution is a build-phase
setting built its default size while the record said the display's. The display is now the
resolution's value among the build-phase settings too, so it's in the image and its key.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@earakely-scale
earakely-scale force-pushed the edgararakelyan/fd-4356-m06-computer-env-type-and-the-composer-container-forms branch from 22348da to 572960c Compare October 28, 2026 17:32
Comment thread src/agent_env/env/envs/computer/composer.py Outdated
…riting the build context

A driver's files come from a stored file universe, and FileArtifactUniverse.put takes any name, so a name
such as ../../x reached a write outside the composer's temporary build folder. Each name now passes
entity.export.checked_path first; one that's absolute or climbs out fails the pair at build, naming it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@earakely-scale
earakely-scale merged commit d7e4860 into main Oct 28, 2026
25 checks passed
@earakely-scale
earakely-scale deleted the edgararakelyan/fd-4356-m06-computer-env-type-and-the-composer-container-forms branch October 28, 2026 18:20
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