Technical Reference · MDM installations
MDM installations
Deploy the device agent through your MDM, verify the rollout with agent coverage, and export it as compliance evidence, with guides for Jamf Pro, Iru (formerly Kandji), Intune, ManageEngine Endpoint Central, Linux, Drata, and Vanta.
Rolling the device agent out through an MDM starts with deployment: your MDM installs the agent and delivers its managed configuration so every device enrolls without an interactive login. Supported inventory integrations add verification by pulling the managed-device inventory into the dashboard and showing which devices are covered by a running agent. Deployment-only providers require device-level validation with speakeasy status or a separate supported inventory source. You can optionally export coverage from a supported inventory integration into your compliance platform as continuously tested evidence.
This page covers the shared concepts. The provider guides cover the console-specific deployment recipes. Guides for supported inventory integrations also explain how to create scope-limited credentials:
Inventory sources (your MDM: deploy the agent, pull the fleet):
- Jamf Pro: deploy and verify on macOS (inventory integration supported)
- Iru (formerly Kandji): deploy and verify on macOS (inventory integration supported)
- Microsoft Intune: deploy and verify on macOS and Windows (inventory integration supported)
Deployment-only providers (deploy the agent without an inventory integration):
- ManageEngine Endpoint Central: deploy on macOS and Windows
Jamf Pro and Iru manage Apple devices only. For the platforms they don’t cover:
- Linux: deploy through Ansible, Puppet, or any configuration management (no inventory integration)
Evidence destinations (your compliance platform: push coverage as evidence):
- Drata: per-device records into a dedicated Custom Connection, mapped to a control test
- Vanta: per-device Custom Resources with a Custom Test over real coverage
Install the agent
Section titled “Install the agent”The agent ships as a single Developer ID signed, notarized, universal .pkg that installs the daemon, CLI, menu-bar UI, and privileged helper together and registers its own LaunchAgents. No script, no separate service-install step. Download it from a stable link that always resolves to the current release: https://app.getgram.ai/v1/install/device-agent-macos.pkg.
Upload that file to your MDM as a Package (Jamf), Custom App (Iru), or line-of-business .pkg app (Intune) (see the provider guides below for the exact steps) and push it once. With auto_update: "automatic" set in the managed configuration below, the agent keeps the daemon, CLI, and app current on its own; re-push the pkg only for a change to the install layout itself, not routine releases.
Windows
Section titled “Windows”The agent ships a per-machine, Authenticode-signed .msi (speakeasy-agent_<version>.msi) that installs the daemon, CLI, and UI under C:\Program Files\Speakeasy\ and registers the daemon as a LocalSystem service. Download it from the release bucket at https://storage.googleapis.com/speakeasy-device-agent-releases-prod/v<version>/speakeasy-agent_<version>.msi and push it as a Win32 app or line-of-business MSI. Raw .zip binaries remain available for scripted or air-gapped installs, but they are unsigned. Prefer the MSI.
Windows fleets can use MDM-controlled or agent-controlled updates. Use auto_update: "notify" when the MDM owns version rollouts, or auto_update: "automatic" when the agent should verify and install signed MSI updates. Do not schedule both update paths at the same time. See Microsoft Intune for a full MDM-controlled recipe, including the detection rule, the identity-file dependency, and the service-restart requirement on token rotation.
There is no installer package for the Linux daemon. Install by downloading the speakeasyd daemon and speakeasy CLI from the release bucket, then registering the systemd unit:
VERSION=$(curl -s https://storage.googleapis.com/speakeasy-device-agent-releases-prod/releases.json | jq -r '.latest.speakeasyd.version')BASE=https://storage.googleapis.com/speakeasy-device-agent-releases-prod/v$VERSIONARCH=amd64 # or arm64
sudo curl -fSL -o /usr/local/bin/speakeasyd "$BASE/speakeasyd_${VERSION}_linux_${ARCH}"sudo curl -fSL -o /usr/local/bin/speakeasy "$BASE/speakeasy_${VERSION}_linux_${ARCH}"sudo chmod 0755 /usr/local/bin/speakeasyd /usr/local/bin/speakeasy
speakeasyd -service install # as the user, not rootspeakeasyd -service startspeakeasy statusThe Linux daemon registers a per-user systemd unit (~/.config/systemd/user/com.speakeasy.daemon.service), so -service install must run in each user’s own session. Running it under sudo installs the unit for root, which has no user identity to attribute sessions to. On headless machines, sudo loginctl enable-linger <user> is also required, or the user unit never starts.
The Linux guide covers the full config-management rollout, including the root helper package that unlocks managed-layer enforcement.
For a fleet, wrap these steps in the mechanism your configuration management or MDM provides rather than running them by hand on every machine. For a single device, the same commands work from an interactive shell, or use the guided walkthrough on the Device Agent page in the dashboard, which fills in the current version and lets you download the binaries directly instead of scripting it.
Once the daemon is running, set the device’s identity the same way as macOS: deploy the managed configuration below for a fleet, or run speakeasy enroll for a one-off or personal install.
The managed configuration
Section titled “The managed configuration”The agent recognizes a user through a managed configuration delivered by IT. No browser flow and no manual enrollment on the device. It’s a schema-versioned document (v: 1) carrying the user’s work email, an organization token, and optional settings. It can be delivered through two channels, each with its own on-disk form:
<?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>v</key><integer>1</integer><key>email</key><string>jane.doe@example.com</string><key>org_token</key><string>spk_org_…</string><key>org_slug</key><string>example-corp</string></dict></plist>-
Configuration Profile (macOS, preferred): a Custom Settings payload (
com.apple.ManagedClient.preferences) targeting the preferences domaincom.speakeasy.agent, materialized by the OS at/Library/Managed Preferences/com.speakeasy.agent.plist. Declarative, MDM-native, no standing script, and users cannot edit it without removing the profile. When a device has both a profile and the JSON file, the profile wins per field. -
Script-dropped
managed.json(works everywhere): a script or package payload writes the JSON file to a fixed system path:OS Path macOS /Library/Application Support/Speakeasy/managed.jsonLinux /etc/speakeasy/managed.jsonWindows %ProgramData%\Speakeasy\managed.json
The configuration must be readable by the logged-in user, not just root: the agent runs as that user. For the JSON file use 0644 root:wheel (or 0640 with the user’s group); 0600 silently breaks enrollment. Profiles have no permission trap. The OS owns the plist.
The org_token is a credential, but the device cannot keep it secret from its own user: the agent runs as the logged-in user, who can therefore read the configuration. Treat the token as a revocable, organization-scoped credential: scope its permissions on that assumption and rotate it rather than trying to hide it. Pass it into your MDM as a script parameter or templated variable, never as a literal in a script body.
Verify on the device
Section titled “Verify on the device”speakeasy status on a target device should report the enrolled email with source: managed (surfaced in the menu bar UI as “Provisioned by IT”). If it reports source: local or source: none, the usual causes are the file permissions being too tight, the file sitting at the wrong path, or the daemon not having restarted since the configuration was written (see Updating the managed configuration below).
Updating the managed configuration
Section titled “Updating the managed configuration”The agent reads its configuration at daemon startup, not continuously, so a change to the file or profile doesn’t take effect until the daemon restarts. Any policy that writes the file must restart the daemon afterwards, on every run: a rotated token sits inert on disk until the next restart. The same restart rule applies to profile updates. On a pkg-installed macOS device, restart the console user’s LaunchAgent (launchctl kickstart -k "gui/$(id -u)/com.speakeasy.daemon"), substituting the console user’s uid when run from a root MDM policy. Windows service installs use speakeasyd -service restart (or sc stop/sc start). Linux uses the same command, but it must run in the user’s own session, since the unit is per-user.
Verify the rollout with agent coverage
Section titled “Verify the rollout with agent coverage”Deploying tells you what your MDM pushed; it doesn’t tell you what’s actually running. MDM Integrations closes that gap: the platform pulls the managed-device inventory from your MDM on a schedule and joins it against the agent’s own heartbeats, so the dashboard can show, per managed device, whether its assigned user has a live agent. Jamf Pro, Iru (formerly Kandji), and Microsoft Intune are supported; see the per-provider guides for setup.
Agent presence is attested per assigned user, not per device. The agent reports a heartbeat for the enrolled user roughly every minute; a device counts as covered when its MDM-assigned user has a recent heartbeat from some device. It proves “this device’s user runs the agent,” not “this specific device is monitored.” Device-level attestation via hardware-serial reconciliation is planned.
Each managed device lands in exactly one coverage state:
| State | Meaning |
|---|---|
| Agent active | The assigned user’s device agent reported a heartbeat within the active window. |
| Agent stale | The assigned user’s agent has gone quiet while the MDM still sees the device checking in (the drift case). The agent may have been disabled or removed. |
| No agent | The assigned user has never reported an agent heartbeat. |
| No email in MDM | The MDM record has no assigned-user email, so coverage cannot be attested. Set the user’s email on the device record in your MDM. |
| Unknown user | The MDM-assigned email doesn’t match any member of the organization. |
| Missing from MDM | The device stopped appearing in the MDM inventory, likely unenrolled or retired. |
The summary also counts agent users without a managed device: people reporting agent heartbeats whose email matches no device in the MDM inventory (the inverse gap, useful for spotting agents running on unmanaged hardware).
The two lists an IT admin works during a rollout are No agent (deploy the agent to these users’ devices) and Agent stale (the agent was running and stopped; investigate whether it was disabled).
Export coverage as compliance evidence
Section titled “Export coverage as compliance evidence”With an inventory source connected, an evidence destination (Drata or Vanta) turns the same coverage into audit-ready evidence. All destinations share the same semantics:
- One record per present managed device, carrying the serial, hostname, assigned-user email, and the user’s agent status. Devices missing from the MDM are excluded until they reappear.
- Snapshot replacement on a schedule. Every push is the complete current state: departed devices drop out automatically, retries can never duplicate records, and cycles where nothing changed are skipped.
- Attestation travels with every record. Each pushed device carries
agent_activealongside anagent_attestationof"device"(that machine’s own agent reported in, matched on hardware serial) or"user"(only its assigned user did, matched on email, strictly weaker). Both can appear in the same push, because a machine whose agent cannot read a serial stays user-attested. Phrase your control tests over these properties so auditors consume exactly the claim the platform can support; where per-device attestation is required, testagent_attestationtoo rather thanagent_activealone.
Every integration (sources and destinations) connects the same way: Device Agent → MDM Integrations in the dashboard, then save the credentials, test the connection, and enable it. New connections start paused so invalid credentials never generate failed sync attempts, and each provider guide covers minting a scope-limited credential so the stored secret can do nothing beyond its one job.