> ## Documentation Index
> Fetch the complete documentation index at: https://docs.runlayer.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Test Device

> Install AI Watch on one macOS, Windows, or Linux device without an MDM

Use this flow to test package-based AI Watch on one device without enrolling it in an MDM. A single Test Device configuration and organization API key can report from macOS, Windows, and Linux.

<Note>
  macOS and Windows support Monitor, Protect, Enforce, and Sessions. Linux is **Detect only** and ignores the configuration's endpoint Mode and Sessions settings. For a managed rollout, use [Deploy AI Watch](/shadow-ai/deploy).
</Note>

## Prerequisites

* Local administrator or root access to the test device
* A supported package platform: Apple Silicon macOS, x64 Windows, or x86\_64 Linux
* An organization API key with the **Shadow AI Scan** role, created in **Settings → Organization API keys**
* Your Runlayer tenant host URL (for example, `https://your-instance.runlayer.com`)

<Warning>
  Record the organization API key when the configuration is created. Runlayer only shows the full secret once. If the Test Device guide later shows `<YOUR_API_KEY>` and you no longer have the secret, delete the configuration and create a new one.
</Warning>

Create the configuration under **Settings → MDM configuration → Add new → Test Device**, then select the operating-system tab in the setup guide. For an existing configuration, open its menu and select **View deployment setup**.

## macOS — install and configure

<Steps>
  <Step title="Download the signed package">
    On the **macOS** tab, download the signed `aiwatch-<version>-macos-arm64.pkg` selected by your Client Updates policy.
  </Step>

  <Step title="Install the package">
    From the directory containing the download, run:

    ```bash theme={null}
    sudo installer -pkg "aiwatch-<version>-macos-arm64.pkg" -target /
    ```

    The package installs `/usr/local/bin/aiwatch`, the scan LaunchAgent, the hook bootstrap LaunchDaemon, and the hourly update LaunchDaemon.
  </Step>

  <Step title="Write the managed preferences">
    Replace the host and organization API key below. Set `Mode`, its paired legacy `Enforcement` value, `Sessions`, and the discovery and scan-tuning values (`DetectProcesses`, `DetectContainers`, `ProjectDepth`, `ProjectTimeout`) to match the Test Device configuration:

    ```bash theme={null}
    PREFS="/Library/Managed Preferences/com.runlayer.aiwatch"

    sudo defaults write "$PREFS" Host -string "https://your-instance.runlayer.com"
    sudo defaults write "$PREFS" OrgApiKey -string "rl_org_..."
    sudo defaults write "$PREFS" Mode -string "monitor"
    sudo defaults write "$PREFS" Enforcement -bool false
    sudo defaults write "$PREFS" Sessions -bool false
    sudo defaults write "$PREFS" DetectProcesses -bool false
    sudo defaults write "$PREFS" DetectContainers -bool false
    sudo defaults write "$PREFS" ProjectDepth -int 7
    sudo defaults write "$PREFS" ProjectTimeout -int 60
    sudo chown root:wheel "$PREFS.plist"
    sudo chmod 644 "$PREFS.plist"
    ```

    Pair Mode and legacy Enforcement as follows. See [Endpoint modes](/shadow-ai/enforce) for the behavior of each mode.

    | Mode      | `Enforcement` |
    | --------- | ------------- |
    | `monitor` | `false`       |
    | `protect` | `true`        |
    | `enforce` | `true`        |

    Set `Sessions` to `true` to collect session data, or `false` to disable it. Monitor with Sessions disabled is scan-only and does not install hooks.

    Set `DetectProcesses`, `DetectContainers`, `ProjectDepth`, and `ProjectTimeout` to match the Test Device configuration's discovery and scan-tuning settings; the values shown in the in-app setup guide already reflect them.
  </Step>

  <Step title="Grant Full Disk Access">
    Without MDM, there is no PPPC profile to grant Full Disk Access automatically. Open **System Settings → Privacy & Security → Full Disk Access**, select **+**, press **Command-Shift-G**, and add:

    ```text theme={null}
    /usr/local/lib/runlayer/aiwatch/aiwatch
    ```

    Enable the entry before scanning. Without this permission, macOS can prevent AI Watch from reading project configurations under protected locations such as `~/Desktop`, `~/Documents`, and `~/Downloads`.

    macOS 13 and newer may also show a one-time **Background Item Added** notification because no MDM Login Items profile pre-approves the scan LaunchAgent.
  </Step>

  <Step title="Apply hooks when enabled">
    If Mode is Protect or Enforce, or Sessions is enabled, run:

    ```bash theme={null}
    sudo launchctl kickstart -k system/com.runlayer.aiwatch.bootstrap
    ```

    Restart your AI clients after the bootstrap completes so they load the installed hook configurations. Skip this step for scan-only Monitor (`Mode=monitor`, `Sessions=false`).
  </Step>
