macOSJamf Pro

Jamf deployment

Jamf deploys the same universal AgentKeeper PKG used by every macOS MDM. There is no Jamf-specific installer, companion install script, or IDE menu.

Universal signed PKG · Package policy

Jamf deploys the same universal AgentKeeper PKG used by every macOS MDM. There is no Jamf-specific installer, companion install script, or IDE menu.

Current package versions and checksums are published with the release artifacts. See Download artifacts.

Upload this file to Jamf: agentkeeper-runtime-macos-latest.pkg

The PKG supports Apple silicon and Intel Macs. Do not rename, rebuild, wrap, or replace it with a generated script, and do not build a fleet package from the source ZIP. This page is the single Jamf workflow: do not copy these steps into a second one.

Before deployment, subscribe to installer updates.

1. Download and verify the PKG

Download the checksums, then verify the exact file on a Mac:

shasum -a 256 agentkeeper-runtime-macos-latest.pkg
pkgutil --check-signature agentkeeper-runtime-macos-latest.pkg
xcrun stapler validate agentkeeper-runtime-macos-latest.pkg
spctl -a -vv -t install agentkeeper-runtime-macos-latest.pkg

The SHA-256 must match checksums.txt. The signature must name Developer ID Installer: RAD Security, Inc. (F65622XJDP). Stapler and Gatekeeper must pass.

2. Create the computer profile

In AgentKeeper, open Settings > Access > API keys and create an Enterprise Installer token for macOS > Jamf. Enter a stable deployment ID for this rollout. Copy the token when it appears. It is a secret and must not go in the configuration profile, a Jamf script parameter, logs, screenshots, or support messages.

The same scoped token can enroll assigned Macs until it expires or is revoked. Keep it only in the restricted Jamf policy/script record. If it appears in a screenshot or support message, revoke it immediately and create a replacement.

The selected scope can include all nine catalog surfaces for inventory. The PKG reconciles the seven local IDE/CLI surfaces: Claude Code, Cursor, Windsurf, GitHub Copilot, Codex, Gemini CLI, and Antigravity. Cowork and MCP Gateway keep their separate deployment paths.

In Jamf Pro:

  1. Open Computers > Configuration Profiles > New.
  2. Set the profile level to Computer Level.
  3. Open Application & Custom Settings > Upload and use preference domain com.agentkeeper.runtime.
  4. Upload this PLIST with your values:
<?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>api_url</key>
  <string>https://agentkeeper.dev</string>
  <key>org_id</key>
  <string>YOUR_AGENTKEEPER_ORG_ID</string>
  <key>mdm_provider</key>
  <string>jamf</string>
  <key>mdm_deployment_id</key>
  <string>YOUR_AGENTKEEPER_DEPLOYMENT_ID</string>
</dict>
</plist>

The deployment ID must match the token. An existing Jamf user assignment can remain for inventory or device ownership; AgentKeeper neither requires nor reads it. Do not put a user field, local account name, or Jamf user variable in the AgentKeeper payload. This is a computer-level deployment, and enrollment can complete before anyone signs in. Do not add enrollment_token; the runtime rejects a readable profile that contains it.

Gate the package on profile readiness

Scoping a profile and package to the same Smart Group does not guarantee delivery order. Create a Jamf Extension Attribute named AgentKeeper Profile Ready with this script:

#!/bin/bash
profile="/Library/Managed Preferences/com.agentkeeper.runtime.plist"

if [[ ! -f "$profile" || -L "$profile" || "$(stat -f %Su "$profile" 2>/dev/null)" != "root" ]]; then
  echo "<result>Not Ready</result>"
  exit 0
fi

size="$(stat -f %z "$profile" 2>/dev/null || true)"
if [[ ! "$size" =~ ^[0-9]+$ || "$size" -eq 0 || "$size" -gt 65536 ]]; then
  echo "<result>Not Ready</result>"
  exit 0
fi

