Figment.so
Mac hostingWebsite hostingGuidesHow to usePricing

Separate Project Workspaces on an Agent Mac

Running two automation projects as the same macOS user is how API keys from Project A end up in Project B's environment and a cleanup script wipes the wrong working directory. The fix is explicit separation: each project gets its own OS user, its own home directory tree, its own LaunchAgent namespace, and its own login Keychain. This page gives you the exact commands, a provisioning checklist, three failure checks, and the Figment plan that fits.

Which Figment Mac Plan Fits This Job

For two or three lightweight agents that run sequentially and build Intel-compatible targets, start on the $29/month Figment virtual Intel macOS environment (2 vCPU, 8 GiB RAM, 80 GiB disk). It runs real macOS and supports multiple user accounts; you reach it through the browser console terminal. Supported Intel Xcode (Xcode 26 or earlier) can cross-compile arm64 device apps and universal macOS binaries from this VM — see Apple's Porting Your macOS Apps to Apple Silicon for the current Intel toolchain boundary.

Move up to an M-series Mac from $95/month when you need any of: ARM-only execution, GPU work, Xcode 27, visionOS, or several concurrent agents each holding memory. Specific chip, RAM, and disk at that entry price are confirmed in the quote — ask before you commit. A team running multiple agents with CI runners and backups is better served by the $249/month Managed Build Mac (one M-series Mac, CI setup, backups, monitoring, 30 minutes monthly support) or the $999/month Managed Mac Team (two M-series Macs, two CI runners, team setup, backups, monitoring, one hour monthly support).

Inputs and Required Permissions

Document the following per project before you provision anything:

Per-project inputExample
Short user account nameagent-projecta
Home directory/Users/agent-projecta
Working directory/Users/agent-projecta/workdir
Secret names (not values)PROJECTA_API_KEY, PROJECTA_DB_URL
Automation binary path/Applications/ProjectA/agent
Required network destinationsapi.projecta.com:443

Keep secret names in your runbook. Load actual values into each user's own login Keychain, or inject them from your CI service's secret manager at job start. Never write secret values into provisioning scripts, LaunchAgent plists, or a shared .env file — the entire point of the separation is to avoid shared state that can leak between projects.

Implement the Workspace Separation

Separation has two layers: distinct OS users (the strong boundary) and mode-700 directory permissions (a lighter boundary when two jobs must share one user).

Layer 1 — One OS user per project

Run sysadminctl -h on your Mac to confirm the flag list for your macOS version, then create each user from an admin session in the browser console terminal:

# Project A agent user
sudo sysadminctl -addUser agent-projecta \
    -fullName "Project A Agent" \
    -password - \
    -home /Users/agent-projecta \
    -shell /bin/bash

sudo createhomedir -c -u agent-projecta
sudo chmod 700 /Users/agent-projecta

# Project B agent user
sudo sysadminctl -addUser agent-projectb \
    -fullName "Project B Agent" \
    -password - \
    -home /Users/agent-projectb \
    -shell /bin/bash

sudo createhomedir -c -u agent-projectb
sudo chmod 700 /Users/agent-projectb

The -password - form prompts interactively so no password is left in shell history. With chmod 700, only the owning user can list or read their home directory. Each user then has its own login Keychain under ~/Library/Keychains/, its own ~/Library/LaunchAgents/, its own environment scope, and its own crontab/at namespaces.

Layer 2 — Mode-700 directories when sharing a user

If one CI user must process repositories from different projects, keep each repository checkout in its own mode-700 directory and never cross-reference paths:

mkdir -p ~/workspaces/projecta ~/workspaces/projectb
chmod 700 ~/workspaces/projecta ~/workspaces/projectb
ls -la ~/workspaces/
# drwx------  ... projecta
# drwx------  ... projectb

This blocks accidental reads but does not stop a compromised job from reading sibling directories under the same uid. Prefer Layer 1 whenever you can.

Per-user LaunchAgents

Apple's Creating Launchd Jobs documents that agents in a user's ~/Library/LaunchAgents/ are launched by that user's own launchd and are not visible to other users. Install and load the plist in the target user's context, and set its mode to 600 as Apple requires for agents and daemons:

sudo -u agent-projecta install -m 600 \
    /tmp/com.yourorg.projecta.plist \
    /Users/agent-projecta/Library/LaunchAgents/com.yourorg.projecta.plist

AGENT_UID=$(id -u agent-projecta)
sudo launchctl bootstrap gui/"$AGENT_UID" \
    /Users/agent-projecta/Library/LaunchAgents/com.yourorg.projecta.plist

bootstrap gui/<uid> is the current form on modern macOS; launchctl load is still accepted but deprecated. Run launchctl help on the target Mac to see the exact subcommands available in your release.

If this Mac is also a GitHub Actions self-hosted runner, review GitHub's hardening for self-hosted runners and run the runner service under the project's dedicated user, not under an admin account.

Project and OS-User Separation Checklist

Walk this list once per new project workspace:

  • OS user created with sysadminctl -addUser
  • /Users/agent-projectX mode is 700 (ls -ld)
  • Working directory inside home exists and is mode 700
  • Secret values loaded into that user's login Keychain, not into env files or plists
  • LaunchAgent plist in ~/Library/LaunchAgents/ for that user only
  • Plist is owned by the agent user and mode 600
  • Plist bootstrapped in that user's GUI domain (bootstrap gui/<uid>)
  • launchctl list as Project A's user does not show Project B's agents
  • ls /Users/agent-projectb as Project A's user returns Permission denied
  • Logs write to the agent user's home, not a shared path
  • Cleanup scripts reference $HOME paths only, never hardcoded /Users/agent-*

Failure and Recovery Checks

Check 1 — cross-user read is blocked:

sudo -u agent-projecta ls /Users/agent-projectb/workdir
# Expected: ls: /Users/agent-projectb/workdir: Permission denied

Check 2 — LaunchAgent namespace is separate:

sudo -u agent-projecta launchctl list | grep projectb || echo "isolated"
# Expected: isolated

Check 3 — Keychain is scoped: add a test item to Project A's login Keychain with security add-generic-password, then run sudo -u agent-projectb security find-generic-password -a agent-projecta. It should fail to locate the item because the lookup runs against Project B's own Keychain.

If any of the three checks fail, stop and fix before loading real agents. A failed check is not a passing build.

Cost Worksheet

ScenarioFigment planMonthlyAnnual at 12×
2–3 sequential agents, Intel-compatible targetsVirtual Intel macOS$29$348
Concurrent agents or ARM/GPU workM-series Mac (entry)from $95from $1,140
One managed CI Mac with backups + monitoringManaged Build Mac$249$2,988
Team: two Macs, two CI runners, one hour supportManaged Mac Team$999$11,988

Prepayment terms, taxes, and any setup fees appear on the actual quote at purchase. Capacity for physical Intel and M-series hardware is confirmed in the quote; the $49/month i3 Mac and physical M-series offers currently collect requests until supply is confirmed.

Start or Migrate

Request your configuration at Figment Mac Hosting, then move to the quick-start. Once the Mac is live:

  1. Run the sysadminctl -addUser block above for every project.
  2. Walk the separation checklist per user.
  3. Run all three failure checks before pointing production traffic or schedules at the new agents.

To migrate from an existing host, export each project's LaunchAgent plist, rewrite any absolute paths under the new user's home, load secrets into the new login Keychain, bootstrap the plist in the target user's GUI domain, then re-run the failure checks.


Related guides:


Subscribe for Figma design and website publishing guides.


Figment.so

Contact

Twitter

Website pricing

Compute index

About

Privacy

Terms