AGENTIC AI / OCTOBER 2026  ·  7 MIN READ

Multi Agent Systems on macOS under tighter Full Disk Access

Apple is tightening how macOS grants and audits Full Disk Access (FDA), especially for background processes and agents. If you build local AI helpers, now is the time to refactor your design for least privilege. In this piece I share a practical, developer-first blueprint for local agents and Multi Agent Systems that work inside macOS’s privacy model instead of trying to tunnel through it.

IN THIS ARTICLE   What changed, and why it matters for local agents  ·  A minimal, privilege‑separated architecture for macOS agents  ·  Designing Multi Agent Systems that respect macOS privacy  ·  Entitlements and TCC: file access without Full Disk Access  ·  Brokering tools with MCP: declare, gate, and audit  ·  Avoiding UX landmines: prompts, login items, and MDM  ·  Testing your agent under the new rules  ·  What to do next

KEY TAKEAWAYS

01   Split your agent into a model controller, a tool broker, and narrowly-scoped adapters; avoid blanket Full Disk Access.

02   Use App Sandbox entitlements, user-selected file access and security-scoped bookmarks; centralise prompts in a GUI broker.

03   Adopt MCP to declare and gate capabilities, and test with TCC logs and automated permission-reset cycles.

What changed, and why it matters for local agents

Apple has signalled a stricter posture on Full Disk Access (FDA) for macOS, in response to the way AI agents increasingly automate across the desktop. You don’t need to read between the lines to see the risk model: processes that roam the filesystem, correlate personal data, and act continuously in the background will be asked to justify every privilege they request. If you ship a helper that quietly starts scanning home directories, expect more prompts and more denials.

For developers this isn’t existential; it’s architectural. FDA has always been a last resort, and the practical route on macOS is to narrow your surface: prefer user-selected files and directories, encapsulate background work behind a single, auditable bundle identifier, and make capabilities explicit. That applies doubly if you’re building Multi Agent Systems where multiple autonomous components could otherwise each pull on different parts of the system.

A minimal, privilege‑separated architecture for macOS agents

The path of least resistance is a three-part split:

– Controller: the LLM/runtime and planning logic. It holds no direct filesystem or network privileges beyond what’s needed to contact a local tool broker. Think of it as pure compute.

– Tool broker: a long-lived, signed, sandboxed macOS app or login item that owns the permissions and user interactions (file pickers, calendars, reminders, etc.). It exposes a small local API (XPC/Unix domain socket/localhost) to the Controller.

– Capability adapters: narrowly-scoped modules inside the broker (or separate XPC services) that implement specific operations (read a file in a particular folder, append to a daily journal, send an email via the user’s default client).

Design rules I follow:

– The Controller never touches the disk directly; it asks the broker for named capabilities.

– The broker requests access using user-driven flows (NSOpenPanel for folders, calendars/contacts prompts when first used). It stores security-scoped bookmarks for persistence.

– Each adapter validates the requested path or resource against what the user granted. No dynamic escalation without a fresh prompt in the broker UI.

– One bundle ID should own the sensitive grants; don’t fragment across multiple binaries unless you’re using XPC services that you can justify and audit.

This keeps privilege in one place, which in turn keeps auditability and user trust intact.

AYAN’S TAKE

On macOS, the right to read a file is a design decision, not an afterthought.

Designing Multi Agent Systems that respect macOS privacy

If you’re orchestrating multiple agents (planners, reviewers, executors), resist the urge to give each of them native reach. Instead:

– Treat agents as personas within the Controller. They share the same narrow IPC out to the broker.

– Use named capabilities rather than raw paths. For example, `read_workspace_docs` maps to a specific, user-selected folder and nothing else.

– Build a policy layer in the Controller that matches an agent’s role to allowed capabilities and parameters (e.g., an email-drafting agent can request `compose_email`, but not `read_file`).

This pattern scales: new agents don’t imply new macOS prompts; they reuse existing, least-privilege grants through the broker.

Entitlements and TCC: file access without Full Disk Access

You don’t need FDA to be useful. With the App Sandbox enabled, a macOS app can gain durable access to user-selected files and folders using security-scoped bookmarks. Practically:

– Enable the App Sandbox.

– Allow user-selected read-write.

