Skip to main content
Migrating from the legacy script-based Detect rollout? Run Legacy macOS Detect Cleanup before rolling out the .pkg so the old runlayer-scan artifacts don’t conflict with com.runlayer.aiwatch.

Overview

A signed, notarized aiwatch binary installs once per device via .pkg. Tenant config — host, a single organization API key, and the Enforcement / Sessions capability flags — is pushed via an MDM Configuration Profile.

Capabilities

All behaviors ship in the same .pkg and shared PPPC / Login Items profiles. The capability flags in the tenant-config profile decide what runs — flip a flag and re-push the profile, no .pkg reinstall. If you need multiple configurations, deploy one tenant-config profile per group. The .pkg, PPPC profile, and Login Items profile are universal and can be reused unchanged across all configurations. Detect is always enabled after AI Watch is installed. Enforce and Sessions are controlled by package configuration, and one organization API key authenticates scanning, enforcement, and sessions; no enrollment keys are required.
FeatureConfigurationWhat it does
DetectAlways onDiscovers shadow MCP servers, skills, and plugins through scheduled scans
EnforceEnforcement / AIWATCH_ENFORCEMENTInstalls client hooks that block unmanaged MCP sources and policy-check local tool activity
SessionsSessions / AIWATCH_SESSIONSInstalls the full event hook set for Sessions telemetry
  • Enforce is disabled by default. Set Enforcement=true (macOS) or AIWATCH_ENFORCEMENT=1 (Windows) to block unmanaged MCP sources and policy-check local tool activity.
  • Sessions is enabled by default. Set Sessions=false (macOS) or AIWATCH_SESSIONS=0 (Windows) to skip the full event/session hook set.
Hook installation follows the combined capability state: AI Watch installs hook configs when either Enforce or Sessions is enabled. Detect-only requires explicitly setting Sessions=false or AIWATCH_SESSIONS=0; omitting Sessions uses the default enabled state and installs hooks for monitoring-only telemetry. The MDM Configuration setup wizard bakes the flag values into the downloaded profile from the Enforce policies / Collect session data toggles. When hand-editing a macOS profile, set each flag to <true/> or <false/> explicitly.

