Skip to content

feat(computer): each computer app runs its own server, which launches it and says when it's ready - #385

Merged
earakely-scale merged 7 commits into
mainfrom
edgararakelyan/fd-4368-m15-app-launch-and-readiness
Oct 29, 2026
Merged

earakely-scale merged 7 commits into
mainfrom
edgararakelyan/fd-4368-m15-app-launch-and-readiness

Conversation

@earakely-scale

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

Copy link
Copy Markdown
Collaborator

M1.5 of the CUA env epic (FD-4345): a computer app starts and says when it's ready, FD-4368. It follows the design doc CUA Env M1.5: App Launch and Readiness, with all seven choices decided, and the full example of an app in this design, M1.5 example app.

Each computer app is its own server. The app's ComputerAppEnvironment class runs on the computer as a server that serves the app's card. It launches the app into the desktop session and says whether the app is ready. The driver finds each app by its server's socket and asks it. Nothing here is specific to XFCE, so another base image or OS brings only its own session module.

What's in it

The protocol (agentenv_protocol.computer):

  • An app's own server (serve_app.py, its work in app_runtime.py's AppRuntime, as a driver's is in runtime.py). The computer's init runs python -m agentenv_protocol.computer.serve_app <file>:<Class> from the app's folder, in the app's venv. The server serves the class's card and tools on the Unix socket .agentenv/server.sock in that folder, with a new extension, computer-app/v1:

    • params: the app's launch;
    • launch (POST): starts the app in the desktop session;
    • ready (GET): returns an AppReadiness: ready, problems, launched, waited_s and timeout_s. It runs the app's checks at once when asked: a window matching launch.window, a port answering on loopback, a command exiting 0, and each @ready_check. Each check is in-process with a 10 s limit; a sync check runs in a thread, one call at a time even past its limit, and an exception becomes a problem.
  • Once the desktop session is up, the server:

    • adds the app's launcher to the session user's menu (~/.local/share/applications). The launcher runs python -m agentenv_protocol.computer.launch <folder>, which asks the server to launch the app, so the server starts every copy of it;
    • launches a boot app, once per boot. Every launch, at boot or on demand, is marked in /run/agentenv/apps/. The mark is the one record of the launch, and the app's clock runs from its time, so a server that restarts takes the app as launched, keeps its clock and leaves it alone.
  • A server that can't start says why: it writes the error to .agentenv/server.failure in the app's folder, cleared at each start, and the driver reports it in place of "its server isn't answering yet". A start in the session that fails (adding the launcher, say) stops the server the same way, so supervisord retries it.

  • The session (session.py) holds what the server does in the desktop session, the Linux way: wait for it, launch into it in a session of its own, add a launcher, and list windows with xprop, titled by _NET_WM_NAME or WM_NAME. Another OS type brings its own.

  • computer-ready asks every app's server at once (apps.py), on the socket in each folder under /opt/agentenv/apps, at the endpoints its card advertises, and counts one as not answering after 15 s:

    • every app's server must answer;
    • each boot app, and each app with no launch, must say it's ready;
    • a demand app counts once something launches it.

    A failing check says what it still waits on: "not ready yet, N s in", or once past the app's timeout_s, "not ready after N s". The app's clock starts from its launch.

  • A new app extra: agentenv-framework-protocol[app], with mcp and uvicorn.

  • Refused declarations:

    • a Ready that waits on nothing;
    • window readiness with no launch.window to match;
    • a launch.window that matches by anything but class, instance or title.

    _declare reports a pydantic error on one line, since put shows its last line.

agent-env:

  • The composer gives every app:
    • a venv with the protocol's app extra, plus the app's own requirements if it names any;
    • the server as a supervisord program, app-<name>, beside its services, app-<name>-<service>;
    • an .agentenv folder owned by the session user.
  • The wait: a deploy, a reset and the probe at put each wait up to the longer of 180 s and the slowest boot app's timeout_s plus 30 s (boot.ready_timeout). Each computer-ready status call may take 30 s, the driver's own checks and then the apps' servers.
  • Put refuses an app whose install leaves out its class's file, since the server serves the class from the app's folder.

