Repository navigation
feat(computer)!: fetch every install at put, build offline from the store, and declare each OS of an app with its own decorator - #402
Merged
earakely-scale merged 10 commits intoOct 30, 2026
Conversation
… store The install format for drivers and apps gains packages (as apt takes them), sources (extra apt repositories with a signing key), downloads (a URL and sha256 per arch), run scripts (as root or the session user) and ports. At put the composer resolves every layer's packages, Python requirements and downloads for each arch in a container of the base, stores each file in the object store by sha256, and stores a lock listing them by a digest of its inputs, which any env with the same inputs reuses. A build stages only the lock's files and installs them with RUN --network=none: the .debs, each venv from its wheels, the run scripts, then an app's @install_step methods, which python -m agentenv_protocol.computer.run_install_step now runs. The build key gains the lock's digest. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s first-written - A file the resolver says it wrote is refused unless it's a plain file in its output folder: build code a dependency runs in the container could leave a link to one of this machine's files, which put would have uploaded. - The resolver installs the apt packages it downloads before building wheels, so a package built from source has the build tools and headers the install names. - A venv whose requirements resolve to no wheels is made empty, with nothing copied or installed. - A lock is written once: a put that resolved the same inputs at the same time uses the lock the first one stored. - Run scripts are quoted, so a name with a space runs as one script. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…from where it fetched them An app's BuildSettings are values its env.toml fixes in its computer's image (build_settings), checked at put, written to .agentenv/build_settings.json in its folder, read as self.build_settings by its install steps and its server, and refused at deploy. An install step no longer sees a half-made self.settings: reading one raises AttributeError naming self.build_settings. A build that follows a resolve links the files it just fetched instead of downloading them back from the object store. The test app now resolves one package from a Launchpad PPA and installs on a build setting. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A SecretStr in a list or in a nested model put its writeOnly under items or $defs, where the check of each field and its anyOf options didn't look, so it would have been written into the image. The whole schema is searched now. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…keeps subschemas Searching every dict of the schema read a field named writeOnly, or a default holding that key, as a secret's marker. The search now follows only the keywords that hold subschemas: properties, items, $defs and the like. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Collaborator
Author
|
Results from the Linux VMs (agent-environments-admin, at d5995611, whose code matches 33d6be1 except for docstrings; the two later commits change only how
On 607ffae, CI passed 24/24 and Greptile gave 5/5. The VMs are terminated. 🤖 Generated with Claude Code |
…aged files' sha256 A lock's inputs are a requirements file's own bytes, so a line including another file (-r, -c), an editable install, a local path or a local folder of wheels would let an edit to that file reuse a stale lock; a local project also can't build on the resolver's read-only context. Put refuses such a line now, naming it. A build that stages a lock's files from the object store refuses one whose sha256 isn't the lock's. The protocol README says how installs behave: apt without recommends, each venv exactly its resolved wheels. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… inputs that ignore edits that resolve nothing - Packages, AptSource, Download and RunScript are defined once, in the protocol, with their checks; the framework's Install imports them, and an app's bad value fails as its class is defined, the declare step naming its field. - The lock is a model (Lock and its locked files), validated when read from the store. - The staged paths are spelled once in packages (STAGED_APT, staged_wheels) and read by the composer. - A lock's inputs hash the request the resolver is given, the resolver script and the lock's shape instead of packages.py's source, so editing packages.py no longer re-resolves every lock; the owner's uid stays out of them. - missing() and unlocked() are one folder_problems(). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An app declares how each OS installs and launches it with a decorator per OS: @linux(...) with Linux's fields, or @install(os_type, spec) for any OS type, a plugin's included, with that OS type's install model. What differs by OS goes in its decorator: launch always; optionally a skill file, and settings and build settings whose fields add to the class's on that OS. @install_step(os=...) and @ready_check(os=...) run on every OS the app installs on unless they name some; naming one it declares no install for is refused when the class is read. On the computer the class reads what's declared for the OS it runs on (platform.detect()), and everything in the framework that reads an app's settings, launch or steps asks for the computer's OS type. BREAKING CHANGE: the class attributes linux = Linux(...) and launch = Launch(...) are gone, and an app's stored declaration keeps its per-OS part under os.<type> (install, launch, skill, settings, build_settings), with ready checks as objects. An app env stored before this must be put again. The driver's Linux defaults module is now agentenv_protocol.computer._linux, since agentenv_protocol.computer.linux is the decorator. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n part, and refuse a per-OS skill beside a method An app env stored before the per-OS shape loads again: AppDeclaration reads its top-level install and launch as its OS parts, and its ready checks by name. A per-OS skill file on a class whose skill is an @agent_skill method is refused as a skill declared twice, since the app's server serves the method on every OS and nothing serves the file yet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…fore checking it A remote --find-links split over two lines was refused as naming a local folder, since its first line holds no URL. The check now reads the file as pip does, joining each line that ends in a backslash to the next. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
earakely-scale
deleted the
edgararakelyan/fd-4360-m12-the-install-format-fetch-at-put-install-from-the-store
branch
October 30, 2026 15:27
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
M1.2 of the CUA env epic (FD-4345): the install format, fetched at put, FD-4360. It follows the design doc CUA Env M1.2: The Install Format, Fetched at Put, whose eight choices were decided on 2026-10-30.
Everything a computer installs now comes from files fetched once when the computer env is put. Builds never reach the internet.
What's in it
The format (
[install.linux]in a driver's env.toml,Linux(...)in an app's class):packages: the OS's packages, as apt takes them:name,name=versionorname/suite.sources: extra apt repositories (a URL, suite, components and a signing key in the layer's folder).downloads: files fetched by URL and checked by sha256, for every arch or one, placed under the layer's prefix.run: scripts of the folder, run as root or as the session user.ports: those the layer listens on. M1.3 checks them against each other.files,pythonandservicesare unchanged. An install that uses none of the new fields stores the same JSON as before.Fetch at put (
env/envs/computer/utils/packages.py,resolve_in_base.py):agentenv-packages/files/), shared by every env and version.Install from the store (the composer):
RUN --network=none:pip --no-index --no-deps;@install_stepmethods.The SDK (breaking: the PR title has a
!):linux = Linux(...)andlaunch = Launch(...)class attributes:@linux(...)takesLinux's fields;@install(os_type, spec)takes any OS type, a plugin's included, with that OS type's own install model.launch=always;skill=(a file), andsettings=andbuild_settings=, whose fields add to the class's on that OS.@install_step(os=...)and@ready_check(os=...)run on every OS the app installs on unless they name some. Naming one the app declares no install for is refused when the class is read.platform.detect()).os.<type>(install,launch,skill,settings,build_settings), and ready checks are objects. An app env stored in the old shape still loads:AppDeclarationreads its top-levelinstallandlaunchas its OS parts, and its ready checks by name.@agent_skillmethod. That's refused as a skill declared twice, since the app's server serves the method on every OS and nothing serves skill files until FD-4369.agentenv_protocol.computer._linux, sinceagentenv_protocol.computer.linuxis the decorator.Linuxmodel gainspackages,sources,downloads,runandports, withPackages,AptSource,DownloadandRunScript.python -m agentenv_protocol.computer.run_install_step <file>:<Class> <method>runs an@install_stepduring the build. Steps were declared but never run until now.Build settings for apps:
BuildSettingsbesideSettings: values its env fixes in its computer's image, as the base computer's build-phase settings are.build_settings = { locale = "fr_FR" }), checked against the class's schema at put..agentenv/build_settings.jsonin the app's folder, so they're part of the image's key, and a change builds another image from the same lock.self.build_settings. An install step has noself.settings: reading one raisesAttributeErrorsaying a deploy's settings aren't known at build.SecretStrbuild setting is refused at declaration, since the image would keep it.Changed while building
run_install_step. A module namedinstall_stepwould have replaced the package'sinstall_stepdecorator once imported.BuildSettings(above), so an install step reads values its env fixed rather than a half-madeself.settings;Signed-Byare tested in CI.-r,-c), an editable install, a local path or a local folder of wheels is refused at put, since the lock's inputs are that file alone and a local project can't build on the resolver's read-only context;pip install --no-deps).Packages,AptSource,DownloadandRunScriptare defined once, in the protocol, with their checks; the framework imports them. An app's bad value now fails when its class is defined, and the declare step names the field (Download.sha256: …).Lockand its locked files), validated when read from the store.packages.packages.py's source. Editingpackages.pyno longer re-resolves every lock, and the owner's uid stays out of the inputs.missing()andunlocked()are onefolder_problems().@installfor plugin OS types;os=on install steps, defaulting to every OS;Testing
tst/unitand the protocol tests passed 11,822, with 66 skipped. One Hermes test fails on macOS only, as before, since it reads/proc/self/fd.tst/integration/computer/app) now takes an apt package (hello), a download checked by sha256, a run script that uses the package, and an install step as the session user. The test checks all four on the deployed computer.tst/unitand the protocol tests passed 11,833, with 66 skipped (11,851 on 83abf63, after the requirements and sha256 checks; 11,853 on 80e81a1; 11,860 on b5b089b, the per-OS app API). The test app's integration run passed again on each, the last with a Linux-added setting, a Linux-only install step and ready check. Besides the macOS-only Hermes failure, one gateway timing test failed under the laptop's Docker load and passed alone; this PR doesn't touch it.model_endpoint_configured);2.70.0~noble) and its build setting reaching both the install step and the server.hello=2.10-3build1was installed at that version;libreofficekit-data/noble-backportscame from backports (~bpo24.04.1);ghcame from GitHub CLI's repository, whose binary keyring holds an expired key beside the current one;zeek-btestcame from a flat OBS repository (suite = "/", armored key);self.settingsraised the new error.0.1.404.dev402), the framework pinned to it, and the shipped drivers' requirements pinned byshipped_bundles.py pin;0.1.404.dev402, and every resolve fetched it from that index.agent-env bundle register;hello2.10-3build1, 65 wheels across the reference driver's and the app's venvs, and the download; the store holds 44 unique files for both. arm64 was ready after its probe; amd64 built but unverified.🤖 Generated with Claude Code


Confidence Score: 5/5
The PR appears safe to merge; no new actionable issues remain from this review.Summary
The PR fetches install files at put, builds offline from stored locks, and adds per-OS app declarations and build settings.
Reviews (10) · Last reviewed commit: "fix(computer): join a requirements line ..." · Reviewed by Greptile