Skip to content

feat(computer): each app in a computer serves its own data plane, which loads, snapshots and skills reach by name - #426

Merged
earakely-scale merged 8 commits into
mainfrom
edgararakelyan/fd-4369-m14-app-data-planes-through-the-driver
Nov 1, 2026
Merged

earakely-scale merged 8 commits into
mainfrom
edgararakelyan/fd-4369-m14-app-data-planes-through-the-driver

Conversation

@earakely-scale

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

Copy link
Copy Markdown
Collaborator

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_skills each cover the computer and every app in it.

  • An app declares where it keeps its data, with @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.
  • An app can use hooks instead. Its own @add_data, @get_data and @reset_data replace the defaults. A hook reads the parts it gets, inline or as a read grant, with agentenv_protocol.computer.part_bytes, now exported.
  • The app base class enforces the contract either way:
    • a load or a reset stops the running app and relaunches it afterwards;
    • a failed load is followed by a reset;
    • data calls and launches take one turn at a time;
    • a call that runs past data_timeout_s (300 s) fails then, saying so, while its reset and relaunch wait for its work to end.
  • The app serves its skill file through skill/v1.
  • The driver fronts its apps as child environments. Its card lists each app's card, rooted at /svc/<app>/, and it forwards requests under that route to the app's server. Its own data/get leaves out the paths apps declare.
  • A load routes each environment by its name. The computer's own name goes to the driver, writing into the home. An app's name goes to that app's 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.
  • A computer's snapshot is one universe. It holds the computer's files, under file_paths, and each app's export. data/get params are now given per environment, so file_paths reach only the computer. An app that keeps nothing yet is left out, rather than failing the snapshot.
  • add_skills installs the computer's skill and each app's.
  • Gate 1 refuses at put:
    • two apps whose paths are the same, or one inside the other;
    • an app named like its computer env.
  • Gate 3's probe asks each app that exports for its export once, through the driver.
  • The bundle check knows a computer env's names, its own and its apps', whether the bundle writes it or it's in the store, and refuses a seed named after none of them. A computer env now checks its env.toml before anything is written (accept_toml), and a computer env's and an app's environment_name default to their folder's there too.

Issue: FD-4369 (M1.4). Builds on #423, merged.

