Figment.so
Mac hostingWebsite hostingGuidesHow to usePricing

Add Approval Steps to Remote Mac Agent Actions

An agent that can send emails, delete files, or push to production needs a human in the loop for high-risk actions. The useful question is which actions are high-risk. Answer it once with a tool-risk decision table, then reference it every time you add a new tool.

Choose a Mac Plan for Approval-Gated Agents

The Figment virtual Intel macOS environment at $29/month (2 vCPU, 8 GiB RAM, 80 GiB disk) is a capable host for orchestration agents that call external APIs, manage files, and trigger CI pipelines. It runs macOS, reaches outbound to any approval channel or API, and runs the terminal-based approval scripts below. Access is through the browser console; run the agent from the desktop terminal, or deploy an outbound GitHub Actions self-hosted runner that the agent job targets.

Move up to an M-series Mac from $95/month when the agent invokes ARM-only binaries or macOS-native frameworks as part of an action. Exact chip, RAM and disk are confirmed in the quote. The Managed Build Mac at $249/month bundles one M-series Mac with CI setup, backups, monitoring, and 30 minutes of monthly support — useful when the approval gate lives inside a CI workflow and pull-request reviewers are already the operators. Physical plans collect requests until supply is confirmed; ask for a configuration before you commit.

Tool-Risk Decision Table

Before adding a tool to your agent, classify it here. The classification determines whether the action runs automatically, needs a soft confirmation, or needs an explicit human approval before proceeding.

Tool / actionReversible?Blast radiusRisk levelApproval required
Read a file or directory listingYesNoneLowNo — run automatically
Fetch data from a read-only APIYesNoneLowNo — run automatically
Write or update a local filePartial (if backed up)Single fileMediumSoft confirm (log + continue unless --dry-run)
Create or update a database recordPartial (if snapshotted)Single recordMediumSoft confirm
Send an email or Slack messageNo — message is deliveredRecipient inboxHighHuman approval required
Delete a file or database recordNo (without backup)Single itemHighHuman approval required
Execute a shell command built from agent outputNoArbitraryCriticalHuman approval + command displayed before execution
Push code to a production branchNoProduction systemCriticalHuman approval required
Call an external API that bills per requestPartial (request sent; can refund)Billing, side effectsHighSoft confirm with per-job budget guard
Revoke or rotate a credentialPartial (re-issue)All services using itCriticalHuman approval required

Revisit this table when you add any new tool. If a tool's blast radius is unclear, treat it as Critical until you understand the downstream effects.

Pattern 1 — GitHub Actions Environment Approval Gate

If your agent runs as a GitHub Actions job on a Figment self-hosted runner, use a protected environment with required reviewers, documented in GitHub's managing environments guide, to gate the high-risk phase. Pair it with GitHub's hardening guidance for self-hosted runners — self-hosted runners are not ephemeral by default and should only run trusted code.

# .github/workflows/agent-with-approval.yml
jobs:
  plan:
    runs-on: [self-hosted, macOS]
    outputs:
      action_plan: ${{ steps.plan.outputs.plan }}
    steps:
      - id: plan
        run: python3 agent_planner.py --output plan.json
      - run: cat plan.json  # Reviewers see the planned actions

  execute:
    needs: plan
    runs-on: [self-hosted, macOS]
    environment: production-approval   # requires a named reviewer to approve
    steps:
      - run: python3 agent_executor.py --plan plan.json

Configure production-approval in Settings → Environments: add required reviewers (up to six), optionally prevent self-approval, and set a wait timer appropriate for your team. Environment-specific secrets (API keys, deploy tokens) are injected only after approval. When execute is reached, GitHub pauses the workflow and notifies reviewers, who see the plan.json output from the previous step before approving. The exact option names move around in GitHub's UI; check the linked guide against what you see.

Pattern 2 — Local Approval Gate Script

For agents launched directly on the Mac through launchd rather than GitHub Actions, an approval gate script pauses execution and waits for a signal file before continuing.

