Skip to main content
Agents collect permissions because widening a grant is easier than working out the narrow one, and nobody comes back to trim them. The audit log already knows what each agent used, so you can ask it. This tutorial gives one agent ten grants, sends it 498 calls, then reports which grants were never touched, proposes a smaller set, and checks the proposal against the recorded decisions. Nothing is changed for you. It runs in memory with no model and no API key. Terminal output of the policy shrink example listing used and unused permissions

Prerequisites

  • Node 20 or newer and TypeScript
  • A scratch project with "type": "module" in package.json
Everything below goes in one index.ts, copied from the policy shrink example. Audit rows only exist if decisions are recorded, so auditAll must be on.

Step 1: Create an over-permissioned agent

Step 2: Generate real decision rows

In production this step is just your agent running for a few weeks. Here we replay a fixed call log through authorize() so the rows are real.

Step 3: See what was used

In a real system, pass a since date to bound the window, for example 30 days.

Step 4: Propose a smaller set

minObservations: 5 means a grant needs at least five allowed calls to stay.

Step 5: Replay the decisions against the proposal

Run it with npx tsx index.ts.

What the output means

Real output from the example:
Five of ten grants were never used, including the dangerous ones: tool:billing *, tool:db:users delete and tool:mail send. The two denied delete calls on repos do not count as use, which is why mcp:github:repos write stays in the unused list. The part to read carefully is newly denied: 3. The agent called query on tool:db:users three times, below the threshold of five, so the proposal drops it. You see that cost before you apply anything. If those calls are legitimate, lower minObservations (the default is 1, which keeps every grant that was ever used).

Limits

  • It proves past behavior only. A quarterly job or an incident runbook may fall outside your window and get proposed for removal. Widen since for agents with long cycles.
  • Matching covers action and resource. Rate limits, time windows and approvals depend on live state and are not re-evaluated in the replay.
  • Evidence is bounded by your audit retention, and by whether auditAll was on.
  • Overlapping grants follow authorize() order, first match wins, so a broad grant listed first takes the credit.

Where to go next

Last modified on October 16, 2026