Skip to content

harn computer

harn computer status [--json]
harn computer install [--driver <id>] [--yes] [--json]
harn computer up [--driver <id>]
harn computer down [--driver <id>]
harn computer doctor [--driver <id>] [--json]
harn computer tools [--driver <id>]
harn computer describe <tool> [--driver <id>]
harn computer call <tool> [json] [--driver <id>] [--wait <seconds>] [--keep-screenshot]
harn computer lease status [--json]
harn computer lease acquire [--wait <seconds>]
harn computer lease release

harn computer gives every harness the same desktop control through two pinned, checksum-verified driver binaries, each called through its CLI.

Agents choose the route in three steps: what the task is, how to do it, and how the person receives the result. They work invisibly first (vendor APIs, CLIs, files, harn fetch, headless harn browse) and deliver in the chat: an answer, a link that reproduces the page’s state, or a picture of just the part that matters; a page opens in the person’s browser only when they ask. The desktop is for a desktop program with no file or API route, a site that refuses the headless browser even when signed in, or work the person will continue in a window no link can hand them. Using the screen, changing something in an outside system, and sending a message are three separate approvals. An agent app’s own browser, a pane built into the app or the app’s control of the person’s Chrome, comes last, after every other route failed, and is never used for a sign-in. Asking for a result is not asking for a new tool: agents ask before writing an integration the repository would keep. An automation-controlled headed browser window is kept for what only Playwright provides, such as scripted batches with assertions, HAR capture, or viewport emulation. The full routing is in the harn-computer skill. The design is in decision record 0228 and decision record 0231.

Id Driver Reads and acts through Daemon
cua Cua Driver 0.38.0 Tool calls with element tokens; accessibility, keyboard and pixel routes; window screenshots; an isolated browser through CDP Yes
agent-desktop agent-desktop 1.0.2 Accessibility-tree snapshots with refs scoped to each snapshot; headless semantic actions by default No; on macOS it starts its own command host, which status reports and down stops

Both drive the same desktop under the same lease. call, tools and describe use one driver: --driver <id>, else the project’s computer.driver, else cua. install, up, down and doctor act on every driver unless --driver names one.

The command is inert until a project opts in with a mode:

.harnery/config.jsonc
{
"computer": { "mode": "ask-each-task" }
}
Mode Meaning
off No desktop control (the default)
ask-each-task The person approves each stated purpose; the approval lasts up to grant_minutes (default 20)
ask-once-per-session One approval covers the rest of that agent’s session, up to 12 hours
on No approval needed

"enabled": true without a mode still means on. Only the project file turns desktop control on; a malformed or contradictory section counts as off and status says why. Where the project is not off, install, up, call, and lease acquire work, and harn init renders the harn-computer skill into each adapter’s skills directory. Optional tunables: grant_minutes, idle_seconds (default 120), and wait_seconds (default 30). See computer in the config schema and decision record 0230.

The project’s mode is the default. Where the project is not off, each person may choose their own with harn computer mode <mode> (this project) or --all-projects, and remove it with --clear. The choice is stored in computer.json beside the user config (~/.config/harnery/), never in the project, so committed files do not change. A personal mode never turns on a project that is off. Loosening it shows the person a Yes/No prompt first; tightening does not.

In an ask mode, call and lease acquire refuse with exit 4 until the agent holds an approval:

Terminal window
harn computer grant --purpose "export the October invoices from the accounting app"
harn computer call ...
harn computer done

grant asks the person directly: a Yes/No prompt in an interactive terminal, or otherwise a native dialog on the desktop (Windows, also from WSL; macOS; Linux desktops with zenity). The dialog names the agent, the project it is working in, and its purpose, says how long the approval lasts, and reminds the person to close anything the agent should not see, because whatever is on screen can reach the model provider. It defaults to the refusing button, and no answer within --timeout (default 120 seconds) counts as no. done ends a task approval and releases the desktop; lease release also ends it. revoke ends your approvals now, and revoke --all ends every agent’s on this desktop.

An approval belongs to one agent in one project. An agent that points HARNERY_COORD_ROOT_OVERRIDE at another project is still named by its own session: Harnery finds that session in the project that registered it, so the dialog, the approval and the lease show the agent’s name, and the approval is kept for the project the command acts on. Only a caller outside any agent session is named after the operating-system user (shell-<user>), and every such shell by that user shares one approval and one lease.

Approvals and every decision about them are kept beside the lease (grants.json and grants-log.ndjson). They are plain files any process running as the desktop’s user can write, so an approval is a cooperative check that stops a well-meaning agent acting unasked. It is not a security boundary against an agent that sets out to get around it.

Host Status
Windows 10/11 (native) Both drivers. State in %LOCALAPPDATA%\harnery
WSL2 on Windows 10/11 Both drivers’ Windows builds, run through interop to drive the Windows desktop; same state directory
macOS 13+ Both drivers. State in ~/Library/Application Support/harnery. Cua Driver runs from /Applications/CuaDriver.app. Each needs a one-time Accessibility (and Screen Recording) grant
Linux desktop Both drivers. State in $XDG_STATE_HOME/harnery (default ~/.local/state/harnery). Run inside the signed-in user’s graphical session with at-spi2-core
Terminal window
harn computer status

