Files
rea/docs/desktop-build-computer.md
Edgar ArakelyanandClaude Opus 5.5 fc4c381dd7 feat(desktop): keep an agentenv computer up and drive it through REA's MCP tools (#9041)
* feat(desktop): keep an agentenv computer up and drive it through REA's MCP tools

- A Python helper keeps one agentenv computer up per build with
  run_bundle(keep=True) and reports its address, MCP URL, control token
  and sandbox id; it tears the computer down when stdin closes.
- AgentEnvComputer owns the helper, waits for readiness, holds one MCP
  session to the computer's computer_* tools, records through the
  computer-recording routes, and removes the sandbox's containers by
  label when agentenv cannot confirm teardown. The token never leaves it.
- start_build_computer starts a computer from a request (archive package,
  apt repository or .deb, opened on demand with REA in /opt/rea) or from
  a bundle folder; act_on_build_computer forwards screenshot, click,
  drag, scroll, type, key and wait and returns the driver's images
  unchanged; close_build_computer tears it down.

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

* fix(desktop): remove a build computer's container by name when agentenv cannot confirm teardown

agentenv's local provider runs an ordinary computer as agent-<sandbox id>
without the agentenv.sandbox label, so the label-only fallback found
nothing and reported a running computer as removed. The fallback now
lists containers by exact name and by label. The real test holds its
recording open long enough for a first frame, checks the container by
name, and runs rea inside the computer; the guide and tool descriptions
say the app opens from the Applications menu and that a screenshot after
opening a menu should come from a wait.

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

* fix(desktop): own failed build-computer starts, keep ids past oversized screenshots, bound recordings

- A start is owned from the helper's spawn. One that fails and whose
  teardown is unconfirmed stays held under a computer_id close can retry;
  closeAll refuses new starts, cancels and joins starts in progress, then
  closes every held computer.
- When the first screenshot is too large, the error carries REA's result
  with the computer_id, and start_build_computer takes screenshot region,
  format and quality.
- Recordings are refused before reading when the manifest's total exceeds
  the allowance, then fetched one segment at a time, each read bounded and
  checked against its size and sha256.
- The helper keeps the tail of its output (a new tail retention mode in
  ProviderProcessSupervisor), so a build's own error is not crowded out.
- A helper that reported no computer exits on its own before SIGTERM, so
  no phantom owner is kept.

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

* fix(desktop): bound a build computer's action log

Every action kept its full arguments for the computer's whole life, so a long
session grew REA's memory without limit. The log now keeps the latest 256
actions within 256 KiB, cuts each string to 1 KiB and each list to 64 items
(naming what it cut), and keeps the total and the last applied index of each
kind of action, so rules over the whole session hold after entries drop.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 11:01:11 +08:00

8.8 KiB

Desktop build computer

A build computer is a local agentenv computer that an agent drives while it builds a desktop app environment. Claude Code cannot attach a new MCP server in the middle of a session, so REA keeps the computer up and forwards the agent's computer-use actions to it through three MCP tools:

Tool What it does
start_build_computer Starts a computer from a request or a bundle folder; returns its id and a first screenshot
act_on_build_computer Sends one action to the computer's driver and returns the driver's answer, images included
close_build_computer Tears the computer down, by agentenv's teardown or by removing its sandbox's containers

The tools are MCP-only, like the debugger sessions: the computer lives as long as the REA MCP server that started it, or until it is closed.

Requirements

  • Docker, and an agentenv-framework install whose agent-env program is named by the absolute REA_AGENT_ENV_COMMAND. Bundles target agentenv 0.9.1431. REA runs its helper, bridge/agentenv/computer.py, with that runner's Python: the interpreter on the program's #! line, or the python3 beside it in its virtual environment.
  • REA_DOCKER_COMMAND, the absolute path of the Docker CLI, lets REA remove a computer whose agentenv teardown was not confirmed. Without it, REA names the command to run instead.
  • For a request, REA_BUILD_COMPUTER_REA_ARCHIVE: the absolute path of a tar archive of a runnable REA, with its production dependencies, whose one top folder holds scripts/rea.mjs. The computer runs REA from it.

Set these where the MCP server starts; a shell export does not reach a server that is already running.

Start a computer

From a request, REA renders a bundle in a private temporary folder: a computer app on the ubuntu-24.04 base with the reference driver that installs the app, opens it on demand with gtk-launch <desktop entry> and keeps no seed and no data hooks. The app is not open when the computer starts: agentenv lists it in the desktop's Applications menu under Other by the environment's name, such as mousepad, and that entry launches it through the app's server.

