Repository navigation
feat(computer): each app in a computer serves its own data plane, which loads, snapshots and skills reach by name - #426
Conversation
- An app declares the paths it keeps its data in under the session user's home (@linux(paths=...)). With no data hooks, they serve its data plane: data/add writes files under them, data/get returns them as a file tree (declared on its card), data/reset puts back the copy its server keeps before it first serves on a boot, the app as installed. - @add_data, @get_data and @reset_data hooks override paths. The base class serves either around the app's contract: a load and a reset stop the app it launched (its pid kept in the app's folder) and relaunch it after, a load that fails is followed by a reset, and each call runs within data_timeout_s. - An app serves its skill file through skill/v1, unless it writes its skill itself. - computer-app/v1's params carry the app's paths. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nments - The driver's card lists each app composed onto the computer as a child environment, named for its folder: its own server's card with every path under /svc/<name>/, or its name and route alone while its server isn't answering. A new AgentEnvEnvironment hook, children_environments(), lists them each time the card is served. - The driver forwards every request under /svc/<name>/ to that app's server on its socket, with no timeout of its own; a name it has no app for is a 404. - The driver's own data/get leaves out the files under any path an app keeps its data in, as the apps' cards say. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s apps by name A load sends each environment to the data plane its name reaches: the computer's own to the driver, an app's to the app's route. A computer's snapshot keeps its files beside each app's export, with data/get params given per environment, and leaves out an app that keeps nothing yet. add_skills installs the computer's skill and each app's. Gate 1 refuses apps whose paths overlap and an app named like its computer; gate 3's probe asks each app for its export once; boot records the card read after ready, with every app on it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ut safely Data calls and launches take one turn at a time, so two loads never overlap and a launch never starts the app mid-load. A sync hook runs in a thread, timed from the start; a call past data_timeout_s fails then, and its reset and relaunch, and the next call, wait for its work to end. Stopping an app ends its whole process group, though its first process exited. An app's paths may not overlap, and a load may not write through a link out of them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ing for its threads A shielded async hook that never ended held the app's turn for good, so every later data call and launch timed out. A call past data_timeout_s is now cancelled: what awaits stops at once, and a step run in a thread, a sync hook's or the defaults' disk work, is still waited for to its end. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ent the app data plane An app's @add_data hook gets its parts as they were sent, inline or as a read grant; agentenv_protocol.computer.part_bytes returns a part's bytes either way, as the paths defaults read them. The protocol README now says how an app's server serves its data plane and skill and how the driver fronts its apps. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s apps A computer env now checks its env.toml before anything is written (accept_toml), and a computer env's and a computer app env's environment_name default to their folder's there too, so the bundle check knows a computer env's name and its apps' and refuses a seed named after none of them, as it does for an MCP server's. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…oting computer Run alone the restart test passes in ~55 s on this branch and on main; after the other computer suites on a loaded machine, its 60 s wait ran out (once on #426's CI, once on an EC2 box). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…erations and the driver's app errors in one shape The data-call logic (turns, stop and relaunch, the reset after a failed load, timeouts) moves out of ComputerAppEnvironment, the class app authors subclass, into app_data.AppDataPlane, which the app's handlers and launch hand their calls to. client.operations(card) replaces each hand-rolled read of a card's capabilities.operations. The driver answers a request for an app it doesn't have, or one whose server doesn't answer, with the protocol's errors (target_unavailable, delivery_failed). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…, the driver's app errors Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| def _computer_app_data_plane(self) -> AppDataPlane: | ||
| self._computer_app_runtime() | ||
| return self._computer_app_data |
There was a problem hiding this comment.
Removing _computer_app_keep_initial breaks app startup in PR #430. At its supplied head, packages/agentenv-protocol/src/agentenv_protocol/computer/serve_app.py:44 still calls app._computer_app_keep_initial(). Against this changed base class, that raises AttributeError before the app server starts.
Keep a forwarding method until PR #430 uses _computer_app_data_plane().keep_initial(), or update that caller as part of the stack.
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/agentenv-protocol/src/agentenv_protocol/computer/app.py
Line: 442-444
Comment:
**Dependent apps cannot start**
Removing `_computer_app_keep_initial` breaks app startup in PR #430. At its supplied head, `packages/agentenv-protocol/src/agentenv_protocol/computer/serve_app.py:44` still calls `app._computer_app_keep_initial()`. Against this changed base class, that raises `AttributeError` before the app server starts.
Keep a forwarding method until PR #430 uses `_computer_app_data_plane().keep_initial()`, or update that caller as part of the stack.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.There was a problem hiding this comment.
Fixed on #430, which is stacked on this PR: its merge of this head, f71db39, changes serve_app.py:44 to app._computer_app_data_plane().keep_initial(). That was the one remaining call site of the removed method; nothing else in #430 calls it (checked with grep). test_computer_serve.py starts apps through serve_app, and #430's CI is running on f71db39.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
What
Each app in a computer serves its own data plane, and the framework reaches each app by name. A load, a snapshot and
add_skillseach cover the computer and every app in it.@linux(paths=[...]), relative to the session user's home. Its own server then serves:data/add, which writes files under those paths and refuses any other path;data/get, which returns them as a file tree;data/reset, which puts them back as installed, from a copy the server keeps when it first starts on a boot.@add_data,@get_dataand@reset_datareplace the defaults. A hook reads the parts it gets, inline or as a read grant, withagentenv_protocol.computer.part_bytes, now exported.data_timeout_s(300 s) fails then, saying so, while its reset and relaunch wait for its work to end./svc/<app>/, and it forwards requests under that route to the app's server. Its owndata/getleaves out the paths apps declare.data/add. The computer's environment loads first, so each app's files end as the app's own export has them. Any other environment is left out, with a warning.file_paths, and each app's export.data/getparams are now given per environment, sofile_pathsreach only the computer. An app that keeps nothing yet is left out, rather than failing the snapshot.add_skillsinstalls the computer's skill and each app's.accept_toml), and a computer env's and an app'senvironment_namedefault to their folder's there too.Issue: FD-4369 (M1.4). Builds on #423, merged.
How
computer/app.py,app_data.py,app_runtime.py,session.py).__init_subclass__moves a subclass's data hooks into_data_hooks.data/add,data/getanddata/resetfrom the hooks, or from the paths' defaults (app_data.add,get,reset).served_operationsserves only what one of them handles. The card declares an intake file tree only when the defaults serve.serve_appkeeps the initial copy in.agentenv/initialbefore it serves.launch_apprecords the app's pid.stop_appstops it: SIGTERM to its process group, then SIGKILL after 10 s.AppOs.pathsis written only when set, so stored declarations without paths don't change.agent_env_environment.py,computer/apps.py,driver.py,runtime.py).AgentEnvEnvironment.children_environments()adds children to the card each time it's served./svc/<app>./svc/{name}/{path}forwards to the app's Unix socket: 404 for an unknown app, 502 when the app doesn't answer.runtime.app_paths()lists the apps' paths, which the driver'sdata/getleaves out.DeployedEnv.get_child_env_card). A composed card that serves a data plane of its own resolves its own name to itself. A computer's driver card is one; a gateway's card serves none, so it is unchanged. A computer app deployed alone shares its name with its computer, so that name reaches the computer's files, as before.ComputerEnv)._loadresolves every environment's address from the recorded card. It refuses one that the card doesn't list or that serves nodata/addbefore anything is sent.inside.add_artifacts, which now takes the address.snapshot_env)._card_environmentslists the card itself first when it exports beside children, then each child its name reaches that exports.data_get_paramsis now{environment_name: params}.boot.readyreturns the card read after the ready status, so each app is on the card the deployment records.rpctakes a child's card.Not in this PR
Testing
Unit: 12,360 passed, none failed, on macOS (
tst/unitand the protocol's tests). New and extended tests:test_computer_app_data.py: paths load, export and reset as installed; a path outside them is refused; hooks override paths; the app is stopped and relaunched around a load and a reset, and left stopped if it wasn't running; a failed load is followed by a reset; a call past its timeout fails; the skill file is served.test_computer_app_server.py: the driver lists its apps as children, forwards each one's requests, and leaves their paths out of its owndata/get.data/add, are refused before anything is sent.snapshot_computer_envkeeps each app's export beside the computer's files, withfile_pathssent to the computer alone; an empty app is left out.add_skillsinstalls the computer's skill and each app's.Integration, laptop (local Docker,
localhost:5000):computer_app_test.py, 3 passed (12 min), on the code with the review fixes.tst/integration/computer/app: notes now keeps its data in~/Notes, with a welcome note its install leaves there. The clock serves its alarms through hooks.datatask:file_pathsthat includeNotes;data/resetto each app from inside the computer, through the driver's routes;Notes, the notes app's three notes, and the clock's alarms.🤖 Generated with Claude Code
Update PR #430's app startup caller or preserve the removed helper before merging the stack.
Fix with agent prompt
Summary
The PR gives each computer app its own named data routes and includes app exports and skills beside the computer's own.
Diagram
%%{init: {'theme': 'neutral'}}%% flowchart LR Framework[Framework] --> Driver[Computer driver] Driver --> Own[Computer files] Driver --> Route["/svc/app"] Route --> App[App server] App --> Data[App data hooks or paths] App --> Skill[App skill]Reviews (6) · Last reviewed commit: "refactor(computer): an app's data plane ..." · Reviewed by Greptile