Shows whether the project enables computer use, the host kind, and for each driver its pinned version, whether it is the default, the installed executable (or not installed), and whether its daemon is running (or not needed). A driver that leaves background processes running after a call, such as agent-desktop’s command host on macOS, gets a helpers: line with their process ids. It ends with the current desktop lease holder, its generation, and its idle expiry. --json emits the same fields, with the drivers under drivers (background processes under background_processes) and the default under default_driver. Status never changes anything.

Terminal window
harn computer install # preview both drivers
harn computer install --yes # install both
harn computer install --driver agent-desktop --yes # install one

Without --yes it prints each plan only: URL, SHA-256, install directory, executable, any preference file, any further step, and any one-time setup the person must do. With --yes it downloads each pinned archive (.zip or .tar.gz), checks its SHA-256, extracts it to a versioned directory under the desktop’s shared state directory, and writes Cua Driver’s preference file (telemetry off) before that binary first runs. On macOS it then verifies CuaDriver.app with codesign --verify --deep --strict, checks its bundle identifier, and copies it to /Applications, replacing only an older Cua Driver bundle. It never edits PATH, registers autostart, or elevates. Re-running is idempotent. One driver failing does not stop the other; the command then exits 1. Upgrading a driver is a Harnery release that moves its pin.

up starts each installed driver’s daemon at the user’s normal integrity level if it is not already running. down stops them, together with any background processes a driver left running. agent-desktop has no daemon, so up skips it; on macOS down stops its command hosts, one per calling environment, which it otherwise keeps running after each call. While another agent holds the desktop, down leaves those processes alone, because a call may be using them, and exits 3. call starts a needed daemon on its own, so up is optional.

Runs each installed driver’s own health report and prints one line per check under the driver’s name. agent-desktop’s checks are its OS permission states, so a missing macOS Accessibility grant shows here, naming the app the grant belongs to: macOS charges agent-desktop’s requests to the app it runs inside (a terminal, an editor, an agent app), and each such app needs its own grant. Automation reads as information, since macOS asks for it per target app only when an action needs it. Until Cua Driver’s grants apply on macOS, its daemon answers everything with permissions_pending; doctor turns that, and a daemon that is not running, into a failing check that says what to do. A new grant can take about a minute to apply. Any check that does not pass, or a failing driver exit, exits 1. --json emits { ok, drivers: [{ driver, ok, exit_code, checks, report }] }.

tools lists one driver’s tools. describe <tool> prints one tool’s full description and arguments. Agents look up a schema on demand instead of carrying the whole catalog in every turn. For agent-desktop, describe adds the JSON keys the command accepts, and describe skill:desktop (or skill:platform) prints the binary’s own guide for its version.

Terminal window
harn computer call launch_app '{"name": "notepad"}'
harn computer call get_window_state '{"include_screenshot": false}'
harn computer call get_window_state '{"screenshot_out_file": "/tmp/state.png"}'
harn computer call --driver agent-desktop snapshot '{"app": "Notepad.exe", "interactive_only": true}'
harn computer call --driver agent-desktop set-value '{"ref_id": "@s8f3k2p9:e1", "value": "hi"}'

Runs one tool call and prints the driver’s JSON. For agent-desktop the tool is a command name and the JSON keys are that command’s snake_case field names; Harnery runs it as a one-entry batch and prints the command’s own envelope, exiting 1 when its ok is false. "headed": true moves to the global --headed flag. The tool batch with {"commands": [...]} runs several entries in one process. The arguments must be a JSON object (default {}). In order, the command:

  1. Sets session to the caller’s agent name when the arguments omit it, so the driver’s per-session state follows the agent. A shell outside any agent session is labelled shell-<user>.

  2. Takes the desktop lease for the caller, or refreshes it. When another agent holds it, the call waits up to --wait seconds (default wait_seconds, else 30) and then exits 3:

    Desktop is held by agent-Ada (generation 7, idle-expires in 84s). Wait, or ask them to run `harn computer lease release`.
  3. Starts the driver’s daemon if it has one and it is not running.

  4. Translates file paths in the arguments and results between WSL and Windows syntax, so screenshot_out_file or output_path can name a WSL path.

  5. Replaces the value of any key ending in png_b64, and any other string of 2,048 or more base64 characters, with a short <omitted N chars; …> note, so one state read cannot flood an agent’s context. Pass --keep-screenshot to keep the inline image, or write it to a file and read that file.

The driver’s stderr passes through and its exit code becomes the command’s exit code.

Command Effect
lease status [--json] Show the holder, its generation, and its idle expiry, or free
lease acquire [--wait <seconds>] Take or refresh the lease without calling a tool (default wait 0)
lease release Release the lease if you hold it and end your task approval; exits 3 when another agent holds it

The lease is one file per desktop, kept in the desktop’s own state directory, so callers in WSL distros, native Windows shells, and different repositories all see the same holder. An idle lease expires idle_seconds after the last call (default 120). While a call runs, the lease is held for at least that call’s timeout plus a margin, so a slow call never outlives it. agent-desktop calls time out after 30 seconds plus whatever the call asks to wait for (wait_timeout, timeout_ms, or the wait command’s ms); on macOS a timed-out call also restarts the command host, so one unresponsive app does not stall every later call. Each new holder increments the generation.

Code Meaning
0 Success
1 Error: computer use disabled, unsupported host, driver not installed, bad arguments, or a failing health check
3 The desktop lease is held by another agent
4 Desktop control needs the person’s approval first, or they declined or did not answer
other call, tools, and describe pass through the driver’s exit code