</Steps>

## Verification

On a test Mac after installation and configuration:

```bash theme={null}
# 1. .pkg installed (binary + scan agent + bootstrap + updater)?
test -x /usr/local/bin/aiwatch && \
  test -f /Library/LaunchAgents/com.runlayer.aiwatch.plist && \
  test -f /Library/LaunchDaemons/com.runlayer.aiwatch.bootstrap.plist && \
  test -f /Library/LaunchDaemons/com.runlayer.aiwatch.update.plist && \
  echo "binary + scan agent + bootstrap + updater 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. Managed preferences configured (host + org API key + Mode / Enforcement / Sessions)?
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. Hourly update daemon loaded in the system domain?
sudo launchctl print system/com.runlayer.aiwatch.update

# 6. Background item registered (macOS 13+)?
sfltool dumpbtm | grep -i runlayer

# 7. Full Disk Access activity visible to TCC?
sudo log show --predicate 'subsystem == "com.apple.TCC"' --last 5m | grep -i aiwatch

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

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

# 10. View process activity; update failures also appear as a non-zero
# last exit code in the launchctl output from step 5.
log show --predicate 'process == "aiwatch"' --last 1h
# Tail in real time:
log stream --predicate 'process == "aiwatch"'
```

In the Runlayer dashboard, navigate to **Shadow → Devices** and confirm the test Mac appears.

## Common post-deploy issues

<AccordionGroup>
  <Accordion title="Users see 'Background Item Added' after install">
    On a manual test-device install, this one-time macOS 13+ notification is expected because no MDM Login Items profile pre-approves the scan LaunchAgent.

    On a managed deployment, confirm `com.runlayer.aiwatch.loginitems.mobileconfig` is scoped to the same device group as the `.pkg`, then force an MDM sync and check:

    ```bash theme={null}
    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+.
  </Accordion>

  <Accordion title="Gatekeeper blocks the package">
    Verify you are deploying the official signed and notarized release artifact:

    ```bash theme={null}
    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`.
  </Accordion>

  <Accordion title="Full Disk Access is enabled but TCC still denies access">
    For a manual install, confirm `/usr/local/lib/runlayer/aiwatch/aiwatch` is enabled under **System Settings → Privacy & Security → Full Disk Access**. For MDM, the PPPC profile pins Full Disk Access to identifier `com.runlayer.aiwatch` and Developer ID team `AF2M8HC7A2`.

    Confirm the installed binary matches:

    ```bash theme={null}
    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.
  </Accordion>
</AccordionGroup>

## Customize scan schedule (optional)

The bundled LaunchAgent ships with a 15-minute `StartInterval`. Most tenants don't need to change it. On a managed fleet, push the script below via your MDM's recurring-script mechanism (Jamf Policy, Kandji Custom Script, SimpleMDM Script, etc.). On a manual test Mac, run it directly with administrator access:

```bash theme={null}
#!/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
```

<Note>
  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.
</Note>

## Upgrade

By default, the root `com.runlayer.aiwatch.update` LaunchDaemon checks hourly and installs the backend-selected signed + notarized `.pkg`. An MDM Custom App push or another manual `installer` run remains a fallback, but the next hourly tick will restore the backend-selected target. To hold a manually pushed version, set `AutoUpdate=false` in managed preferences or align the backend rollout policy first.

The package preinstall removes only the previous `/usr/local/lib/runlayer/aiwatch/` onedir tree before PackageKit deploys the new payload, so obsolete modules and version metadata cannot survive an upgrade or rollback. The separate Runlayer CLI installation is preserved. Mid-scan upgrades are safe — macOS lets you unlink an open file, and the running `aiwatch` keeps its mapped pages until the next LaunchAgent tick picks up the new version. Postinstall intentionally leaves an already-loaded updater running so it cannot terminate its own installer transaction; the hourly cadence remains stable.

MDM Configuration Profiles are unchanged across version upgrades. Preferences written manually under `com.runlayer.aiwatch` also remain in place.

<Warning>
  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.
</Warning>

## Uninstall

For full package and hook cleanup guidance, see [Remove AI Watch](/shadow-ai/deploy/remove).

## Troubleshooting

For deployment diagnostics and common macOS package issues, see [Troubleshooting](/shadow-ai/troubleshooting).

## Windows — install and configure

Windows testing uses the same signed x64 MSI as an Intune deployment, but installs it directly on one device.