– Prompt the user once to pick the working directory (or a small set of folders).

The design principles behind this are set out in Designing for Agents: When Software Starts to Act.

– Persist the bookmark; resolve it when the broker starts; scope every path operation to that bookmark.

A minimal entitlements file for the broker might look like this (shown in JSON for readability):

json{
  "com.apple.security.app-sandbox": true,
  "com.apple.security.files.user-selected.read-write": true,
  "com.apple.security.files.bookmarks.app-scope": true,
  "com.apple.security.files.downloads.read-write": false,
  "com.apple.security.files.documents.read-write": false,
  "com.apple.security.network.client": true
}

Notes:

– Marking Documents or Downloads as blanket read-write will broaden scope; I avoid it unless the user explicitly picks those folders.

– Security-scoped bookmarks are essential: they let you reopen access across restarts without re-prompting, so long as the original grant covered the path.

CLI-only agents can’t present NSOpenPanel. The reliable pattern is to ship a tiny GUI broker or Preferences app that collects user grants and stores bookmarks; headless helpers (login items/XPC services) consume those bookmarks.

For background persistence on login, a LaunchAgent is valid, but a signed login item via ServiceManagement (SMAppService) is cleaner. If you must use a LaunchAgent, keep it auditable and avoid running as root. Example install (development only):

bash#!/usr/bin/env bash
set -euo pipefail
PLIST="$HOME/Library/LaunchAgents/com.example.agent.broker.plist"
cat > "$PLIST" <<'PLIST'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict>
  <key>Label</key><string>com.example.agent.broker</string>
  <key>ProgramArguments</key>
  <array><string>/usr/local/bin/agent-broker</string></array>
  <key>RunAtLoad</key><true/>
  <key>KeepAlive</key><true/>
  <key>ProcessType</key><string>Interactive</string>
</dict></plist>
PLIST
launchctl unload "$PLIST" 2>/dev/null || true
launchctl load "$PLIST"
launchctl kickstart -k gui/$(id -u)/com.example.agent.broker

This runs in the user session, not as root. If you need GUI prompts for permissions, prefer a proper app bundle with a menu bar item instead of a bare LaunchAgent.

Brokering tools with MCP: declare, gate, and audit

The Model Context Protocol (MCP) gives us a clean way to present capabilities to a model while keeping enforcement in the broker. The shape I like:

– The broker exposes a local JSON interface (HTTP on 127.0.0.1 or stdio) with a registry of tools and a policy engine.

– Each tool declares parameters, required grants, and a verifier that checks requested paths against resolved bookmarks.

– The Controller connects via MCP client glue and never sees raw file paths it isn’t allowed to touch.

A compact MCP-like broker in Node/TypeScript might look like this:

typescriptimport http from 'http';
import { parse } from 'url';

const roots = { workspace: process.env.AGENT_WORKSPACE! }; // resolved from bookmark
function withinRoot(root: string, p: string) { return p.startsWith(root + '/'); }

const tools = {
  read_file: async ({ root, path }: any) => {
    const base = roots[root]; if (!base) throw new Error('unknown root');
    const full = base + '/' + path.replace(/^\/+/, '');
    if (!withinRoot(base, full)) throw new Error('out of scope');
    return await Bun.file(full).text(); // or fs.promises.readFile
  },
};

http.createServer(async (req, res) => {
  const { pathname } = parse(req.url!, true);
  if (req.method === 'POST' && pathname === '/tool') {
    const body = await new Response(req).json();
    const fn = (tools as any)[body.name];
    if (!fn) return res.writeHead(404).end();
    try { const data = await fn(body.args); res.end(JSON.stringify({ ok: true, data })); }
    catch (e: any) { res.writeHead(400).end(JSON.stringify({ ok: false, error: e.message })); }
    return;
  }
  res.writeHead(404).end();
}).listen(7777);

Even in this toy sketch you can see the policy hooks: capabilities are named, parameters are validated, and access is confined to a user-approved root. In production, add:

– A manifest that lists every tool, inputs, side effects, and required grants.

An independent AI architecture check is the practical next step when these risks apply to your own systems.

– Structured audit logs of tool calls (timestamp, agent persona, capability, parameters, result size).

– Rate limits and circuit breakers to avoid unbounded loops.