Testing

  • Unit: 10,854 passed and 66 skipped (tst/unit), and 887 passed in the protocol's tests.
  • New tests (test_computer_app_server.py, with real uvicorn servers on Unix sockets):
    • The app's own server:
      • readiness by launch, window and @ready_check;
      • checks that fail, hang or raise, run at once without holding up the server;
      • a boot app launched once per boot across a server restart, and a demand app's launch kept across one;
      • a sync check past its limit making one call however often readiness is asked;
      • a failed launch reported, and retried by the next server;
      • the lifespan starting the app once the server serves;
      • a server that can't start writing why, for each way it can fail.
    • The driver asking every app's server at once: a missing server, one that failed to start, a boot app not yet ready, then "not ready after", and a demand app that counts once it's launched through the launcher.
    • The session: a launch in a session of its own from the app's folder, the launcher's desktop entry, the xprop parse without its escapes, and an xprop cut short being killed.
    • The composer:
      • an app that requires nothing gets a venv with the app extra, from a release or from this checkout;
      • the server program;
      • ready_timeout;
      • put's refusal of a class file the install leaves out.
  • Integration (tst/integration/computer, laptop arm64, real Docker): 21 passed and 1 skipped (the agent task, which needs a model endpoint). The fixture app has no requirements of its own. Its server launches a Tk window of class Notes at boot, and a @ready_check waits for its service's file. Deployed alone, computer-ready reports "app notes" ready, the launcher is in the session user's menu, and the app is marked launched.
  • Chaos: 11/11 passed, on the fixture app deployed alone (rerun on 6ecb0f4; one put's probe flaked on P6, the reference driver's chord check, and passed rerun):
    • Window killed once ready: the tools keep working (a screenshot is served), and computer-ready reports "app notes" not ready, with "no window with class 'Notes'".
    • A window that never opens at deploy (it opens at put, then a setting hides it): the deploy fails after 180.9 s with "app notes: not ready after 60 s: no window with class 'Notes'", and nothing is left running.
    • The app's server killed once ready: supervisord restarts it with a new pid, and it answers ready while leaving the one window it launched alone. The menu launcher's command then opens a second copy through it, making two windows.
    • A server that can't start at deploy (a setting makes the class refuse to construct): the deploy fails after 181.0 s with "app notes: its server failed to start: RuntimeError: this app has no server", and nothing is left running.
    • A launcher the server can't add at deploy: the server stops, and the deploy fails after 180.7 s with "app notes: its server failed to start: PermissionError: ~/.local/share/applications isn't writable".
    • Each teardown is clean.
  • Modal VM (amd64, the image in ECR): 4/4.
    • The fixture app composes, and gate 3 probes it on modal_vm.
    • Deployed alone in 9.1 s, its server launched its window, added its launcher and said it's ready.
    • Its server, killed, was restarted and left the one window alone.
    • Teardown is clean.
  • CI: all 27 checks passed on e42f65d, the merge of main; 6ecb0f4 is running.

🤖 Generated with Claude Code

RetriggerView in GreptileConfidence Score: 5/5

The PR appears safe to merge; both numbered findings are addressed and no new actionable issue was found.

Summary

Each computer app now runs its own server to launch the app and report readiness.

  • Each computer app runs a server that launches it and reports when it is ready.
  • Computer readiness now includes checks from every app server.
  • Computer builds give each app a server venv and wait for boot apps to be ready.

No new actionable issues were found. Tests were read but not run.

Diagram

sequenceDiagram
    participant Init as Computer init
    participant App as App server
    participant Session as Desktop session
    participant Driver as Computer driver
    Init->>App: Start server
    App->>Session: Wait for session and add launcher
    App->>Session: Launch boot app unless already marked
    Driver->>App: Read card and request readiness
    App->>Session: Check window, port, command, and custom checks
    App-->>Driver: Readiness and launch state
    Note over App,Driver: Startup errors are written to server.failure
Loading

Reviews (6) · Last reviewed commit: "fix(computer): a failed app start stops ..." · Reviewed by Greptile

Base automatically changed from edgararakelyan/fd-4366-m09-load_artifact-and-run_code-inside-the-computer to main October 29, 2026 15:04
A computer app's launch and readiness, which M1.1 declares, now act. The composer writes each app's
.agentenv/app.json and, with a launch, a launch script, a launcher and, for a boot app, an XDG autostart entry.
The SDK runtime counts each app that starts with the computer in computer-ready: by a window matching
launch.window (a new list_windows operation, with a Linux default over xprop and the reference driver's over
python-xlib), a port, a command, or its @ready_check methods, run in the app's venv by _ready. A deploy, a reset
and the probe wait up to the slowest boot app's own timeout. Declarations whose readiness can't be checked are
refused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@earakely-scale
earakely-scale force-pushed the edgararakelyan/fd-4368-m15-app-launch-and-readiness branch from 1f3e1aa to 8b2a455 Compare October 29, 2026 15:25
An app's ComputerAppEnvironment class now runs on the computer as the app's own server
(serve_app), in the app's venv, on .agentenv/server.sock in its folder. It serves the app's
card with computer-app/v1: the app's launch as params, launch, and ready, which checks the
app's window, port, command and @ready_check methods in-process, each within 10 s. Once
the desktop session is up it adds the app's launcher to the session user's menu, which
launches through the server, and launches a boot app once per boot. What it does in the
session is the SDK's per-OS session module.

