Skip to content

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

Open
earakely-scale wants to merge 12 commits into
mainfrom
edgararakelyan/fd-4356-m06-computer-env-type-and-the-composer-container-forms
Open

earakely-scale wants to merge 12 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:

    • forms, display, network, [tools] hide, [settings.<layer>] and data_paths;
    • 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;
    • forms other than container;
    • 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 tools.hide pattern matching no tool, a data_path outside the home;
    • 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. It prints its "building with docker" line before writing an env whose type builds at put (Env.builds_at_put). 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 tables are keyed by a layer's id or its name, the last part of its id. The planner rewrites computer = "ubuntu-24.04" to the base's full id, so [settings."ubuntu-24.04"] must still find it.
  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.

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 (11,157 tests) except parse_test.py::TestFiles::test_deep_nesting[json], which also fails on main under Python 3.14; CI runs 3.12.

  • 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.
    • 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: 12 findings over four 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.

    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: 4/5

The PR still needs the installed-write alias fix before merging.

Fix All in CursorFindings

  1. P1 Installed computer writes fail ▶
  2. P2 Empty forms create nothing ▶
Fix with agent prompt
### Issue 1
src/agent_env/bundle/plan.py:305-310
`write_installed` uses `plan_entities`, which never calls `_resolve_aliases`. References such as `computer = "ubuntu-24.04"` and `driver = "reference"` therefore stay bare store references. On a fresh store, writing the env fails instead of pulling in its base and driver, even though a bundle run resolves them correctly.

Run alias resolution before checking and ordering entity-only plans too.

### Issue 2
src/agent_env/env/envs/computer/gate.py:29-30
`forms = []` passes the gate and saves an env that can never produce a computer. Composition has no pairs to build, so `_computer` writes nothing. Later, `env build` also has no pairs and exits successfully without repairing the env.

Reject an empty forms list before saving it.

```suggestion
    if not forms:
        problems.append("forms: name at least one form to compose")
    problems += [f"forms: the {form} form isn't supported yet; a computer env composes {', '.join(FORMS)}"
                 for form in forms if form not in FORMS]
```

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Summary

Adds computer environments that combine a base computer and driver into container images.

  • Computer envs check their base, driver, and settings before they are stored.
  • Computer envs build reusable images and record the result for each architecture.
  • Computer drivers declare how a computer installs and serves them.
  • Bundle entries can use installed env and artifact aliases in their references.

Review checks were static; no tests or Docker builds were run.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Computer environment declaration] --> B[Check base and driver]
    B --> C[Save environment]
    C --> D[Compose each architecture]
    D --> E{Matching image exists?}
    E -->|Yes| F[Reuse image]
    E -->|No| G[Build and push image]
    F --> H[Record pair result]
    G --> H
    H --> I[Join completed images]
    I --> J[Save computer artifact]
    K[env build] --> D
Loading

Reviews (8) · Last reviewed commit: "test(computer): record the composed comp..." · 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
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 on lines 305 to +310
def _resolve_aliases(self) -> None:
"""Point each task's reference to an agent, env or artifact by an alias an installed bundle gives it, and the
configured default agent when it's one, at what it names, pulled into the plan, unless the store has one of the
alias's id, which the reference keeps naming. A field naming the alias is rewritten to the id. A reference that
can't be pointed at the alias's entity is left with the problem the store ref check reports; ``alias_uses``
holds the aliases the plan names."""
"""Point each entry's reference to an agent, env or artifact by an alias an installed bundle gives it, and each
task's to the configured default agent when it's one, at what it names, pulled into the plan, unless the store
has one of the alias's id, which the reference keeps naming. A field naming the alias is rewritten to the id. A
reference that can't be pointed at the alias's entity is left with the problem the store ref check reports;
``alias_uses`` holds the aliases the plan names."""

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Installed computer writes fail

write_installed uses plan_entities, which never calls _resolve_aliases. References such as computer = "ubuntu-24.04" and driver = "reference" therefore stay bare store references. On a fresh store, writing the env fails instead of pulling in its base and driver, even though a bundle run resolves them correctly.

Run alias resolution before checking and ordering entity-only plans too.

Knowledge Base Used: Bundle authoring, installation, and execution

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agent_env/bundle/plan.py
Line: 305-310

Comment:
**Installed computer writes fail**

`write_installed` uses `plan_entities`, which never calls `_resolve_aliases`. References such as `computer = "ubuntu-24.04"` and `driver = "reference"` therefore stay bare store references. On a fresh store, writing the env fails instead of pulling in its base and driver, even though a bundle run resolves them correctly.

Run alias resolution before checking and ordering entity-only plans too.

**Knowledge Base Used:** [Bundle authoring, installation, and execution](https://app.greptile.com/scale-ai/-/custom-context/knowledge-base/scaleapi/agentenv-framework/-/docs/bundle-authoring-and-execution.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Cursor Fix in Claude Code Fix in Codex

Comment on lines +29 to +30
problems += [f"forms: the {form} form isn't supported yet; a computer env composes {', '.join(FORMS)}"
for form in forms if form not in FORMS]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Empty forms create nothing

forms = [] passes the gate and saves an env that can never produce a computer. Composition has no pairs to build, so _computer writes nothing. Later, env build also has no pairs and exits successfully without repairing the env.

Reject an empty forms list before saving it.

Suggested change
problems += [f"forms: the {form} form isn't supported yet; a computer env composes {', '.join(FORMS)}"
for form in forms if form not in FORMS]
if not forms:
problems.append("forms: name at least one form to compose")
problems += [f"forms: the {form} form isn't supported yet; a computer env composes {', '.join(FORMS)}"
for form in forms if form not in FORMS]
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agent_env/env/envs/computer/gate.py
Line: 29-30

Comment:
**Empty forms create nothing**

`forms = []` passes the gate and saves an env that can never produce a computer. Composition has no pairs to build, so `_computer` writes nothing. Later, `env build` also has no pairs and exits successfully without repairing the env.

Reject an empty forms list before saving it.

```suggestion
    if not forms:
        problems.append("forms: name at least one form to compose")
    problems += [f"forms: the {form} form isn't supported yet; a computer env composes {', '.join(FORMS)}"
                 for form in forms if form not in FORMS]
```

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Cursor Fix in Claude Code Fix in Codex

@earakely-scale
earakely-scale force-pushed the edgararakelyan/fd-4356-m06-computer-env-type-and-the-composer-container-forms branch from 144a07a to ce61b31 Compare October 28, 2026 01:25
earakely-scale and others added 12 commits October 27, 2026 19:30
…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>
… 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>
@earakely-scale
earakely-scale force-pushed the edgararakelyan/fd-4356-m06-computer-env-type-and-the-composer-container-forms branch from ce61b31 to a41b625 Compare October 28, 2026 02:33

This branch has not been deployed

No deployments
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