#!/bin/bash
# approval_gate.sh
# Usage: ./approval_gate.sh "ACTION_DESCRIPTION" TIMEOUT_SECONDS
set -euo pipefail
DESCRIPTION="${1:-Approve action}"
TIMEOUT="${2:-300}"
GATE_DIR="/Users/agent/approvals"
LOG="/Users/agent/logs/approvals.log"
mkdir -p "$GATE_DIR" "$(dirname "$LOG")"

TOKEN="$(date +%s)_$$_$RANDOM"
APPROVE_FILE="$GATE_DIR/approve_${TOKEN}"
DENY_FILE="$GATE_DIR/deny_${TOKEN}"

echo "$(date -u +%FT%TZ) APPROVAL REQUIRED: $DESCRIPTION" | tee -a "$LOG"
echo "  -> touch '$APPROVE_FILE' to approve"
echo "  -> touch '$DENY_FILE'   to deny"

# Notify the operator via a Shortcut you have pre-built in Shortcuts.app.
shortcuts run "Alert: Approval Required" 2>/dev/null || true

elapsed=0
while [ "$elapsed" -lt "$TIMEOUT" ]; do
  if [ -f "$APPROVE_FILE" ]; then
    echo "$(date -u +%FT%TZ) APPROVED: $DESCRIPTION" >> "$LOG"
    rm -f "$APPROVE_FILE" "$DENY_FILE"
    exit 0
  fi
  if [ -f "$DENY_FILE" ]; then
    echo "$(date -u +%FT%TZ) DENIED: $DESCRIPTION" >> "$LOG"
    rm -f "$APPROVE_FILE" "$DENY_FILE"
    exit 1
  fi
  sleep 5
  elapsed=$((elapsed + 5))
done

echo "$(date -u +%FT%TZ) TIMEOUT: $DESCRIPTION" >> "$LOG"
exit 1

The macOS Shortcuts command line runs a Shortcut you build once in Shortcuts.app. shortcuts run "Alert: Approval Required" sends a notification; the operator responds by touching the approve or deny file from any terminal on the VM. Inject the gate into the agent loop for every High and Critical action:

import subprocess

def require_approval(description: str, timeout: int = 300) -> None:
    result = subprocess.run(
        ["/Users/agent/scripts/approval_gate.sh", description, str(timeout)],
        check=False,
    )
    if result.returncode != 0:
        raise RuntimeError(f"Action denied or timed out: {description}")

Failure and Timeout Behaviour

OutcomeAgent behaviourLogged
Approved within timeoutExecution continuesYes — APPROVED with timestamp
Denied by operatorRuntimeError raised, exit 1Yes — DENIED with timestamp
Timeout expiresExit 1; launchd retries on next intervalYes — TIMEOUT with description
Operator unreachableTimeout fires after configured secondsYes — treated as timeout

Do not set timeouts longer than your acceptable blast-radius window. A "delete database records" action with a 24-hour approval window defers damage, it does not prevent it.

Audit Trail Requirements

Every approval decision must be durable:

  • Write every APPROVED, DENIED, and TIMEOUT line to a log outside the agent's working directory so a failing job cannot truncate its own evidence.
  • Include the action description verbatim, the UTC timestamp, and the approver identifier. For GitHub Actions, the reviewer's login appears on the workflow run.
  • Rotate approvals.log monthly and retain at least 90 days.
  • Scan the log weekly for TIMEOUT lines — repeated timeouts mean the notification path is broken or the window is too short.

Start or Migrate the Workload

The $29 Figment VM runs both patterns above. Start with the GitHub Actions environment gate if your agent already has a CI workflow; use the local gate script for launchd-scheduled agents. If you need ARM execution or GPU work alongside the approval logic, request an M-series quote. Browse Mac hosting plans or request your configuration.

Related reading


Subscribe for Figma design and website publishing guides.


Figment.so

Contact

Twitter

Website pricing

Compute index

About

Privacy

Terms