-
Notifications
You must be signed in to change notification settings - Fork 368
feat(workers): the cross-platform worker companion #534
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
0b32398
e5d0ce1
7997b9c
f2b94d9
53690c3
6b5587e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| # The worker capability and policy manifests are hashed, not merely read. | ||
| # | ||
| # worker-companion embeds the parked manifest byte-for-byte and requires the CUA | ||
| # daemon to report back that exact sha256 before a worker is considered bounded, | ||
| # and docs/byo-*.md has the operator pin the base policy by digest. A CRLF | ||
| # checkout on Windows silently changes both digests, so a Windows operator | ||
| # following the runbook would compute a hash that never matches the one the | ||
| # control plane expects — with nothing visibly wrong in either file. | ||
| # | ||
| # These must therefore arrive with LF on every platform, regardless of the | ||
| # checking-out machine's core.autocrlf. | ||
| docs/*-parked-capabilities.yaml text eol=lf | ||
| docs/*-base-policy.yaml text eol=lf |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,187 @@ | ||
| # Bring your own macOS worker | ||
|
|
||
| OpenMausBot keeps its control plane on one Mac and connects a bot to a macOS | ||
| machine you already run — a guest VM on the same Apple silicon Mac, or a | ||
| second physical Mac. It does not create the guest, manage a hypervisor, store | ||
| SSH credentials, open a TCP listener, mount the control plane's workspace, or | ||
| fall back to another computer when the worker fails. | ||
|
|
||
| A macOS worker pairs with [a Windows worker](byo-windows.md) rather than | ||
| replacing it: workers are named independently and lease independently, so one | ||
| bot can hold a macOS desktop while another holds a Windows desktop. | ||
|
|
||
| ## Why a guest and not this Mac | ||
|
|
||
| The `local` computer beta drives the Mac OpenMausBot is running on. That Mac | ||
| is also yours — the bot shares your screen, your keyboard and your files. A | ||
| guest gives the bot its own login session, its own home directory and its own | ||
| Accessibility grants, and it can be rebuilt from scratch when something goes | ||
| wrong. | ||
|
|
||
| Apple's software licence allows up to two macOS guests on one Apple silicon | ||
| host, so a single worker guest leaves headroom. | ||
|
|
||
| ## Before you start | ||
|
|
||
| - **Apple silicon.** macOS guests use Virtualization.framework; an Intel Mac | ||
| cannot host one. | ||
| - **Disk.** Budget 80–100 GB: a restore image is roughly 16 GB (deletable | ||
| after install) plus the guest's own disk. | ||
| - **A hypervisor.** [`tart`](https://tart.run) is the easiest to keep | ||
| reproducible — it is CLI-driven, pulls prebuilt Apple silicon images, and | ||
| `tart ip` gives you an address to put in your SSH config. UTM works too if | ||
| you would rather click through the install. | ||
|
|
||
| ## Create the guest | ||
|
|
||
| ```bash | ||
| brew install cirruslabs/cli/tart | ||
| tart clone ghcr.io/cirruslabs/macos-sequoia-base:latest omb-worker | ||
| tart set omb-worker --cpu 4 --memory 8192 --disk-size 80 | ||
|
Comment on lines
+39
to
+40
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: # Inspect the affected guide and the repository review conventions that cover documentation.
printf '%s\n' '--- docs/byo-macos.md ---'
cat -n docs/byo-macos.md | sed -n '1,70p'
printf '%s\n' '--- available convention scopes ---'
find /tmp/coderabbit-repo-knowledge/milind-soni-openmausbot-87e2adbc -maxdepth 2 -type f -name '*.md' -print
printf '%s\n' '--- convention headers ---'
for f in /tmp/coderabbit-repo-knowledge/milind-soni-openmausbot-87e2adbc/*/*.md; do
printf '\n### %s\n' "$f"
head -5 "$f"
doneRepository: milind-soni/OpenMausBot Length of output: 4256 Security Misconfiguration (CWE-494): Download of Code Without Integrity Check Reachability: External · Exploitability: Difficult Pin the guest image used by the setup guide.
🤖 Prompt for AI AgentsSource: MCP tools |
||
| tart run omb-worker | ||
| ``` | ||
|
|
||
| Then, inside the guest: | ||
|
|
||
| 1. Create a **dedicated standard (non-administrator) account** for the worker. | ||
| Readiness refuses an account in the `admin` group: an administrator could | ||
| rewrite the very base policy that bounds it, so installing the tools as an | ||
| admin does not make that account an eligible worker. | ||
| 2. Log in as the worker account and turn on **Users & Groups → automatic | ||
| login** for it. An Aqua session must exist at all times; readiness checks | ||
| that the worker account owns `/dev/console`. | ||
| 3. Turn **off** screen lock and sleep (Lock Screen → *Require password … | ||
| Never*, *Turn display off … Never*). A locked screen reads as not ready. | ||
| 4. Turn on **General → Sharing → Remote Login** for that account only. | ||
|
|
||
| On the control-plane Mac, add the guest to your SSH config with key-only | ||
| authentication and confirm it works before going further: | ||
|
|
||
| ```bash | ||
| ssh omb-worker true | ||
| ``` | ||
|
|
||
| OpenMausBot stores only that alias. | ||
|
|
||
| ## Install the tools | ||
|
|
||
| Inside the guest, as the worker account: | ||
|
|
||
| ```bash | ||
| cua-driver --version # must print exactly 0.20.0 | ||
| node --version # 24 or newer | ||
| openmausbot-worker-companion --version | ||
| ``` | ||
|
|
||
| Install the pinned CUA Driver release with the official instructions — do not | ||
| use an unreviewed wrapper or an ambient alternate binary. Build the companion | ||
| from the exact OpenMausBot source commit on the control-plane Mac with | ||
| `pnpm build:worker-companion`, copy only its `package.json` and `dist/` into a | ||
| private directory owned by the worker account, install its dependencies there | ||
| (`npm install --omit=dev`), and put its `openmausbot-worker-companion` bin on | ||
| that account's `PATH`. The dependency is the pinned CUA SDK: the companion | ||
| reads the driver's own Accessibility and Screen Recording grants through it. | ||
|
|
||
| The driver listens on a unix socket at `~/.openmausbot/run/cua.sock`. Both the | ||
| socket and its directory must be owned by the worker account and private to | ||
| it; readiness refuses a socket it cannot read and write. | ||
|
|
||
| ## Grant Accessibility and Screen Recording | ||
|
|
||
| This is the one step nobody can script for you. macOS grants both permissions | ||
| **per binary**, System Integrity Protection prevents writing the permission | ||
| database, and replacing the driver binary silently revokes them. | ||
|
|
||
| In the guest, open **System Settings → Privacy & Security** and add the CUA | ||
| Driver binary under both **Accessibility** and **Screen Recording**. Then | ||
| confirm the driver itself sees them: | ||
|
|
||
| ```bash | ||
| openmausbot-worker-companion --permissions | ||
| ``` | ||
|
|
||
| It prints `{"accessibility":true,"screenRecording":true}` when both are live. | ||
|
|
||
| Readiness re-reads this on every poll rather than trusting that you did it | ||
| once, so a driver upgrade that drops the grants surfaces as | ||
| `worker_accessibility_denied` instead of as mysterious failures mid-task. | ||
|
|
||
| ## Pin the base policy | ||
|
|
||
| Copy [`macos-base-policy.yaml`](macos-base-policy.yaml) into the guest at | ||
| `~/Library/Application Support/OpenMausBot/macos-policy.yaml`, then record its | ||
| digest: | ||
|
|
||
| ```bash | ||
| shasum -a 256 ~/Library/Application\ Support/OpenMausBot/macos-policy.yaml | ||
| ``` | ||
|
|
||
| Enter that digest in OpenMausBot when you add the worker. Until you do, the | ||
| worker stays *unconfigured*: without a pinned digest the driver's tool ceiling | ||
| would be whatever happens to be on the guest's disk. | ||
|
|
||
| Note that a matching file is not sufficient on its own. CUA loads its policy | ||
| once at daemon start, and an unset policy variable disables enforcement | ||
| entirely, so readiness requires the daemon to *report* the same digest it | ||
| finds on disk. | ||
|
Comment on lines
+111
to
+126
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift 🔎 Supported by static analysis🏁 Script executed: printf '%s\n' '--- repository conventions ---'
find /tmp/coderabbit-repo-knowledge/milind-soni-openmausbot-87e2adbc -maxdepth 2 -type f -name '*.md' -print
printf '%s\n' '--- target document ---'
cat -n docs/byo-macos.md | sed -n '1,170p'
printf '%s\n' '--- relevant references ---'
rg -n -i 'CUA|DRIVER_POLICY|policy|digest|daemon|macos' docs README.md .github 2>/dev/null | head -250Repository: milind-soni/OpenMausBot Length of output: 34870 🏁 Script executed: printf '%s\n' '--- scoped repository guidance ---'
cat /tmp/coderabbit-repo-knowledge/milind-soni-openmausbot-87e2adbc/learnings/repo-wide.md
cat /tmp/coderabbit-repo-knowledge/milind-soni-openmausbot-87e2adbc/learnings/src.md
printf '%s\n' '--- analogous Windows setup ---'
cat -n docs/byo-windows.md | sed -n '90,180p'
printf '%s\n' '--- CUA launch/configuration references ---'
rg -n -i 'autostart|launch(agent|daemon)?|CUA_DRIVER_(POLICY|PERMISSION|CAPABILITY)|cua-driver (status|start|restart|install|mcp)|official CUA|trycua' --glob '!docs/plans/**' --glob '!README.md' .Repository: milind-soni/OpenMausBot Length of output: 50381 🏁 Script executed: printf '%s\n' '--- macOS worker readiness and launch path ---'
cat -n server/mac-worker.ts | sed -n '1,180p'
printf '%s\n' '--- worker companion driver setup ---'
cat -n worker-companion/src/driver.ts | sed -n '1,150p'
printf '%s\n' '--- macOS policy and capability file contracts ---'
cat -n docs/macos-base-policy.yaml
cat -n docs/macos-parked-capabilities.yamlRepository: milind-soni/OpenMausBot Length of output: 17248 🌐 Web query:
💡 Result: The Cua Driver is an open-source tool designed to enable background computer-use automation on macOS, Windows, and Linux [1][2]. Version 0.20.0, released in mid-August 2026, is a version of this driver [3][4]. Installation and Autostart Policy on macOS: The official installation for macOS is performed via the command: /bin/bash -c "$(curl -fsSL https://cua.ai/driver/install.sh)" [3][4] This installer places the CuaDriver.app bundle in /Applications and creates a symlink at ~/.local/bin/cua-driver [5][6]. Because the app bundle uses a consistent signing identity (com.trycua.driver), macOS Transparency, Consent, and Control (TCC) permissions for Accessibility and Screen Recording are maintained across upgrades [5][6]. Regarding autostart: The Cua Driver does not have a native autostart command for macOS [7][8]. Instead, you must manually create a launchd agent (a LaunchAgent) to ensure the daemon runs after reboots [8]. Official documentation recommends placing a plist file at ~/Library/LaunchAgents/com.trycua.cua-driver.plist [8]. Environment Variables and Configuration: Configuration options for the driver—such as permission modes, capability manifests, or policy files—are read only once when the daemon starts [9][8]. If you are configuring a custom autostart launchd plist, you must inject these configurations as EnvironmentVariables within the XML plist structure so they are active when the daemon starts automatically [10]. For example, to set a custom policy file path: EnvironmentVariables CUA_DRIVER_POLICY_FILE /path/to/your/policy.yaml [10] Similarly, if you need to pin specific permission modes (like bounded or unrestricted) or use other flags, these must be configured in the autostart entry itself rather than relying on system-wide environment variables, as the driver process fixes these modes at launch [9][8]. After modifying the launchd plist, you must reload the agent using launchctl: launchctl unload ~/Library/LaunchAgents/com.trycua.cua-driver.plist launchctl load ~/Library/LaunchAgents/com.trycua.cua-driver.plist [10] Citations:
🏁 Script executed: printf '%s\n' '--- shared readiness predicates ---'
rg -n -A80 -B10 'function evaluateSharedHealth|export function evaluateSharedHealth|worker_policy_missing|worker_permission_mode_mismatch|capabilityLoaded' server/remote-worker.ts server/mac-worker.ts docs/byo-macos.mdRepository: milind-soni/OpenMausBot Length of output: 34368 Document the macOS CUA daemon configuration. The guide copies the policy files but does not create or reload the required user 🤖 Prompt for AI Agents |
||
|
|
||
| ## Install the parked capability manifest | ||
|
|
||
| The base policy is the stable ceiling; a **capability manifest** is the | ||
| short-lived, per-task boundary that intersects it. Between tasks the guest | ||
| should hold the parked manifest, which grants no tools at all: | ||
|
|
||
| ```bash | ||
| cp macos-parked-capabilities.yaml \ | ||
| ~/Library/Application\ Support/OpenMausBot/active-capabilities.yaml | ||
| ``` | ||
|
|
||
| Readiness requires the daemon to report a loaded capability manifest, so a | ||
| guest without one never becomes ready. With the parked manifest in place the | ||
| worker is reachable and provably bounded, and can do nothing until a task | ||
| capability is approved — the correct resting state. | ||
|
|
||
| ## Add the worker | ||
|
|
||
| In OpenMausBot, open **Settings → Workers**, add a worker with: | ||
|
|
||
| - an id (lowercase, e.g. `mac-guest`) | ||
| - platform **macOS** | ||
| - the SSH alias | ||
| - the base-policy digest | ||
|
|
||
| Then assign a bot to it from that bot's Computer panel. Two workers may not | ||
| share one SSH alias — that would take two independent leases against a single | ||
| real desktop, and each would believe it held the screen exclusively. | ||
|
|
||
| ## What the bot can and cannot do | ||
|
|
||
| One bot leases a macOS worker at a time; a second turn aimed at the same | ||
| desktop waits rather than interleaving real mouse and keyboard input. Work on | ||
| another worker, and on Linux Local VMs, continues in parallel. | ||
|
|
||
| Auto mode is unavailable on a worker. Every task is bounded by three | ||
| independent fences — the stable base policy, a short-lived CUA capability | ||
| manifest, and the task manifest — so there is nothing for auto mode to | ||
| approve on its own. | ||
|
|
||
| At a sign-in, password, MFA or CAPTCHA step the bot stops and asks you to | ||
| complete it on the visible screen. | ||
|
|
||
| ## When it is not ready | ||
|
|
||
| Readiness reports the first thing that is actually wrong: | ||
|
|
||
| | Code | What to fix | | ||
| | --- | --- | | ||
| | `worker_offline` | SSH cannot reach the guest | | ||
| | `worker_driver_missing` / `worker_driver_wrong_version` | CUA Driver absent, off `PATH`, or not 0.20.0 | | ||
| | `worker_companion_missing` | the companion is not installed for the worker account | | ||
| | `worker_privileged_account` | the SSH account is in the `admin` group | | ||
| | `worker_no_interactive_session` | nobody is logged in at the guest's console | | ||
| | `worker_locked` | the guest's screen is locked | | ||
| | `worker_channel_missing` / `worker_channel_access_denied` | the driver socket is absent or not private to the worker account | | ||
| | `worker_policy_missing` / `worker_policy_mismatch` | the base policy is absent, unloaded, or not the pinned digest | | ||
| | `worker_permission_mode_mismatch` | CUA Driver is not running in bounded mode | | ||
| | `worker_accessibility_denied` / `worker_screen_recording_denied` | grant the permission to the driver binary in the guest | | ||
| | `worker_busy` | another turn holds this desktop | | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🔎 Supported by static analysis
🏁 Script executed:
Repository: milind-soni/OpenMausBot
Length of output: 9749
🏁 Script executed:
Repository: milind-soni/OpenMausBot
Length of output: 1292
🌐 Web query:
Apple macOS Sequoia Software License Agreement two additional instances Apple-branded Mac virtualization development testing personal non-commercial use💡 Result:
Under the Apple macOS Sequoia Software License Agreement (SLA), you are permitted to install, use, and run up to two (2) additional copies or instances of the Apple Software within virtual operating system environments on each Apple-branded computer you own or control that is already running the Apple Software [1][2]. These virtualized instances are restricted to the following specific purposes [1]: (a) Software development; (b) Testing during software development; (c) Using macOS Server; or (d) Personal, non-commercial use. The license expressly prohibits using these virtualized copies in connection with service bureaus, time-sharing, terminal sharing, relay services, or other similar types of services [1]. Additionally, you may not use the Apple Software to run any other Apple operating systems (such as iOS, iPadOS, watchOS, or tvOS) in virtual operating system environments on your Mac [1]. It is important to note that this allowance requires that the host hardware itself be an Apple-branded computer that is already running the Apple Software [1][3]. While some users have historically explored technical methods to bypass the concurrent VM limit, the official license terms remain limited to two virtualized instances per host [4][5][6]. In macOS Sequoia, Apple also introduced improvements to the virtualization framework that allow virtual machines (when running macOS 15 or later on a macOS 15 or later host) to derive a unique identity from the host's Secure Enclave, enabling the use of iCloud and other Apple ID-related services within the virtualized environment [7][8].
Citations:
Qualify the macOS virtualization license statement.
Apple permits up to two additional macOS instances only on an Apple-branded Mac already running macOS, and only for specified purposes such as development, testing, macOS Server, or personal non-commercial use. State these conditions and instruct operators to confirm that their use case complies with the SLA.
🧰 Tools
🪛 LanguageTool
[grammar] ~21-~21: Ensure spelling is correct
Context: ...something goes wrong. Apple's software licence allows up to two macOS guests on one Ap...
(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)
[uncategorized] ~21-~21: Did you mean the proper noun “Apple Silicon”?
Context: ...ce allows up to two macOS guests on one Apple silicon host, so a single worker guest leaves h...
(APPLE_PRODUCTS)
🤖 Prompt for AI Agents
Source: MCP tools