The driver finds apps by their sockets: computer-ready needs every app's server to answer,
each boot app and each app with no launch to be ready, and a demand app once launched.
list_windows leaves the driver contract, and app.json, the launch script, the XDG entries
and _ready go.

The composer gives every app a venv with the protocol's new app extra (mcp, uvicorn) and
its server as a supervisord program, and put refuses an app whose install leaves out its
class's file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@earakely-scale earakely-scale changed the title feat(computer): launch computer apps and wait until they're ready feat(computer): each computer app runs its own server, which launches it and says when it's ready Oct 29, 2026
@earakely-scale
earakely-scale marked this pull request as ready for review October 29, 2026 16:48
@earakely-scale
earakely-scale requested a review from a team as a code owner October 29, 2026 16:48
Comment thread packages/agentenv-protocol/src/agentenv_protocol/computer/app.py Outdated
Comment thread packages/agentenv-protocol/src/agentenv_protocol/computer/app.py Outdated
Comment thread packages/agentenv-protocol/src/agentenv_protocol/computer/session.py Outdated
Comment thread packages/agentenv-protocol/src/agentenv_protocol/computer/app.py Outdated
Comment thread packages/agentenv-protocol/src/agentenv_protocol/computer/apps.py Outdated
earakely-scale and others added 4 commits October 29, 2026 10:12
…start

An app's server ran its checks one after another and the driver asked each app in turn,
while a deploy gave the whole computer-ready status 10 s, so one slow check timed the
status out and the deploy's error stopped naming the app. Checks now run at once in the
server, each still within 10 s, the driver asks every app at once and counts a server as
not answering after 15 s (ANSWER_TIMEOUT_S), and the deploy's status call allows 30 s.

A server that can't start writes why to .agentenv/server.failure in the app's folder,
cleared at each start, and the driver reports it instead of "its server isn't answering
yet".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…and xprop's titles

A restarted app server now finds a demand app it launched as well as a boot one: every
launch marks /run/agentenv/apps/, so the driver keeps counting the open app.

A sync @ready_check past its bound keeps running in its thread, which can't be stopped, so
a later poll waits on that call instead of starting another, and polls can't fill the
worker threads.

The session reads window titles and classes without xprop's escapes, so a title with
quotes matches, and kills xprop when a window check is cut short.

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

What an app's server does moves out of app.py into app_runtime.py, as a driver's does into
runtime.py: AppRuntime holds its state, launches the app and runs its checks, and the class
keeps thin launch and ready methods and the declaration. A subclass whose __init__ skips
super().__init__() now fails at start, saying so in .agentenv/server.failure.

The ready answer is an AppReadiness the server dumps and the driver validates. Whether the
app was launched comes from its boot mark alone, and its clock from the mark's time, so a
restarted server no longer restarts the app's clock. The driver and the launcher take the
computer-app/v1 endpoints from the app's card instead of building them, and checks run as
coroutines rather than lambdas.

The serve_app startup tests join the serve tests, sharing one import-isolation fixture.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comment thread packages/agentenv-protocol/src/agentenv_protocol/computer/session.py Outdated
…s match

The app server's start in the desktop session ran as a background task whose error nobody
read, so a launcher it couldn't add left readiness at "not launched yet" until the deploy
timed out. A start that fails now stops the server, which writes the error to
.agentenv/server.failure and exits, and the driver reports it as for any server that
can't start, a demand app's too. A failed launch is still reported by readiness.

The session reads a window's title from WM_NAME when it sets no _NET_WM_NAME, as older X11
apps do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@earakely-scale
earakely-scale merged commit 4e0f85f into main Oct 29, 2026
28 checks passed
@earakely-scale
earakely-scale deleted the edgararakelyan/fd-4368-m15-app-launch-and-readiness branch October 29, 2026 19:50
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