– Optional user confirm flows for risky actions (e.g., sending email, deleting files).

Avoiding UX landmines: prompts, login items, and MDM

Permission prompts are a user experience tax you pay at design time. Minimise them by:

– Centralising all prompts in the broker app. Present a clear onboarding that asks for exactly the folders and data types you need, nothing more.

– Reusing security-scoped bookmarks rather than re-prompting. Keep them in the keychain or app container, and refresh on failure.

– Using a single bundle ID for the broker so TCC grants apply consistently across updates.

– Avoiding unnecessary helpers. If you must use XPC services, justify each with its own, tight entitlement set.

Enterprise deployments can pre-approve certain privacy dialogues via MDM using a PPPC (Privacy Preferences Policy Control) profile. This is for managed Macs; it won’t help for consumer installs. A minimal JSON-shaped illustration (actual profiles are XML plists):

json{
  "PayloadType": "com.apple.TCC.configuration-profile-policy",
  "PayloadContent": [
    {
      "Identifier": "com.example.agent.broker",
      "Services": {
        "SystemPolicyDocumentsFolder": { "Allowed": true, "Authorization": "Allow" }
      }
    }
  ]
}

Don’t overreach. Pre-approving “All Files” access is heavy-handed and often unnecessary if you follow the user-selected-folder pattern.

Finally, be transparent. Expose a simple “Permissions” screen that lists every capability, its status (granted/denied), and an easy way to re-request or remove a grant.

Testing your agent under the new rules

Treat permissions as a test dimension. I run three cycles:

– Clean-room: reset TCC and simulate first-run onboarding.

– Degraded: revoke a single grant and ensure the broker recovers with a friendly prompt.

– Adversarial: attempt out-of-scope paths and confirm denials are logged and safe.

Useful commands while iterating:

bash# Reset file access decisions for your app (use your bundle ID)
/usr/bin/tccutil reset All com.example.agent.broker

# Watch TCC logs in real time
/usr/bin/log stream --predicate 'subsystem == "com.apple.TCC"' --info

# Quick list of running LaunchAgents matching your label
/bin/launchctl print gui/$(id -u) | /usr/bin/grep -A2 com.example.agent.broker || true

Also test updates: bump your app version and ensure bookmarks still resolve; check that your login item or LaunchAgent restarts cleanly; validate that your MCP tool registry is deterministic across restarts so the Controller doesn’t cache stale capabilities.

Security walkthroughs are worth it: try to trick your own broker with path traversal, symlinks out of the approved root, and oversized outputs. Your audit log should make these attempts obvious.

What to do next

If you’re shipping or planning a local agent on macOS, here’s a concrete plan you can action this week:

1) Inventory capabilities. List every operation your agent performs. Map each to a resource type (file under X, calendar, contacts, network call).

2) Decide your broker boundary. Pick one bundle ID to own user prompts and permissions. Everything else talks to it via a local API.

3) Enable the sandbox. Start with user-selected read-write and bookmarks. Remove any broad folder entitlements you don’t need.

4) Implement capability adapters. One adapter per operation; enforce parameters and scope before doing any work.

5) Add MCP glue. Present a typed tool registry to the Controller. Make every tool self-documenting with a manifest.

6) Build onboarding. Ask for the minimum folders and data types; explain why. Persist bookmarks; expose a Permissions screen.

7) Test like a user and like an attacker. Automate TCC resets and negative tests; capture audit logs.

8) Avoid FDA unless there’s no alternative. If you truly need it, be explicit in your UX and consider an enterprise-only path with PPPC.

If you’d like a second pair of eyes on your design or a quick architecture review, I’m happy to help. I occasionally take on short engagements as an Agentic AI Consultant or Solution Architecture Consultant. If you’re moving from prototype to production and need to align privacy, performance, and developer ergonomics, a brief stint with a Generative AI Consultant mindset can de-risk the path.

The direction of travel is clear: Apple will continue to push capability-scoped workflows. Build within that model now and your agents will feel native, trustworthy, and fast—without the permission pop-up fatigue.

AYAN SARKAR

Chief Technology & AI Officer and Co-Founder, Webskitters. Writing about AI strategy, technology architecture and leadership.

Follow on LinkedIn ↗    All articles ↗