> For the complete documentation index, see [llms.txt](https://docs.keeper.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.keeper.io/keeperpam/endpoint-privilege-manager/deployment/deploy-with-macos/agentic-ai-policy-workload-reduction.md).

# Agentic AI Policy Workload Reduction

<figure><img src="https://762006384-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MJXOXEifAmpyvNVL1to%2Fuploads%2FBfqNPwMALisBMotziGAX%2Fimage.png?alt=media&amp;token=4715cc90-7146-473b-84b5-6548f1c31f9a" alt=""><figcaption></figcaption></figure>

Keeper Endpoint Privilege Manager (EPM) is billed by the number of workloads evaluated per endpoint. Every process launch, file access, elevation request, and background check that reaches the agent counts as a workload, so an endpoint running unusually noisy applications will consume disproportionately more workloads than one running the same policies against well-behaved software.

<figure><img src="https://762006384-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MJXOXEifAmpyvNVL1to%2Fuploads%2FBv7CWSjgFJXvLG7GaDYu%2Fimage.png?alt=media&amp;token=de5fd5a6-1573-4515-a6e5-f1ecb02ac4d1" alt=""><figcaption></figcaption></figure>

The most effective way to reduce workload consumption without weakening policy coverage is to quiet the applications themselves. Many common developer, productivity, and background tools poll the filesystem, refresh caches, or run periodic housekeeping far more aggressively than they need to; adjusting each application's own settings to reduce that unnecessary activity cuts workloads at the source, before they ever reach EPM for evaluation.

The recommendation below identifies a specific application with known noisy defaults and shows how to centrally deploy the settings changes that reduce its workload footprint across a managed fleet.

***

### Recommendation: Deploy Cursor Polling Settings via a `JobUpdate` Policy (macOS)

#### Symptom

macOS endpoints running the Cursor IDE (and, by extension, VS Code, which shares Cursor's Git and search machinery) produce a continuous stream of `git` and `rg` (ripgrep) process launches even when the user is idle. On a host running the EPM System Extension, every one of these process spawns is intercepted for policy evaluation, which:

* Drives up workload counts per endpoint (and therefore billed usage).
* Adds sustained CPU and disk load on the endpoint.
* Floods the audit and policy pipeline with low-value process-exec events.
* Under load, can contribute to `KEEPER_POLICY_EXEC_TIMEOUT` fail-open behavior.

#### Why It Happens

Cursor's defaults are optimized for interactive responsiveness in small repos, not for quiet operation on managed endpoints. Out of the box, Cursor:

* Runs Git auto-fetch and auto-refresh on a short interval (roughly once per second by default).
* Auto-detects repositories and recursively scans the workspace for `.git` directories.
* Uses `ripgrep` for global search and Quick Open, walking the entire workspace unless directories are excluded.
* Watches the entire workspace via filesystem watchers, including `node_modules`, build output directories, and `.git` internals.

Each `git`/`rg` invocation is a distinct process launch that the EPM System Extension must evaluate against policy. In large repositories or monorepos this activity is effectively continuous.

#### Fix: Push Quiet Settings to Every User via a `JobUpdate` Policy

Rather than asking every developer to hand-edit their own `settings.json`, deploy a single `JobUpdate` policy that installs a scheduled job on managed macOS endpoints. The job runs as the EPM service (root), enumerates every user home under `/Users`, and writes the recommended settings into each user's `~/Library/Application Support/Cursor/User/settings.json`.

The settings the job applies are:

json

```json
{
  "git.autofetch": false,
  "git.autorefresh": false,
  "git.autoRepositoryDetection": false,
  "git.repositoryScanMaxDepth": 0,
  "search.useIgnoreFiles": true,
  "search.useGlobalIgnoreFiles": true,
  "search.useParentIgnoreFiles": true,
  "search.followSymlinks": false,
  "search.exclude": {
    "**/node_modules": true,
    "**/dist": true,
    "**/build": true,
    "**/.git": true
  },
  "files.watcherExclude": {
    "**/node_modules/**": true,
    "**/.git/objects/**": true,
    "**/.git/subtree-cache/**": true,
    "**/dist/**": true,
    "**/build/**": true
  }
}
```

Effect on workload volume:

* **Git**: no auto-fetch, no auto-refresh, no auto repository detection, and repository scan depth is `0` — no recursive workspace scanning. This eliminates the steady one-per-second `git` invocation baseline.
* **Search (ripgrep)**: honors `.gitignore` files at every level, does not follow symlinks, and skips `node_modules`, `dist`, `build`, and `.git`. Global search and Quick Open no longer traverse these heavy directories.
* **File watcher**: excludes the same directories from the workspace watcher, preventing constant re-indexing when build tools or `git` write into them.

#### Policy JSON

Paste this into the Admin Console. The outer fields scope and trigger the installation; the inner `Extension.JobJson` is the job definition. The `ConfigurationPolicyProcessor` reads `Extension.JobJson` and saves it through the blessed `SaveJobToDiskAndLkg` path, which updates both disk and the Last Known Good copy — so the LKG watcher will not revert it.

json

```json
{
  "PolicyName": "Configure Cursor git/ripgrep polling (JobUpdate)",
  "PolicyType": "JobUpdate",
  "PolicyId": "configure-cursor-polling-policy",
  "Status": "enforce",
  "Actions": {
    "OnSuccess": { "Controls": [] },
    "OnFailure": { "Command": "" }
  },
  "NotificationMessage": "A policy has been set to monitor mode.  When this policy is enabled, [mfa, justification, request] will be required to run this process as an administrator.",
  "NotificationRequiresAcknowledge": false,
  "RiskLevel": 50,
  "Operator": "And",
  "Rules": [
    { "RuleName": "UserCheck",        "ErrorMessage": "This user is not included in this policy",        "RuleExpressionType": "BuiltInAction", "Expression": "CheckUser()" },
    { "RuleName": "MachineCheck",     "ErrorMessage": "This Machine is not included in this policy",     "RuleExpressionType": "BuiltInAction", "Expression": "CheckMachine()" },
    { "RuleName": "ApplicationCheck", "ErrorMessage": "This application is not included in this policy", "RuleExpressionType": "BuiltInAction", "Expression": "CheckFile(false)" },
    { "RuleName": "DateCheck",        "ErrorMessage": "Current date is not covered by this policy",      "RuleExpressionType": "BuiltInAction", "Expression": "CheckDate()" },
    { "RuleName": "TimeCheck",        "ErrorMessage": "Current time is not covered by this policy",      "RuleExpressionType": "BuiltInAction", "Expression": "CheckTime()" },
    { "RuleName": "DayCheck",         "ErrorMessage": "Today is not included in this policy",            "RuleExpressionType": "BuiltInAction", "Expression": "CheckDay()" },
    { "RuleName": "CertificateCheck", "ErrorMessage": "Certificate hash is not included in this policy", "RuleExpressionType": "BuiltInAction", "Expression": "CheckCertificate()" }
  ],
  "UserCheck": [ "*" ],
  "MachineCheck": [ "O_w7iACgy53mAwPlniSz4w" ],
  "ApplicationCheck": [ "*" ],
  "DayCheck": [],
  "DateCheck": [],
  "TimeCheck": [],
  "CertificationCheck": [],
  "Extension": {
    "JobId": "configure-cursor-polling",
    "Action": "Add",
    "JobJson": {
      "id": "configure-cursor-polling",
      "name": "Configure Cursor git/ripgrep polling",
      "description": "Writes recommended git/search settings into each user Cursor settings.json via bash (self-contained, no external script)",
      "enabled": true,
      "asUser": false,
      "priority": 5,
      "events": [
        { "eventType": "Custom", "customEvent": "PolicyPreprocessingCompleted" }
      ],
      "schedule": { "intervalMinutes": 30 },
      "parameters": [],
      "tasks": [
        {
          "id": "run-config",
          "name": "Apply Cursor polling settings",
          "command": "/bin/bash",
          "arguments": "-c \"for d in /Users/*/;do [ Shared = $(basename $d) ]&&continue;mkdir -p $d/Library/Application\\ Support/Cursor/User;echo '{\\\"git.autofetch\\\":false,\\\"git.autorefresh\\\":false,\\\"git.autoRepositoryDetection\\\":false,\\\"git.repositoryScanMaxDepth\\\":0,\\\"search.useIgnoreFiles\\\":true,\\\"search.useGlobalIgnoreFiles\\\":true,\\\"search.useParentIgnoreFiles\\\":true,\\\"search.followSymlinks\\\":false,\\\"search.exclude\\\":{\\\"**/node_modules\\\":true,\\\"**/dist\\\":true,\\\"**/build\\\":true,\\\"**/.git\\\":true},\\\"files.watcherExclude\\\":{\\\"**/node_modules/**\\\":true,\\\"**/.git/objects/**\\\":true,\\\"**/.git/subtree-cache/**\\\":true,\\\"**/dist/**\\\":true,\\\"**/build/**\\\":true}}' > $d/Library/Application\\ Support/Cursor/User/settings.json;done\"",
          "executionType": "Service",
          "expectedExitCode": 0,
          "timeoutSeconds": 60,
          "scriptType": "Auto"
        }
      ],
      "mqttTopics": {
        "allowedPublications": [ "KeeperLogger" ],
        "allowedSubscriptions": []
      },
      "osFilter": { "windows": false, "linux": false, "macOS": true }
    }
  }
}
```

Key fields to review before deploying:

* **`PolicyType: "JobUpdate"`** — marks this as a job-installation policy. The `ConfigurationPolicyProcessor` reads `Extension` and installs the job.
* **`Extension.Action: "Add"`** — installs or updates the job (`"Add"` upserts; use `"Remove"` to uninstall).
* **`Extension.JobId`** — must match `JobJson.id` (`configure-cursor-polling`).
* **`Status`** — set to `"enforce"` to activate; `"monitor"` only logs.
* **`MachineCheck`** — replace `O_w7iACgy53mAwPlniSz4w` with your target machine id(s), or use `["*"]` to apply to all machines.
* **`schedule.intervalMinutes: 30`** — production value. Test policies may use `1` for fast verification; revert to `30` (or higher) before rolling out broadly.
* **Trigger** — the job runs on both the `PolicyPreprocessingCompleted` custom event and the schedule interval, so newly-installed endpoints get their first apply as soon as policy preprocessing completes.

> **Do not** deploy by editing `/Library/Keeper/sbin/Jobs/configure-cursor-polling.json` directly. The `ConfigurationLkgReconciliation` filesystem watcher reverts manual edits (`JOB_WATCHER_RESTORE`). The Admin Console's `JobUpdate` policy is the only supported path.

#### User Coverage: Local, AD Mobile, and Network Homes

The `/Users/*/` glob covers every home directory mounted under `/Users`, which on macOS includes:

* **Local users** — e.g. `/Users/test`, `/Users/jsmith`.
* **Active Directory mobile accounts** — the AD plug-in creates mobile account homes under `/Users/<aduser>`. These are local cached homes, so writes are fast and reliable.
* **Network home directories** — NFS/AFP homes mounted under `/Users/<networkuser>` are matched by the glob; writes traverse the network to the home server.

The `Shared` directory is intentionally skipped. Because the job runs as root, file permissions on user homes do not block writes for any user type. Homes mounted outside `/Users` (e.g. `/Volumes/homes/<user>`) are **not** covered by the default glob — extend the glob if your environment uses non-standard home roots.

#### Escaping Pitfall

The task inlines a bash script via `/bin/bash -c "<script>"`. The `Application Support` path contains a space, which must survive three layers of parsing: JSON, .NET's `Process.Start` argument tokenizer, and bash. The correct JSON uses **two backslashes** for each `Application Support` occurrence:

```
...mkdir -p $d/Library/Application\\ Support/Cursor/User; ... > $d/Library/Application\\ Support/Cursor/User/settings.json;...
```

* `Application\\ Support` (2 backslashes in JSON) → 1 backslash in the args string → bash treats the space as escaped → one word → works.
* `Application\\\\ Support` (4 backslashes) → 2 backslashes in the args string → bash interprets `\\` as an escaped backslash, leaving the space unescaped → word-split → the `echo >` redirect targets a directory → `Is a directory` → exit 1.

Both occurrences (the `mkdir` path and the `echo >` redirect path) must use two backslashes.

#### Verification

* A **successful** run leaves a fresh `~/Library/Application Support/Cursor/User/settings.json` (root-owned, \~477 bytes) in every non-`Shared` user home. `TASK_COMPLETE` and `JOB_EXECUTION_COMPLETE` for a success are logged at `Debug`/`Info` and are filtered out when `system.logging.level` is `Warning`.
* A **failed** run produces `[WRN] [JobExecutor] [JOB_STOPPED] ... Task 'run-config' failed` and `[WRN] [JobService] [JOB_EXECUTION_COMPLETE] ... Success: False` at Warning level — these are visible by default.
* To see the explicit `ExitCode=0` line, temporarily set `system.logging.level` to `Debug` in `/Library/Keeper/sbin/appsettings.json` and restart the agent (`keepersudo launchctl kickstart -k system/com.keeper.endpoint-privilege-manager.launcher`), then revert to `Warning` after verifying.

#### Caveats

* **Overwrites `settings.json` entirely.** The task uses `echo > settings.json`, which replaces the whole file. Any user customizations in Cursor settings are lost on each run. A merge-based approach (read existing JSON, merge the polling keys, write back) would preserve user customizations and is a planned enhancement.
* **Idempotency.** The job is idempotent for the polling settings (re-writing the same JSON), but destructive to any other keys the user has set (see above).
* **Network home performance.** Writes to NFS/AFP homes traverse the network; on slow links the task may approach the 60-second timeout. Consider narrowing the glob to exclude slow or unreachable network home roots if timeouts occur.
* **Non-`/Users` home roots.** Not covered by default; extend the glob (e.g. `/Users/*/ /Volumes/homes/*/`) if your environment uses custom home paths.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.keeper.io/keeperpam/endpoint-privilege-manager/deployment/deploy-with-macos/agentic-ai-policy-workload-reduction.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
