-
Notifications
You must be signed in to change notification settings - Fork 347
feat(workers): named remote CUA workers for macOS and Windows desktops #533
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
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,185 @@ | ||
| # 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
+30
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. 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- applicable repository knowledge files ---'
find /tmp/coderabbit-repo-knowledge/milind-soni-openmausbot-87e2adbc -type f -name '*.md' -print
printf '%s\n' '--- target document ---'
cat -n docs/byo-macos.md | sed -n '1,90p'Repository: milind-soni/OpenMausBot Length of output: 4806 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- repository learnings ---'
cat /tmp/coderabbit-repo-knowledge/milind-soni-openmausbot-87e2adbc/learnings/repo-wide.md
printf '%s\n' '--- source-specific learnings ---'
cat /tmp/coderabbit-repo-knowledge/milind-soni-openmausbot-87e2adbc/learnings/src.mdRepository: milind-soni/OpenMausBot Length of output: 1288 🌐 Web query:
💡 Result: To achieve reproducibility when working with Tart CLI and OCI images such as ghcr.io/cirruslabs/macos-sequoia-base, you should reference specific image digests rather than mutable tags like latest [1][2][3]. Using tags like latest can lead to unexpected behavior because the underlying image content may change over time [4]. By using the immutable OCI digest (e.g., ghcr.io/cirruslabs/macos-sequoia-base@sha256:), you ensure that your environment remains consistent across different runs and CI/CD pipelines [4]. To determine the exact digest of an image you have locally, you can use the tart fqn command, which returns the fully qualified OCI name, including the digest [5]. Best practices for reproducibility with Tart include: 1. Avoid 'latest': Replace:latest with a specific version tag if available, or preferably, the image's immutable SHA256 digest [4]. 2. Identify Local Images: Use tart fqn to inspect the OCI reference for images already present in your local cache [5]. 3. Push with Versioning: When pushing your own customized images to a registry, use explicit version tags or digests rather than relying on tags that are frequently overwritten [6]. 4. Metadata: Tart supports OCI annotations (such as org.cirruslabs.tart.upload-time) in the manifest, which can help track when an image was pushed [7]. Additionally, you can now use --labels when pushing images to add custom metadata, which aids in traceability and searching within your OCI registry [8][9]. While Tart uses a local VM format on disk to leverage APFS copy-on-write for high-performance cloning, it integrates seamlessly with OCI registries for distribution [10]. Following these steps ensures that the virtual machine environment deployed in your infrastructure is byte-for-byte identical to the one you tested locally [4]. Citations:
Pin the guest image reference.
🧰 Tools🪛 LanguageTool[uncategorized] ~31-~31: Did you mean the proper noun “Apple Silicon”? (APPLE_PRODUCTS) 🤖 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, copy only | ||
| its `package.json` and `dist/` into a private directory owned by the worker | ||
| account, and put its `openmausbot-worker-companion` bin on that account's | ||
| `PATH`. | ||
|
|
||
| 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 | ||
|
Comment on lines
+109
to
+110
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. 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win 🔎 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' '--- documentation scope ---'
sed -n '1,220p' docs/byo-macos.md
printf '%s\n' '--- relevant directory creation and installer references ---'
rg -n -C 3 'OpenMausBot|macos-policy|macos-base-policy|mkdir|installer' --glob '!docs/byo-macos.md' .Repository: milind-soni/OpenMausBot Length of output: 50380 🏁 Script executed: log=/tmp/coderabbit-shell-logs/shell-output-phbAYD
printf '%s\n' '--- convention file list ---'
sed -n '1,80p' "$log"
printf '%s\n' '--- guide lines 90-145 ---'
sed -n '90,145p' docs/byo-macos.md
printf '%s\n' '--- exact policy-path references ---'
rg -n -C 4 'Library/Application Support/OpenMausBot|macos-policy|parked-capability' --glob '!electron/vendor/**' --glob '!**/*.lock' .Repository: milind-soni/OpenMausBot Length of output: 8454 🏁 Script executed: printf '%s\n' '--- applicable repository guidance ---'
cat /tmp/coderabbit-repo-knowledge/milind-soni-openmausbot-87e2adbc/learnings/repo-wide.md
printf '%s\n' '--- applicable learnings ---'
cat /tmp/coderabbit-repo-knowledge/milind-soni-openmausbot-87e2adbc/learnings/src.md
printf '%s\n' '--- installer and worker files ---'
git ls-files | rg -i '(^|/)(install|installer|setup|worker|mac|companion)|package\.json$'
printf '%s\n' '--- all support-directory operations, excluding generated/vendor content ---'
rg -n -C 3 'MAC_SUPPORT_RELATIVE|Library/Application Support/OpenMausBot|active-capabilities.yaml|macos-policy.yaml|mkdir[^\\n]*support|mkdir[^\\n]*OpenMausBot' \
--glob '!electron/vendor/**' --glob '!**/*.lock' .Repository: milind-soni/OpenMausBot Length of output: 9702 Create the macOS policy directory before copying files. The guide copies files to 🤖 Prompt for AI Agents |
||
| 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. | ||
|
|
||
| ## 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. | ||
|
Comment on lines
+121
to
+140
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. 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift 🔎 Supported by static analysis🏁 Script executed: #!/bin/bash
set -e
printf '%s\n' '--- repository conventions ---'
for f in /tmp/coderabbit-repo-knowledge/milind-soni-openmausbot-87e2adbc/*/*.md; do
[ -f "$f" ] && { echo "### $f"; head -5 "$f"; }
done
printf '%s\n' '--- document context ---'
cat -n docs/byo-macos.md | sed -n '1,180p'
printf '%s\n' '--- remote worker symbols and context ---'
ast-grep outline server/remote-worker.ts --match '$_' --view compact 2>/dev/null || true
rg -n -C 12 'policy|capability|ready|digest|daemon|CUA' server/remote-worker.ts
printf '%s\n' '--- macOS launch/configuration references ---'
rg -n -C 3 'launchd|LaunchAgent|LaunchDaemon|active-capabilities|policy|CUA|autostart|installer' docs server README.md .github 2>/dev/null || trueRepository: milind-soni/OpenMausBot Length of output: 50382 🏁 Script executed: #!/bin/bash
set -e
printf '%s\n' '--- relevant tracked files ---'
git ls-files | rg '(^|/)(mac|cua|worker|remote).*|byo-macos|policy|capabilit' | head -120
printf '%s\n' '--- exact policy/capability configuration references ---'
rg -n -C 5 --glob '!docs/superpowers/**' --glob '!*.lock' \
'CUA_[A-Z0-9_]*(POLICY|CAPABILITY)|POLICY_[A-Z0-9_]*|CAPABILITY_[A-Z0-9_]*|macos-policy\.yaml|active-capabilities\.yaml|policyLoaded|capabilityLoaded' \
. 2>/dev/null | head -240
printf '%s\n' '--- macOS worker adapter ---'
fd -i 'mac-worker' . --type f -x sh -c 'echo "### $1"; cat -n "$1"' sh {}
printf '%s\n' '--- companion/daemon launch references in source and documentation ---'
rg -n -C 6 --glob '!docs/superpowers/**' \
'daemon|launchd|LaunchAgent|LaunchDaemon|autostart|restart|cua-driver.*(start|daemon)|worker-companion' \
docs server src scripts package.json 2>/dev/null | head -260Repository: milind-soni/OpenMausBot Length of output: 48004 🏁 Script executed: #!/bin/bash
set -e
printf '%s\n' '--- Windows setup contract for comparison ---'
cat -n docs/byo-windows.md | sed -n '88,132p'
printf '%s\n' '--- macOS setup remainder and troubleshooting ---'
cat -n docs/byo-macos.md | sed -n '169,215p'
printf '%s\n' '--- tracked companion/build metadata ---'
git ls-files worker-companion package.json pnpm-workspace.yaml third_party/cua-driver/README.md | xargs -r -n1 sh -c 'echo "### $0"; rg -n -C 4 "autostart|policy|capability|daemon|macOS|launch" "$0" | head -120'
printf '%s\n' '--- source path that invokes the macOS probe ---'
cat -n server/mac-worker.ts | sed -n '86,178p'Repository: milind-soni/OpenMausBot Length of output: 9881 Document the macOS daemon launch configuration. The guide copies the policy and capability files but does not state how to configure or start the macOS CUA daemon. 🤖 Prompt for AI Agents |
||
|
|
||
| ## 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 | | ||
|
Comment on lines
+169
to
+184
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 | 🟡 Minor | ⚡ Quick win Add the capability readiness errors to the table.
🤖 Prompt for AI Agents |
||
| | `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.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
🔎 Supported by static analysis
🏁 Script executed:
Repository: milind-soni/OpenMausBot
Length of output: 2527
🏁 Script executed:
Repository: milind-soni/OpenMausBot
Length of output: 1287
🌐 Web query:
Apple macOS Sequoia Software License Agreement two additional virtual instances service bureau time-sharing💡 Result:
The Apple macOS Sequoia Software License Agreement (SLA) permits the installation and use of up to two (2) additional copies or instances of the macOS software within virtual operating system environments on each Apple-branded computer you own or control that is already running the Apple software [1]. These virtualized instances are restricted to the following purposes: (a) software development; (b) testing during software development; (c) using macOS Server; or (d) personal, non-commercial use [1]. Crucially, the macOS Sequoia license expressly prohibits using these virtualized copies or instances in connection with service bureau, time-sharing, terminal sharing, or other similar types of services, except as explicitly permitted under specific leasing arrangements for "Permitted Developer Services" (typically detailed in Section 3 of the license) [1][2][3]. This means standard virtualization of macOS on non-Apple hardware, or using macOS VMs to provide hosted desktop or time-sharing services to third parties, is generally not permitted under the standard EULA [1][3][4]. In summary: - You may run up to two virtual instances of macOS Sequoia on an Apple-branded host [1]. - You cannot use these instances for service bureau or time-sharing services, unless they qualify under the limited "Permitted Developer Services" leasing provisions [1][3]. - The license requires the host system to be an Apple-branded computer [1][5]. Use on non-Apple hardware violates the license agreement [6][4].
Citations:
Qualify the macOS VM licensing statement.
The macOS Sequoia SLA allows up to two additional virtual instances on an Apple-branded computer that you own or control and that already runs macOS. The instances must be used for software development, development testing, macOS Server, or personal non-commercial use. The SLA restricts service-bureau and time-sharing use. Add these conditions and direct commercial or remote-service users to verify their license.
🧰 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