How

  • Protocol, apps (computer/app.py, app_data.py, app_runtime.py, session.py).
    • __init_subclass__ moves a subclass's data hooks into _data_hooks.
    • Base dispatchers serve data/add, data/get and data/reset from the hooks, or from the paths' defaults (app_data.add, get, reset).
    • served_operations serves only what one of them handles. The card declares an intake file tree only when the defaults serve.
    • serve_app keeps the initial copy in .agentenv/initial before it serves.
    • launch_app records the app's pid. stop_app stops it: SIGTERM to its process group, then SIGKILL after 10 s.
    • AppOs.paths is written only when set, so stored declarations without paths don't change.
  • Protocol, driver (agent_env_environment.py, computer/apps.py, driver.py, runtime.py).
    • AgentEnvEnvironment.children_environments() adds children to the card each time it's served.
    • The driver returns its apps' cards, each rewritten under /svc/<app>.
    • A route /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's data/get leaves out.
  • Resolving names (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.
  • Loads (ComputerEnv).
    • _load resolves every environment's address from the recorded card. It refuses one that the card doesn't list or that serves no data/add before anything is sent.
    • It then sends each environment's files there through inside.add_artifacts, which now takes the address.
  • Snapshots (snapshot_env).
    • _card_environments lists the card itself first when it exports beside children, then each child its name reaches that exports.
    • data_get_params is now {environment_name: params}.
    • An empty file tree is left out of the universe, and refused only when every environment is empty.
  • The rest.
    • boot.ready returns the card read after the ready status, so each app is on the card the deployment records.
    • The bundle check knows a computer env's own name and its apps' names, for one the bundle writes and one in the store.
    • The probe's driver client rpc takes a child's card.
    • The protocol README describes an app's data plane and how the driver fronts its apps.

Not in this PR

  • A computer app deployed alone. Its name reaches the computer's files, so its own data plane and skill aren't reached by name there. To exercise an app's data plane, compose it into a computer env.

Testing

Unit: 12,360 passed, none failed, on macOS (tst/unit and the protocol's tests). New and extended tests:

  • Protocol:
    • 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 own data/get.
    • Review fixes:
      • data calls take turns;
      • a sync hook past its timeout fails then, while its reset and relaunch wait for its thread;
      • a stop ends what a launch started after its first process exits;
      • overlapping paths are refused where they're declared;
      • a load through a link out of the app's paths is refused.
    • The conformance probe asks each app that exports for its export once.
  • Framework:
    • A computer's card resolves its own name to itself.
    • A snapshot keeps the card first beside its children, with its own params, and leaves out what doesn't export.
    • A computer loads its own files, then each app's through the app's route, leaving out other environments. Unknown names, or apps serving no data/add, are refused before anything is sent.
    • snapshot_computer_env keeps each app's export beside the computer's files, with file_paths sent to the computer alone; an empty app is left out.
    • add_skills installs the computer's skill and each app's.
    • Gate 1 refuses overlapping paths and an app named like its computer.
    • Boot returns the card read after ready.
    • The bundle check accepts a seed named after a computer env or one of its apps, and refuses a misnamed one, both for a computer env from the same bundle and for one in the store.

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.
  • The new data task:
    1. loads a universe of the computer's files, two notes and the clock's alarms;
    2. snapshots the computer with file_paths that include Notes;
    3. sends data/reset to each app from inside the computer, through the driver's routes;
    4. snapshots again.
  • What it checks:
    • The first snapshot keeps the computer's files without Notes, the notes app's three notes, and the clock's alarms.
    • After the resets, notes holds only its welcome note, the clock has no alarms, and the computer's files are unchanged.
    • The existing alone and together tasks still pass on the changed apps, each composed and probed by gate 3.

🤖 Generated with Claude Code

RetriggerView in GreptileConfidence Score: 4/5

Update PR #430's app startup caller or preserve the removed helper before merging the stack.

Fix All in CursorFindings

  1. P1 Dependent apps cannot start ▶
Fix with agent prompt
### Issue 1
packages/agentenv-protocol/src/agentenv_protocol/computer/app.py:442-444
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.

Summary

The PR gives each computer app its own named data routes and includes app exports and skills beside the computer's own.

  • Each computer app can load, export, and reset its own data.
  • The computer driver lists apps and forwards requests to their servers.
  • Computer loads send each named environment's files to its own data plane.
  • A computer snapshot keeps the computer's files and each app's export separately.
  • Adding skills to an agent can include skills served by computer apps.

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]
Loading

Reviews (6) · Last reviewed commit: "refactor(computer): an app's data plane ..." · Reviewed by Greptile

earakely-scale and others added 3 commits October 31, 2026 19:18
- 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>
@earakely-scale
earakely-scale requested a review from a team as a code owner November 1, 2026 02:24
…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>
earakely-scale and others added 3 commits October 31, 2026 20:06
…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>
earakely-scale added a commit that referenced this pull request Nov 1, 2026
…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>
earakely-scale added a commit that referenced this pull request Nov 1, 2026
…, the driver's app errors

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comment on lines +442 to +444
def _computer_app_data_plane(self) -> AppDataPlane:
self._computer_app_runtime()
return self._computer_app_data

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 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.

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.

Fix in Cursor Fix in Claude Code Fix in Codex

@earakely-scale earakely-scale Nov 1, 2026 •

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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.

@earakely-scale
earakely-scale merged commit 1793366 into main Nov 1, 2026
25 checks passed
@earakely-scale
earakely-scale deleted the edgararakelyan/fd-4369-m14-app-data-planes-through-the-driver branch November 1, 2026 16:08
earakely-scale added a commit that referenced this pull request Nov 1, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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