Repository navigation
feat(protocol): add the computer driver SDK - #336
earakely-scale merged 19 commits into
Conversation
AgentEnvError(code, message, retryable, applied, hint, status). Raised in an extension it answers with its status and the fields in the error body; raised in a tool, on mcp's FastMCP and on fastmcp alike, it answers isError with "<code>: <message>" as text and the fields as structured content. Any other extension failure stays extension_failed / 500, now with applied "unknown". invoke_extension raises ExtensionCallError, an httpx.HTTPStatusError carrying the parsed fields, so callers that catch HTTPStatusError keep working. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@extension(uri, op=...) makes a handler one method of an extension: handlers sharing the URI merge into one card entry, each method under methods.<op> with its own verb and endpoint, by default /agentenv/ext/<extension>/<op>. The environment's extension_params(uri) adds extension-level params beside the methods, and declared_extensions() advertises declaration-only extensions. Two handlers for one method, a handler without op on a URI another uses, and methods disagreeing on description or required are refused at mount. Without op, routes and cards are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d_tools @tool takes annotations (MCP ToolAnnotations fields), structured_output and meta, passed through to mcp's FastMCP and to fastmcp, with the hidden-by-default marker merged into meta. A tool returning Annotated[CallToolResult, Model] is sent as is on either FastMCP and listed with Model's schema. served_tools(tools) lets an environment choose at mount which @tool methods it serves, and with which descriptions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An environment with tool_access = True filters tools/list and refuses tools/call per caller role (AgentEnv-Role, or a gateway-stamped _meta role) on both FastMCPs, and serves disable-tool/v1 and enable-tool/v1 at the gateway's paths and bodies, so modify_env_tool_access works on an env with no gateway in front. agentenv_protocol.tool_access keeps the gateway's rule semantics, hidden-by-default tools included; a refused call answers permission_denied. The gateway keeps its own code, since its image installs no protocol, and tst/unit/env/gateway/tool_access_parity_test.py runs the same rule cases against both. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lass agentenv_protocol.computer: a driver subclasses ComputerDriverEnvironment and marks the methods that capture and send input (@display, @capture, @click, @drAg, @scroll, @type_text, @press; the pointer and keyboard groups; @set_display; @run_in_session and @write_files, with Linux defaults). The class is checked when defined: every required operation, whole groups, one method per operation per platform, and features that match the decorators. Predicates (os, distribution, version, arch, display) choose a method per platform. The base class serves the contract from those operations: - the seven computer_* tools and the pointer and keyboard group tools, with structured results, a one-line summary as text, output schemas and the errors table's codes and applied; - frames: f<n> ids, native region crops that map back exactly, the last 64 kept, stale when the display geometry changes, each caller's latest; - frame and norm coordinate spaces, refused rather than clamped outside the frame or range, with the default written into the tool descriptions; - the input queue (one call at a time, input_busy past the wait) and others_since_frame; - computer-ready (real checks; tools refuse device_unavailable until they pass once), computer-display, computer-spaces, computer-keyboard, computer-exec (idempotent by request_id, output capped, timed out, a background app's open pipes drained rather than waited on); - data/add into the session user's home from inline bytes, https URLs and read grants, written atomically with path checks; data/get of the data paths, inline or under a write_namespace grant; - the generated card, and per-role tool access. Settings come from AGENTENV_COMPUTER_SETTINGS. The new computer extra installs mcp, uvicorn and Pillow. Tests drive the whole base class through an in-memory screen in tests/computer_fake.py, which doesn't ship. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… computer drivers Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
eb167be to
4318955
Compare
|
No dependency changes detected. Learn more about Socket for GitHub. 👍 No dependency changes detected in pull request |
…'s way Found end to end: an X11 driver's own `_button` helper replaced the base class's `_button`, so every click failed with a TypeError. The base class's private members are now all `_computer_*`, and a driver that redefines one of them, another private member of the base class, or a computer_* tool is refused when it is defined. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eing From review: - a caller cancelled while waiting for the input queue no longer leaves its lock acquisition behind to take the queue and never give it back; - a screenshot that fails after an input was delivered reports the input as done (applied yes, no frame) rather than an error that invites a retry, and held keys are recorded as soon as they are pressed; - the automatic key release keeps waiting while other input keeps the queue busy; - a point is checked against its frame again once the call has its turn, and a display change takes a turn too, so a queued click can't land on the old geometry; - a running command is joined by its request_id however many others finish meanwhile; only finished results are evicted; - structured_output=False lists no schema for a CallToolResult tool on fastmcp. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Found end to end: 64 raw 1280×800 frames held about 230 MB in the driver, and the bench's 1920×1080 would hold over 400 MB. A frame now keeps the PNG it is encoded to once at capture, the one every screenshot answers with anyway, and decodes it only to crop or to answer in JPEG. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
End-to-end and chaos results at 1c71b35The only later commit, 659293f, realigns lines in tools.py. The SDK was exercised as a driver author would use it. An X11 driver built on Contract on a real X server (EC2 c6i.4xlarge, agent-environments-admin): 58/58
Through agent-env and the existing gateway (
A real agent (
Chaos (EC2): 29/29
Fixed on this branch because of these runs
For M0.3, not this PR
|
…ne methods, and update a served tool served_extensions(extensions) and served_operations(operations) choose at mount which @extension handlers, extension methods and data-plane methods are served, beside served_tools; a hook naming something with no handler is refused. update_tool(name, description=...) changes a served tool's description after the mount, on the card and in tools/list, on mcp's FastMCP and on fastmcp. The card route now serves the application's current card. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…models and the card hooks ComputerDriverEnvironment keeps its state in one ComputerRuntime (self._computer: operations, frames, the input queue, readiness, held keys and command runs) instead of _computer_* attributes, and the mixins declare what they read from the driver. The card's choices move to card.py and go through served_tools, served_extensions and served_operations, so a missing operation's handlers are no longer withdrawn by setting them to None. The definition-time checks move to checks.py. Every tool result is built from its pydantic model, the same one that gives its output schema, so unset fields are left out (parent_frame_id on a full screenshot, effect when there is none). Setting the default space rewrites the point tools' descriptions through update_tool rather than mcp's tool manager, and x and y stay optional on click and scroll everywhere: a computer without computer-pointer/v1 refuses a call that leaves them out. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…default space changes computer-spaces/v1 set chose the tools to update from the driver's operations, so a driver whose served_tools left out a point tool answered 500, after the new space was already set. It now reads the tools from mounted_card(), the card the mounted application serves, which the SDK adds beside update_tool. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…un it examples/computer_x11 is the driver the end-to-end runs used: mss for capture and XTEST through python-xlib for input, the pointer and keyboard groups, an XTEST ready check, and a reconnect when the X connection drops. Its X calls run off the event loop. The image runs it on Xvfb beside a Tk practice app that records what reaches it, built from this checkout's SDK. test_computer_x11_example runs the driver against fake mss and Xlib modules, so it keeps up with the SDK without an X server. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…the keys it is given - Input is no longer resent when the X connection drops during it, which could click or type twice; the connection is checked with a round trip before input is sent, so a dead X server still answers device_unavailable, and a drop mid-input answers delivery_failed (applied unknown). - A drag that fails or is cancelled lets go of its button and modifiers. - A character on a key's second level, such as A or !, is pressed with Shift, in computer_key and computer_keyboard_down as in typing. - Only keys an action pressed are released, so a key held by computer_keyboard_down stays down through typing, clicks, scrolls and chords; computer_keyboard_up releases what each key's press pressed. - A character the keymap lacks borrows a spare keycode, kept for that character and reused, or the least recently used one once all are taken, instead of using up a keycode per character; text that can't be typed at all is refused before any of it is sent. - The click gap and drag spacing are named constants. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
uv.lock is main's, locked again with the protocol's computer extra. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s the turn, and release timed holds - A click or scroll at the pointer reads the pointer once its input turn comes, not before it waits, so one queued behind a drag acts where the drag left the pointer. - A screenshot takes the input count before it captures, so input that lands while it runs counts as after the frame in others_since_frame. - A timed computer_keyboard_down that is cancelled during its hold still releases its keys. - The X11 example never maps a keycode a key holds down to another character, and finds every keycode a chord needs before pressing any, so a chord it can't hold whole is refused with nothing sent. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…re pressing any in the X11 example keys_down held its keys one at a time, so keys it couldn't hold all at once were refused after the first were already down. It now plans them together through hold_each, which still returns the keycodes each key pressed for computer_keyboard_up. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds the computer driver SDK to
agentenv-protocol: a driver is aComputerDriverEnvironmentsubclass that implements screen operations (capture, click, type, …), and the base class serves the computer contract from them. Linear: FD-4380 (M0.0, the SDK prerequisites) and FD-4348 (M0.2, the base class) of the CUA Env epic. Nothing undersrc/agent_env/changes; core gains one test.SDK changes
AgentEnvError(code, message, retryable, applied, hint, status). Raised in an extension it answers with its status and those fields in the error body; raised in a tool (mcp's FastMCP and fastmcp) it answersisErrorwith"<code>: <message>"as text and the fields as structured content. Other extension failures stayextension_failed/ 500, nowapplied: "unknown".invoke_extensionraisesExtensionCallError, anhttpx.HTTPStatusErrorcarrying the parsed fields, so callers that catchHTTPStatusError(DeployedEnv.invoke) keep working.@extension(uri, op=...): handlers sharing a URI merge into one card entry, each method with its own verb and endpoint (/agentenv/ext/<extension>/<op>by default).extension_params(uri)adds extension-level params;declared_extensions()advertises declaration-only ones. Withoutop, routes and cards are unchanged.@toolmetadata.annotations,structured_outputandmetaon both FastMCPs; a tool returningAnnotated[CallToolResult, Model]is sent as is and listed withModel's schema.served_tools(tools),served_extensions(extensions)andserved_operations(operations)choose at mount which tools, extensions (and methods of one) and data-plane methods an env serves; returning something with no handler is refused.update_tool(name, description=...)changes a served tool's description after the mount, on the card and intools/list, on both FastMCPs;mounted_card()is the card the mounted application serves, and the card route serves it as it stands.tool_access = Truefilterstools/listand refusestools/callper caller role, and servesdisable-tool/v1/enable-tool/v1at the gateway's paths and bodies, somodify_env_tool_accessworks on an env with no gateway. The gateway keeps its own code (its image installs no protocol);tst/unit/env/gateway/tool_access_parity_test.pyruns the same rule cases, 5,000 random step sequences included, against both.agentenv_protocol.computer@display,@capture,@click,@drag,@scroll,@type_text,@press; the pointer and keyboard groups;@set_display;@run_in_sessionand@write_fileswith Linux defaults), checked when the class is defined, chosen per platform by predicates (os,distribution,version,arch,display).computer_*tools and the group tools, with structured results, output schemas, a one-line summary as text, and the errors table's codes andapplied.f<n>, native crops that map back exactly, last 64 kept, stale on a display change, each caller's latest), coordinate spaces (frame and norm, refused rather than clamped, the default written into tool descriptions), the input queue (one call at a time,input_busy,others_since_frame).computer-ready(real checks; tools refusedevice_unavailableuntil they pass once),computer-display,computer-spaces,computer-keyboard,computer-exec(idempotent byrequest_id, output capped, timed out; a background app's open pipes are drained rather than waited on).data/addinto the session user's home from inline bytes, https URLs and read grants, atomic with path checks;data/getof the settings'data_paths, inline or under awrite_namespacegrant.driver.py) holds the features and hooks. Its state lives in oneComputerRuntime(self._computer: operations, frames, queue, readiness, held keys, command runs).card.pydecides what is served, through the hooks above.results.pyhas each tool's result model, which gives both its output schema and its result.checks.pyhas the definition-time checks. The mixins (tools.py,extensions.py,dataplane.py) declare what they read from the driver.xandyare optional on click and scroll in every schema, with a description saying when they are needed: a computer withoutcomputer-pointer/v1refuses a call that leaves them out (invalid_argument).packages/agentenv-protocol/examples/computer_x11/: the X11 driver the end-to-end runs used (mss capture, XTEST input through python-xlib, both groups, an XTEST ready check, reconnect on a dropped X connection), an image that runs it on Xvfb beside a Tk practice app, and a README.test_computer_x11_example.pyruns it against fakemssandXlibmodules. M0.3's reference driver andcomputer_driverenv type come later.AGENTENV_COMPUTER_SETTINGS. A newcomputerextra installsmcp,uvicornandPillow(uv.lockadds Pillow, from PyPI).agentenv_protocol.computeris not imported byimport agentenv_protocol.Design choices, as built
The 13 choices in the design doc stand. Where the build adds to or departs from them:
/agentenv/ext/<extension>/<op>withop, today's otherwise.ExtensionCallErrorsubclasseshttpx.HTTPStatusError.defaultand*; no role meansdefault.f<n>, last 64, latest per caller identity, which isdefaultfor every caller until FD-4386;caller_identity(context)is the seam.computer_pointer_list(the default pointer only, no leasing).max_key_repeat,max_scroll_amountandlayouts, which the tools needed to boundrepeat,amountand the keyboard layout.ComputerSettings.AGENTENV_COMPUTER_SETTINGS.computerextra ismcp,uvicorn,Pillow;mssandpython-xlibgo to the reference driver (M0.3).data/add: inline bytes, https URLs and read grants (ReadObjectunder thedata-objects/v1marker'sread); nodata/reset.Left out, since nothing uses them yet:
@list_windows(no default in this PR), thepointerandwindowtool parameters (leasing and windows come later), and a shipped fake screen (it lives inpackages/agentenv-protocol/tests/computer_fake.py).agentenv_protocol.computeris a normal public API with no experimental marker; a break after merge gets a!.plugin-apireads onlyagent_env, so it doesn't cover the protocol.Testing
make unit-test(the protocol's tests and core's unit tier) andmake lintpass; the protocol's tests also pass under Python 3.10.pytest-cov, run for this PR (not added to the dev extra): every module undercomputer/100%,errors.pyandtool_access.py100%. Every changed line inagent_env_environment.pyandclient.pyis covered except the base_describe_registered_toolstub, which mirrors_register_tool's: an app without MCP tools refusesupdate_toolbefore it gets there.tst/unit/env/gateway/process_scope_socket_test.py::test_an_armed_time_trigger_keeps_firing_after_a_client_leavesfailed once under full parallel load and passes alone; it waits 0.3 s for a virtual-time mark and predates this branch.computer_keywithAand!typesA!; a Shift held bycomputer_keyboard_downstays down through typing and a Shift-scroll (ABc); 59 distinct CJK characters, more than Xvfb's 19 spare keycodes, plus 10 repeats come out exact.computer_*calls).Docs
The protocol README gains the SDK changes and a "Computer drivers" section. The website should get a "Computer drivers" page covering the same: what a driver is, the operations table, what the base class serves, errors and settings.
🤖 Generated with Claude Code


Confidence Score: 5/5
The latest changes appear safe to merge.Summary
Adds the computer driver SDK, structured errors, serving hooks, and tool-access support.
Reviews (12) · Last reviewed commit: "fix(protocol): find every keycode a comp..." · Reviewed by Greptile