mode="$(stat -f %Lp "$profile" 2>/dev/null || true)"
if [[ ! "$mode" =~ ^[0-7]{3,4}$ ]] || (( (8#$mode & 022) != 0 )); then
  echo "<result>Not Ready</result>"
  exit 0
fi

if /usr/bin/plutil -extract enrollment_token raw -o - "$profile" >/dev/null 2>&1; then
  echo "<result>Not Ready</result>"
  exit 0
fi

for key in api_url org_id mdm_provider mdm_deployment_id; do
  if ! /usr/bin/plutil -extract "$key" raw -o - "$profile" 2>/dev/null | /usr/bin/grep -q '[^[:space:]]'; then
    echo "<result>Not Ready</result>"
    exit 0
  fi
done

provider="$(/usr/bin/plutil -extract mdm_provider raw -o - "$profile" 2>/dev/null)"
if [[ "$provider" != "jamf" ]]; then
  echo "<result>Not Ready</result>"
  exit 0
fi

echo "<result>Ready</result>"

This check prints only Ready or Not Ready; it never prints the enrollment token and does not read it. Scope the configuration profile first, update inventory, then create a Smart Group where AgentKeeper Profile Ready is Ready. Scope the package policy to that profile-ready group. Existing Jamf ownership data is fine, but it stays outside the AgentKeeper payload and is not read by the runtime.

3. Upload and assign the PKG

  1. Open Settings > Computer management > Packages > New.
  2. Upload agentkeeper-runtime-macos-latest.pkg and record its version and SHA-256 in the package notes.
  3. Open Computers > Policies > New.
  4. Choose the required trigger and set frequency to Once per computer.
  5. Under Packages, add the AgentKeeper package with action Install.
  6. In Settings > Computer management > Scripts, create a dedicated AgentKeeper enrollment script from the code below. Paste the token into AGENTKEEPER_ENROLLMENT_TOKEN; do not use Jamf parameters $4 through $11, because parameters are process arguments on the Mac.
  7. Add the script to the policy and set its priority to After so the runtime binary exists before token staging starts.
  8. Scope the policy to the profile-ready Smart Group and save it.
#!/bin/bash
set -euo pipefail
set +x

AGENTKEEPER_ENROLLMENT_TOKEN='PASTE_ENTERPRISE_INSTALLER_TOKEN'
trap 'unset AGENTKEEPER_ENROLLMENT_TOKEN' EXIT

BIN="/Library/Application Support/AgentKeeper/bin/agentkeeper"
REMEDIATE="/Library/Application Support/AgentKeeper/helpers/agentkeeper-macos-remediate.sh"

if [[ "$(id -u)" -ne 0 || ! -x "$BIN" || ! -x "$REMEDIATE" ]]; then
  echo "AgentKeeper PKG is not ready for enrollment" >&2
  exit 1
fi

printf '%s' "$AGENTKEEPER_ENROLLMENT_TOKEN" | "$BIN" \
  stage-managed-enrollment \
  --token-stdin \
  --program-data "/Library/Application Support/AgentKeeper" \
  --install-dir "/Library/Application Support/AgentKeeper"
unset AGENTKEEPER_ENROLLMENT_TOKEN

"$REMEDIATE"

The PKG installs the runtime. The After script stages the token in a root-only 0600 file and starts enrollment. A successful exchange creates config.json and State/credential.json, removes that Mac's staged copy, and enrolls the machine even at the login window. After a regular non-root user signs in, the runtime discovers the active console account through macOS Directory Services and reconciles that account's AgentKeeper-owned hooks. It repeats this after a user switch, never scans /Users, and preserves customer hooks. Cowork is not installed or removed.

4. Verify installation and protection

pkgutil --pkg-info dev.agentkeeper.runtime
sudo launchctl print system/com.agentkeeper.runtime
sudo "/Library/Application Support/AgentKeeper/helpers/agentkeeper-macos-status.sh"

Read the result in this order:

EvidenceMeaning
Package receipt and running LaunchDaemonInstalled only. This does not prove enrollment.
enrollment_state="enrolled", managed_profile_state="ready", hook_matrix.config_json=true, and credential_state="valid"The universal runtime enrolled successfully.
Expected hook coverage plus a fresh live Activity eventThe restarted agent is using the universal runtime.

hook_matrix.config_json=false with credential_state="missing" means enrollment did not finish. runtime_mode="degraded" or hook_matrix.runtime_hook_forwarder=false means protection is incomplete, even if enrollment succeeded. hook_matrix.runtime_hook=true alone only proves that the packaged runtime hook exists.

Before a regular user signs in, status can show a valid enrollment with targeting_mode="active_console_user", no active_console_user, and maintenance_state="waiting_for_user". That is a machine enrolled and waiting for a user session, not a request to add a username to the Jamf profile. After sign-in or a user switch, the next one-minute maintenance tick reconciles the active account automatically.

Fully quit and reopen one selected AI agent, run a harmless action, and confirm a fresh Workstation heartbeat and Activity event. Test a BLOCK only on a documented pre-execution surface.

Recovery and logs

The service checks profile and staged-token changes on its one-minute maintenance tick. Network enrollment failures retry with backoff up to one hour. If enrollment_state is managed_profile_missing, managed_profile_incomplete, or managed_profile_invalid, fix the computer profile. If it is managed_token_missing, rerun the Jamf policy's After script. To start an immediate attempt after correcting either item, run:

sudo "/Library/Application Support/AgentKeeper/helpers/agentkeeper-macos-remediate.sh"

The helper enrolls before it changes hooks. It returns nonzero instead of claiming repair when the Mac is not enrolled. If you must reinstall a Jamf policy configured Once per computer, reset that Mac's policy history or use a temporary one-device custom-trigger recovery policy.

enrollment.json is protected bootstrap retry state. It is not a runtime configuration file and cannot replace the per-device credential created by a successful enrollment exchange. Never copy or rename it to config.json. If someone already copied it, do not hand-edit the copy or create State/credential.json. The Mac remains installed_unhealthy, and hook reconciliation stays blocked, until the metadata profile and a fresh token staged through standard input complete enrollment. The successful exchange atomically writes the legitimate config and credential.

The relevant package log is install.log; runtime.log can be empty before enrollment. Collect only the safe state lines:

sudo grep -Eh 'Managed-profile enrollment|runtime maintenance state=|postinstall completed' \
  "/Library/Logs/AgentKeeper/install.log" \
  "/Library/Logs/AgentKeeper/runtime.err.log" 2>/dev/null

sudo "/Library/Application Support/AgentKeeper/bin/agentkeeper" support-bundle \
  --program-data "/Library/Application Support/AgentKeeper" \
  --install-dir "/Library/Application Support/AgentKeeper" \
  --redact

Do not paste the token, the token-bearing After script, enrollment.json, or unredacted logs into a ticket or chat. If a token is exposed, revoke it and create a replacement before continuing the deployment.

Migrating from an older Jamf script

After the universal runtime is enrolled and producing a live event, disable the recurring legacy Code/IDE installer and remove its API-key profile. Do not let both installers compete. Reconciliation replaces only AgentKeeper-owned IDE hook entries and preserves customer entries. Leave the separately assigned Cowork plugin installed; the universal PKG does not replace it.

See Jamf's guides for uploading a package, deploying a package with a policy, and running scripts with a policy. For AgentKeeper, continue with Validate deployment, Legacy script migration, and Uninstall and rollback.