Prerequisites

  • Devices enrolled via UAMDM (User-Approved MDM) or DEP/ADE. TCC payloads are ignored on manually-enrolled MDM.
  • A single organization API key with the Detect Scan role minted in Settings → Organization API keys in the Runlayer dashboard. Record the secret value (rl_org_...). The same key authenticates scanning, enforcement, and sessions.
  • Your Runlayer tenant host URL (e.g. https://your-instance.runlayer.com).
Apple Silicon only for now. The current release ships an arm64 .pkg.

Artifacts

The package is a .zip named aiwatch-<version>-macos-arm64.zip. Contents:
FilePurpose
aiwatch-<version>-macos-arm64.pkgSigned + notarized installer (single aiwatch binary + scan LaunchAgent + hook bootstrap)
com.runlayer.aiwatch.pppc.mobileconfigFull Disk Access / TCC grants (upload as-is)
com.runlayer.aiwatch.loginitems.mobileconfigPre-approves the bundled LaunchAgents on macOS 13+ (upload as-is; LabelPrefix=com.runlayer.aiwatch covers all current and future user-context units)
Contact your Runlayer account team if you don’t have the .zip yet.
Deploy the three Configuration Profiles before the .pkg. Profiles must land in /Library/Managed Preferences/ and TCC before the bundled LaunchAgent’s first scan tick — otherwise aiwatch logs host not configured and TCC denies project-config reads until the next MDM sync.

Deployment

1

Define two Custom Attributes (one-time per Organization Group)

  1. Devices → Provisioning → Custom Attributes → Add.
  2. CustomAttribute1 → set value to your tenant host URL (e.g. https://your-instance.runlayer.com).
  3. CustomAttribute2 → set value to your org API key secret (rl_org_...).
2

Upload PPPC + Login Items profiles

  1. Devices → Profiles → Add → Upload.
  2. Upload com.runlayer.aiwatch.pppc.mobileconfig and com.runlayer.aiwatch.loginitems.mobileconfig as-is. Both pre-pinned to Developer ID team AF2M8HC7A2.
  3. Assign to your target Smart Group.
3

Upload the WS1 tenant-config profile

  1. Devices → Profiles → Add → Upload.
  2. Upload com.runlayer.aiwatch.config.ws1.mobileconfig as-is — no editing. WS1 substitutes {CustomAttribute1} / {CustomAttribute2} at deploy time.
  3. Assign to the same Smart Group.
4

Upload the .pkg

  1. Apps & Books → Internal → Add Application.
  2. Upload aiwatch-<version>-macos-arm64.pkg.
  3. Assign to the same Smart Group.
To rotate values later: update the Custom Attribute values in the WS1 console and re-publish the profile. No .pkg reinstall.

Verification

On a test Mac after MDM sync:
# 1. .pkg installed (binary + scan agent)?
test -x /usr/local/bin/aiwatch && \
  test -f /Library/LaunchAgents/com.runlayer.aiwatch.plist && \
  echo "binary + scan agent OK"

# 2. Signature + notarization?
pkgutil --check-signature /path/to/aiwatch-<version>-macos-arm64.pkg
codesign -dv --verbose=4 /usr/local/lib/runlayer/aiwatch/aiwatch
# Expect: Authority=Developer ID Application: Anysource Inc. (AF2M8HC7A2)
#         Identifier=com.runlayer.aiwatch

# 3. Configuration Profile landed (host + org API key + capability flags)?
defaults read /Library/Managed\ Preferences/com.runlayer.aiwatch.plist

# 4. Scan agent loaded for the console user?
launchctl print "gui/$(id -u)/com.runlayer.aiwatch"

# 5. Login Items profile applied (macOS 13+)?
sfltool dumpbtm | grep -i runlayer

# 6. PPPC grants visible to TCC?
sudo log show --predicate 'subsystem == "com.apple.TCC"' --last 5m | grep -i aiwatch

# 7. Trigger an immediate scan
/usr/local/bin/aiwatch scan

# 8. (Enforce or Sessions) Confirm hooks are installed for the console user
/usr/local/bin/aiwatch setup hooks check --mdm
# Expect: exit 0 (compliant). Exit 1 = hook configs drifted (the next bootstrap tick rewrites them).

# 9. View scan output (LaunchAgent stdout/stderr captured by macOS unified log)
log show --predicate 'process == "aiwatch"' --last 1h
# Tail in real time:
log stream --predicate 'process == "aiwatch"'
In the Runlayer dashboard, navigate to Shadow and confirm scan data is arriving from the test device.

Common post-deploy issues

Confirm com.runlayer.aiwatch.loginitems.mobileconfig is scoped to the same device group as the .pkg, then force an MDM sync and check:
sfltool dumpbtm | grep -i runlayer
On macOS 13+, expect the Runlayer item to show enabled allowed visible. macOS 12 and older ignore the Login Items payload; the notification control only exists on macOS 13+.
Verify you are deploying the official signed and notarized release artifact:
pkgutil --check-signature /path/to/aiwatch-<version>-macos-arm64.pkg
spctl --assess --type install -vv /path/to/aiwatch-<version>-macos-arm64.pkg
Expected: a Developer ID Installer signature for Anysource Inc. and source=Notarized Developer ID.
The PPPC profile pins Full Disk Access to identifier com.runlayer.aiwatch and Developer ID team AF2M8HC7A2. Confirm the installed binary matches:
codesign -dv --verbose=4 /usr/local/lib/runlayer/aiwatch/aiwatch
Expected: Authority=Developer ID Application: Anysource Inc. (AF2M8HC7A2) and Identifier=com.runlayer.aiwatch. Custom or ad-hoc signed builds will not satisfy the PPPC CodeRequirement.

Customize scan schedule (optional)

The bundled LaunchAgent ships with a 15-minute StartInterval. Most tenants don’t need to change it. To override, push the script below via your MDM’s recurring-script mechanism (Jamf Policy, Kandji Custom Script, SimpleMDM Script, etc.):
#!/bin/bash
set -euo pipefail
PLIST=/Library/LaunchAgents/com.runlayer.aiwatch.plist
INTERVAL=1800   # 30 min — edit per tenant
/usr/libexec/PlistBuddy -c "Set :StartInterval ${INTERVAL}" "$PLIST"

CONSOLE_UID=$(stat -f %u /dev/console 2>/dev/null || echo "")
if [ -n "$CONSOLE_UID" ] && [ "$CONSOLE_UID" != "0" ]; then
    launchctl bootout "gui/${CONSOLE_UID}/com.runlayer.aiwatch" 2>/dev/null || true
    launchctl bootstrap "gui/${CONSOLE_UID}" "$PLIST" 2>/dev/null || true
fi
The next .pkg upgrade resets StartInterval to the bundled default — the override script must run on a recurring schedule, or be re-applied after each upgrade.

Upgrade

Push the new .pkg version via your MDM’s Custom App / package mechanism. The pkgbuild receipt (com.runlayer.aiwatch) tracks every file in the install layout; installer removes any files in the old receipt not present in the new payload and atomically lays down the new tree per file. Mid-scan upgrades are safe — macOS lets you unlink an open file, the running aiwatch keeps its mapped .dylib pages until the next LaunchAgent tick picks up the new version. The three Configuration Profiles (tenant config, PPPC, Login Items) are unchanged across version upgrades.
If you’ve overridden StartInterval via Custom Script, the upgrade resets it to the bundled default. Re-apply the override script on a recurring schedule.

Uninstall

For full package and hook cleanup guidance, see Remove AI Watch.

Troubleshooting

For deployment diagnostics and common macOS package issues, see Troubleshooting.