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:
- Open Computers > Configuration Profiles > New.
- Set the profile level to Computer Level.
- Open Application & Custom Settings > Upload and use preference domain
com.agentkeeper.runtime. - 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
- Open Settings > Computer management > Packages > New.
- Upload
agentkeeper-runtime-macos-latest.pkgand record its version and SHA-256 in the package notes. - Open Computers > Policies > New.
- Choose the required trigger and set frequency to Once per computer.
- Under Packages, add the AgentKeeper package with action Install.
- 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$4through$11, because parameters are process arguments on the Mac. - Add the script to the policy and set its priority to After so the runtime binary exists before token staging starts.
- 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:
| Evidence | Meaning |
|---|---|
| Package receipt and running LaunchDaemon | Installed 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 event | The 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.