{
  "source": {
    "kind": "request",
    "desktop_entry": "org.xfce.mousepad",
    "window_class": "Mousepad",
    "install": { "kind": "archive", "package": "mousepad" }
  }
}

install takes one of:

kind Installs
archive package from the Ubuntu 24.04 archive, on amd64 and arm64
apt_repository package from the repository at uri with suite and components, trusting the key file key_file
package_file The .deb at url, checked against sha256, for one architecture, after its apt dependencies are in

Build steps run with the network off, so a .deb's dependencies must be listed. The bundle adds REA in a build-only layer: Node.js 24.22.1, downloaded per architecture and checked against its release checksum, and REA from REA_BUILD_COMPUTER_REA_ARCHIVE, both unpacked into /opt/rea by a root build step, with node and rea on PATH. An exported bundle carries none of it.

From a bundle folder, such as one export-desktop-application wrote, REA starts that bundle's tasks/smoke.json as it is:

{ "source": { "kind": "bundle", "path": "/home/ada/mousepad-bundle" } }

REA runs the deploy with run_bundle(keep=True, emulate=False) on the local sandbox provider, so only the host's own architecture is built. The first start of a bundle builds its image and can take minutes; later starts reuse it. REA then waits until the computer's readiness checks pass, opens one MCP session to its driver and takes a screenshot, with the region, format and quality given in screenshot. The result holds computer_id, agentenv's sandbox_id, the environment name or bundle folder, and the screenshot's frame as structured content beside its image.

A failed start returns the last 16 KiB of the helper's stderr, where a failing build prints its error, and its exit status; a cancelled one stops the helper, which tears down whatever agentenv brought up. When that teardown is not confirmed, the error's computer_id names what REA keeps for close_build_computer to retry. Closing the server cancels starts still in progress and waits for them before closing every computer.

Act

act_on_build_computer takes computer_id, an action and the driver's own arguments for computer_<action>, which REA passes through:

action Arguments
screenshot region, format (png or jpeg), quality
click x, y, button, count (2 double-clicks), modifiers
drag path of [x, y] points, button, duration_ms, modifiers
scroll direction, x, y, amount, modifiers
type text, where \n presses Enter and \t Tab
key keys, one chord pressed in order, and repeat
wait ms

Pointer actions also take frame_id, space and norm_scale, and every action but screenshot takes screenshot: true for an image afterwards. That image is taken at once, so after an action that opens a menu or a window, ask for it with a wait of a few hundred milliseconds instead. Points are pixels of the latest frame, so take a screenshot first; with space: "norm" they are on a 0..norm_scale grid over it. Keys are W3C names or one character; REA translates model spellings such as ctrl+s, Return, esc and cmd and leaves other names for the driver to judge. An argument the action does not take is refused before anything is sent.

The result carries the driver's content unchanged, its text and images, after REA's own JSON: action_index and the driver's structured result. A refusal, such as a stale frame, is an error carrying the computer's message. A response larger than the MCP budget is refused with how to ask for a smaller image: a region, or jpeg with a lower quality. That error keeps REA's own result, computer_id included, as details.result, since the action or start has happened.

Every action is logged on the computer in order with the arguments sent, its start and end and whether it was applied, refused or failed. The log keeps the latest 256 actions within 256 KiB of JSON, dropping the oldest first. Each string argument, such as type's text, is kept to its first 1 KiB and each list, such as drag's path, to its first 64 items; an entry names the arguments it cut in truncated_arguments, and the driver gets them whole. The log counts every action, so it says how many it dropped, and keeps the index of the last applied action of each kind for rules over the whole session. The log and the computer's recording hooks (computer-recording/v1 start and end under REA's own request id, and the segments fetched through the driver's data/get one at a time, each read within its manifest size and checked against it) are kept for later build stages; they are not tools. A recording larger than a quarter of the V8 heap is refused before any segment is read.

Close

close_build_computer ends REA's MCP session and closes the helper's stdin, on which the helper runs agentenv's teardown. When agentenv confirms nothing is left, the result says teardown: "agentenv". Otherwise REA removes the containers of that exact sandbox: agentenv runs a computer as agent-<sandbox id> without a label and labels its other containers agentenv.sandbox=<sandbox id>, so REA lists both with docker ps -aq --filter, removes them with docker rm -f, and says teardown: "container_label". When neither confirms it, the error names what is left and the computer stays listed, so closing it again retries. Closing the MCP server closes every computer the same way, and a helper whose REA server exits sees its stdin close and tears down.

The control token that guards the computer's framework-only routes stays in REA: no result, error or log carries it. Code running in the computer as its user can still read it, which is acceptable for a local build.

See the specification for scope and verification.