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 releaseharn 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.
Drivers
Section titled “Drivers”| 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.
Enabling it
Section titled “Enabling it”The command is inert until a project opts in with a mode:
{ "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.
Each person’s own mode
Section titled “Each person’s own mode”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.
Approval
Section titled “Approval”In an ask mode, call and lease acquire refuse with exit 4 until the agent
holds an approval:
harn computer grant --purpose "export the October invoices from the accounting app"harn computer call ...harn computer donegrant 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.
Platforms
Section titled “Platforms”| 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 |
computer status
Section titled “computer status”harn computer statusShows 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.
computer install
Section titled “computer install”harn computer install # preview both driversharn computer install --yes # install bothharn computer install --driver agent-desktop --yes # install oneWithout --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.
computer up / down
Section titled “computer up / down”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.
computer doctor
Section titled “computer doctor”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 }] }.
computer tools / describe
Section titled “computer tools / describe”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.
computer call
Section titled “computer call”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:
-
Sets
sessionto 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 labelledshell-<user>. -
Takes the desktop lease for the caller, or refreshes it. When another agent holds it, the call waits up to
--waitseconds (defaultwait_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`. -
Starts the driver’s daemon if it has one and it is not running.
-
Translates file paths in the arguments and results between WSL and Windows syntax, so
screenshot_out_fileoroutput_pathcan name a WSL path. -
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-screenshotto 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.
computer lease
Section titled “computer lease”| 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.
Exit codes
Section titled “Exit codes”| 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 |