<Steps>
  <Step title="Download the signed MSI">
    On the **Windows** tab of the Test Device setup guide, download `aiwatch-<version>-win-x64.msi`.
  </Step>

  <Step title="Install with the generated configuration">
    Open PowerShell as Administrator in the download directory and run the command rendered by the setup guide. It includes the selected host, organization API key, Mode, Sessions, discovery, scan-tuning, and update settings:

    ```powershell theme={null}
    msiexec /i aiwatch-<version>-win-x64.msi /qn AIWATCH_HOST=https://your-instance.runlayer.com AIWATCH_ORG_API_KEY=rl_org_... AIWATCH_MODE=monitor AIWATCH_ENFORCEMENT=0 AIWATCH_SESSIONS=0 AIWATCH_DETECT_PROCESSES=0 AIWATCH_DETECT_CONTAINERS=0 AIWATCH_AUTO_UPDATE=1 AIWATCH_PROJECT_DEPTH=7 AIWATCH_PROJECT_TIMEOUT=60
    ```

    The MSI installs `aiwatch.exe`, writes configuration under `HKLM\Software\Runlayer\AIWatch`, and registers the `AIWatchScan`, `AIWatchHooks`, and `AIWatchUpdate` tasks under `\Runlayer`.
  </Step>

  <Step title="Restart AI clients when hooks are enabled">
    If Mode is Protect or Enforce, or Sessions is enabled, restart running AI clients after installation so they load their hook configurations. The hook task is a no-op for scan-only Monitor (`Mode=monitor`, `Sessions=false`).
  </Step>

  <Step title="Verify">
    In an elevated PowerShell window:

    ```powershell theme={null}
    Get-ItemProperty "HKLM:\Software\Runlayer\AIWatch"
    Get-ScheduledTask -TaskPath "\Runlayer\" | Format-Table TaskName, State
    Start-ScheduledTask -TaskPath "\Runlayer\" -TaskName "AIWatchScan"

    # Protect, Enforce, or Sessions only
    & "C:\Program Files\Runlayer\AIWatch\aiwatch.exe" setup hooks check --mdm
    ```

    In Runlayer, open **Shadow → Devices** and confirm the Windows device appears. See [Intune verification](/shadow-ai/deploy/intune#verification) for detailed task and log checks.
  </Step>
</Steps>

## Linux — install and configure (Detect only)

Linux packages run all-user scans every 15 minutes through cron. They do not install hooks or support endpoint modes or Sessions. See [Linux support](/shadow-ai/deploy/linux#supported-distributions) before installing; do not install inside WSL.

<Steps>
  <Step title="Download a package">
    On the **Linux** tab of the Test Device setup guide, download the x86\_64 package for the distribution:

    * `.deb` for Debian or Ubuntu
    * `.rpm` for RHEL, Rocky, Alma, Fedora, or Amazon Linux
  </Step>

  <Step title="Install">
    <CodeGroup>
      ```bash Debian / Ubuntu theme={null}
      sudo apt-get install -y ./runlayer-aiwatch_<version>_amd64.deb
      ```

      ```bash RHEL / Fedora / Amazon Linux theme={null}
      sudo dnf install -y ./runlayer-aiwatch-<version>-1.x86_64.rpm
      ```
    </CodeGroup>
  </Step>

  <Step title="Write the non-secret Detect configuration">
    Keep the organization API key out of this world-readable file:

    ```bash theme={null}
    sudo install -d -m 755 /etc/runlayer/aiwatch
    sudo tee /etc/runlayer/aiwatch/config.json >/dev/null <<'JSON'
    {
      "Host": "https://your-instance.runlayer.com",
      "Sessions": false,
      "Enforcement": false,
      "DetectProcesses": false,
      "DetectContainers": false,
      "ProjectDepth": 7,
      "ProjectTimeout": 60,
      "AutoUpdate": true
    }
    JSON
    sudo chown root:root /etc/runlayer/aiwatch/config.json
    sudo chmod 644 /etc/runlayer/aiwatch/config.json
    ```
  </Step>

  <Step title="Write the root-only credentials">
    ```bash theme={null}
    sudo tee /etc/runlayer/aiwatch/credentials >/dev/null <<'EOF'
    RUNLAYER_API_KEY=rl_org_...
    EOF
    sudo chown root:root /etc/runlayer/aiwatch/credentials
    sudo chmod 600 /etc/runlayer/aiwatch/credentials
    ```
  </Step>

  <Step title="Scan and verify">
    ```bash theme={null}
    sudo /usr/lib/runlayer/run-aiwatch-scan.sh
    aiwatch scan --dry-run
    grep run-aiwatch /etc/cron.d/runlayer-aiwatch
    ```

    In Runlayer, open **Shadow → Devices** and confirm the Linux device appears with **Detect = Active** and **Protect / Enforce / Sessions = Disabled**. That disabled state is expected. See the full [Linux guide](/shadow-ai/deploy/linux) for legacy distributions, updates, and troubleshooting.
  </Step>
</Steps>
