Govern
Documentation

Version Latest

Agent policy

Write allow, ask and deny rules for what coding agents do, set defaults and delegation, apply sector templates, preview the policy per engine, approve hooks, revoke people, and read the decision log.

Agent policy decides what coding agents may do on a developer's machine or in a sandbox: which commands they run, which files they read and write, where they connect, what they push and install, which hooks run, and which MCP tools and models they use. You write one policy in Govern. Govern compiles it for each agent engine and signs it; the endpoint agent (keystone-agent) enforces it locally, and the model and MCP gateways enforce the model and tool rules on the server.

The console service is Agent policy, with the pages Agent policy, Policy preview, Coding tool control, Sector templates, Hooks, Agent approvals and Agent decisions. Organisation routes are under /api/v1/orgs/{orgID}/agent-policy.

Permissions

PermissionAllows
agentpolicy:readRead settings, rules, templates, the preview, hooks, the decision log and revocations
agentpolicy:writeChange settings (including suspend), create, update and delete rules, apply templates, submit hooks, revoke people and clear revocations
agentpolicy:approveDecide runtime approval requests; approve, reject and revoke hooks

The people who write rules need not be the people who approve actions or hooks. Deciding a runtime approval also needs eligibility in the shared approvals service (approvals:approve or membership of the request's approver group), so give approvers a role with both agentpolicy:approve and approvals:approve.

1. Configure signing (operator)

Govern signs every policy manifest with Ed25519, and the endpoint agent refuses a manifest whose signature does not verify. Without a key, the manifest routes answer 503 signing_unavailable: an unsigned policy is never served. The preview works without a key.

openssl genpkey -algorithm ed25519 -out /etc/keystone/agentpolicy-signing.pem
chmod 600 /etc/keystone/agentpolicy-signing.pem
export NI_AUTH_AGENTPOLICY_SIGNING_KEY_FILE=/etc/keystone/agentpolicy-signing.pem
export NI_AUTH_AGENTPOLICY_SIGNING_KEY_ID=ap-2026-10        # optional; derived from the key otherwise
VariablePurpose
NI_AUTH_AGENTPOLICY_SIGNING_KEY_FILE or NI_AUTH_AGENTPOLICY_SIGNING_KEY_PEMActive Ed25519 private key (PKCS#8 PEM). _PEM wins when both are set.
NI_AUTH_AGENTPOLICY_SIGNING_KEY_IDKey id; without it, ap- and 16 hex characters of the public key's SHA-256
NI_AUTH_AGENTPOLICY_SIGNING_KEY_NOT_BEFORERFC 3339 start of the key's validity
NI_AUTH_AGENTPOLICY_RETIRED_KEYS_FILEJSON key set of rotated-out public keys, each with not_after
KEYSTONE_AGENTPOLICY_AGENT_COMMANDThe endpoint agent executable the compiled settings call (default keystone-agent)

The public keys are served at GET /.well-known/neuralinverse-agentpolicy-keys.json. To rotate, move the old public key into the retired keys file with not_after set to the rotation time, set the new key with NOT_BEFORE at the same time, and restart. An invalid key or setting stops the server at startup.

2. Set defaults and delegation

For actions no rule matches, the organisation sets a default effect per kind. Only the organisation sets defaults, and it decides per kind whether teams (or teams and projects) may loosen a rule (delegation: none, teams or teams_and_projects). Without delegation, a team or project rule can only tighten.

KindDefault without a setting
shell, file_write, network, git_push, package_install, mcp_toolask
file_read, hook, modelallow
git_force_pushdeny

The hook default is allow or deny, never ask, because hooks run unattended.

curl -sS -X PUT "$KEYSTONE_URL/api/v1/orgs/$ORG_ID/agent-policy/settings" \
  -H "Authorization: Bearer $ORG_ADMIN_TOKEN" -H 'Content-Type: application/json' \
  -d '{"offline_grace_seconds": 14400, "approval_ttl_seconds": 900,
       "kinds": {"shell": {"default": "ask", "delegation": "teams"},
                 "network": {"default": "deny", "delegation": "none"}}}'
SettingDefaultRangeMeaning
offline_grace_seconds864000 to 2592000How long a manifest stays valid without reaching Govern
approval_ttl_seconds90060 to 86400Lifetime of a pending approval request and of a once grant
session_grant_max_seconds28800300 to 604800Lifetime of a session grant
decision_retention_days907 to 400How long the decision log keeps a decision
suspended, suspended_reasonfalsereason at most 500 charactersRevokes every manifest of the organisation

Only the fields you send change. Every change that alters a manifest increments the organisation's revision; the endpoint agent fetches a fresh manifest every 300 seconds while online.

3. Write rules

A rule is a scope, a kind, a pattern and an effect (allow, ask or deny).

  • Scope: org, team (scope_id is a team) or project (scope_id is a repository identifier such as github.com/acme/fw, normalised to lower case without a trailing .git or /).
  • Locked (org and team rules): lower levels may not override the same target, even when the kind is delegated.
  • Approval (ask rules): user (default), the developer confirms in the tool; approvers, a named approver must grant the action in Govern. approvers lists up to 20 user:<uuid> or team:<uuid> entries; an empty list means any member with agentpolicy:approve other than the requester.

Action kinds

KindPattern matchesExample
shellOne command line, decided per simple commandnpm run *, rm -rf /*
file_readA path an agent tool reads**/.env, ~/.ssh/**
file_writeA path created, edited or deleted**/*.ld, /etc/**
networkA destination host, *.domain or an IP address, never a URL*.internal.example, registry.npmjs.org
git_pushA push to a branchmain, release/*
git_force_pushA force push to a branch*
package_installInstalling a package, as ecosystem:namenpm:*, pypi:requests
hookRunning an organisation-approved hook, by nameformat-*
mcp_toolAn MCP tool call, as server/tooljira/*, github/create_pull_request
modelUsing a model, by model idclaude-opus-*

Patterns are at most 512 characters. Paths in a home directory are written ~/...; /home/<user>/... and /Users/<user>/... are refused. The :* suffix of older Claude Code rules is refused; write npm run *. A rule that names an MCP server or a model without a wildcard must name one that exists in the organisation's MCP catalog or model catalog.

Examples

# Organisation: never read .env files, and lock it.
curl -sS -X POST "$KEYSTONE_URL/api/v1/orgs/$ORG_ID/agent-policy/rules" \
  -H "Authorization: Bearer $ORG_ADMIN_TOKEN" -H 'Content-Type: application/json' \
  -d '{"scope": "org", "kind": "file_read", "pattern": "**/.env", "effect": "deny", "locked": true,
       "description": "Credentials stay out of agent context."}'

# Team: pushes to main need a named approver.
curl -sS -X POST "$KEYSTONE_URL/api/v1/orgs/$ORG_ID/agent-policy/rules" \
  -H "Authorization: Bearer $ORG_ADMIN_TOKEN" -H 'Content-Type: application/json' \
  -d '{"scope": "team", "scope_id": "'$TEAM_ID'", "kind": "git_push", "pattern": "main",
       "effect": "ask", "approval": "approvers", "approvers": ["team:'$LEADS_TEAM_ID'"]}'

# Project: allow npm scripts in one repository (needs shell delegated to teams_and_projects).
curl -sS -X POST "$KEYSTONE_URL/api/v1/orgs/$ORG_ID/agent-policy/rules" \
  -H "Authorization: Bearer $ORG_ADMIN_TOKEN" -H 'Content-Type: application/json' \
  -d '{"scope": "project", "scope_id": "github.com/acme/fw", "kind": "shell",
       "pattern": "npm run *", "effect": "allow"}'

Govern refuses (400) a team or project rule that could never take effect: one against a locked org rule, a looser effect on a kind not delegated to that level, or the same effect as the org rule. One target exists at most once per scope (409). To change a rule, send PUT .../rules/{ruleID} with the full rule and the version you read; a stale version answers 409.

How a decision is made

Every effective rule whose pattern matches counts, and the strictest effect wins (deny, then ask, then allow) regardless of how specific its pattern is: an allow git_push feature/* does not open main if deny git_push * exists. If no rule matches, the organisation default applies. The decision reports the rule that decided it.

Shell command lines are split into simple commands across ;, &&, ||, pipes, substitutions, sh -c, eval and similar, and the strictest command decides. Privilege wrappers and environment prefixes are stripped before deny and ask rules are checked, so deny dd * also catches sudo dd, /bin/dd and FOO=1 dd. A line that chains commands, substitutes output, redirects to a file, runs a nested script or uses sudo needs at least the developer's confirmation, even when every command in it is allowed. A line that cannot be parsed is refused.

Text rules over shell commands reduce what an agent can reach; they are not an isolation boundary. A program a rule allows can do anything it is able to do.

4. Apply a sector template

Templates add a starting set of rules, each with its rationale in description. Applying one adds the rules whose target is not already present at the scope and, at organisation scope, sets defaults for kinds you have not configured. It never removes or loosens anything, and applying it twice changes nothing. Template rules that ask named approvers have none named; edit them to name yours.

curl -sS -X POST "$KEYSTONE_URL/api/v1/orgs/$ORG_ID/agent-policy/templates/medical/apply" \
  -H "Authorization: Bearer $ORG_ADMIN_TOKEN" -H 'Content-Type: application/json' \
  -d '{"scope": "org"}'
TemplateWritten for
firmwareEmbedded C and C++ on microcontrollers: linker scripts, startup code and device programming stay under human control
hdl-fpgaHDL projects on FPGA toolchains: programming a device, writing bitstreams and changing timing and pin constraints need a person
plc-otPLC and industrial control projects: agents never download logic to a controller or reach plant networks; safety logic needs a named approver
automotiveVehicle software: changes to safety-relevant components and calibration data need a named approver
avionicsAirborne software: requirements, trace data and qualified-tool configuration are controlled items that need a named approver
medicalMedical device software: agent reads of patient data are denied; changes to released and controlled items need a named approver
financeFinancial services code: payment, ledger and infrastructure changes need a named approver; agents never read cardholder data or production credentials
mainframeMainframe development: agents may edit COBOL, PL/I and JCL in the repository, but submitting jobs and changing host datasets need a person

The answer lists rules it skipped and why (skipped) and defaults it left because you set them (defaults_skipped). Templates are a starting point; review every rule against your own process before you rely on it.

5. Preview before you roll out

curl -sS "$KEYSTONE_URL/api/v1/orgs/$ORG_ID/agent-policy/preview?user_id=$USER_ID&project=github.com/acme/fw&engine=codex" \
  -H "Authorization: Bearer $ORG_ADMIN_TOKEN"

The preview (console Policy preview) shows exactly what one person gets in one project on one engine, without signing. user_id defaults to you, engine to claude-code.

PartShows
resolved.rulesThe effective rules with their source
resolved.ineffectiveStored rules that do not apply to this person and project, with the reason
compiled.filesThe files the endpoint agent installs for the engine
compiled.gapsEvery rule the engine cannot express exactly: unsupported (the engine does not enforce it as written), approximated (the engine enforces something stricter) or uncovered (no hook of the tool sees the action)

Gaps are enforced by the endpoint agent through the enforcement hook, but only where the endpoint agent runs. Check the gaps for each engine before you rely on a rule; Third-party AI coding tools lists what each tool covers.

6. Review hooks

Only hooks your organisation approved run, and only through the endpoint agent, which checks the script's SHA-256 against the approved digest before running it. The compiled settings stop the engines from running any other hook. Approved hooks are delivered to Claude Code and Codex, not to the vendor tools that only run Govern's enforcement hook.

curl -sS -X POST "$KEYSTONE_URL/api/v1/orgs/$ORG_ID/agent-policy/hooks" \
  -H "Authorization: Bearer $ORG_ADMIN_TOKEN" -H 'Content-Type: application/json' \
  -d @- <<'EOF'
{"name": "format-on-edit", "event": "PostToolUse", "matcher": "Edit|Write",
 "engines": ["claude-code", "codex"], "timeout_seconds": 30,
 "script": "#!/bin/sh\nexec make fmt\n"}
EOF
  • Events: PreToolUse, PostToolUse, UserPromptSubmit, SessionStart, SessionEnd, Stop, SubagentStop, PreCompact and Notification (Codex does not run Notification).
  • The script is at most 64 KiB and starts with a #! line; the timeout is 1 to 600 seconds.
  • A submitted hook is pending. A member with agentpolicy:approve other than the submitter approves or rejects it with POST .../hooks/{hookID}/review and {"decision": "approve", "comment": "..."} (or reject). Submit hooks as a person: a hook submitted by an API key not bound to a person cannot be reviewed.
  • POST .../hooks/{hookID}/revoke stops an approved hook. A hook rule can deny an approved hook for a team or project.

7. Revoke a person or suspend the organisation

curl -sS -X PUT "$KEYSTONE_URL/api/v1/orgs/$ORG_ID/agent-policy/revocations/$USER_ID" \
  -H "Authorization: Bearer $ORG_ADMIN_TOKEN" -H 'Content-Type: application/json' \
  -d '{"reason": "Laptop reported stolen"}'

A revoked person's manifests deny every action on every engine and project. DELETE .../revocations/{userID} lets their agents act again. To stop every agent of the organisation, set {"suspended": true, "suspended_reason": "..."} in the settings. A revocation or suspension reaches an online agent with its next manifest fetch (within 300 seconds); an offline agent keeps its last manifest until it expires, so keep offline_grace_seconds short where fast revocation matters.

Delivery to machines

  • The endpoint agent fetches the compiled policy for its engine as an Ed25519-signed manifest, verifies it, and refuses a manifest older than the one it holds, so a replayed manifest cannot restore a looser policy.
  • Offline, it uses the last verified manifest until it expires. After that, every action is denied until a fresh manifest verifies.
  • A revoked manifest denies every action, online or offline.
  • Every action goes through keystone-agent policy enforce, which answers before the engine's own hook timeout and denies on any internal error.

Decision log

The endpoint agent reports every decision: time, person, project, engine, session, kind, effect, outcome (allowed, denied, asked, approved, rejected or error), the deciding rule, the enforcer and the approval. Command text, paths and hosts are never stored; the target is kept only as a SHA-256 hash.

curl -sS "$KEYSTONE_URL/api/v1/orgs/$ORG_ID/agent-policy/decisions?outcome=denied&from=2026-10-01T00:00:00Z" \
  -H "Authorization: Bearer $ORG_ADMIN_TOKEN"

Filter by user_id, project, engine, kind, effect, outcome, from and to. The console page is Agent decisions.

Policy change control

  • Every change is audited in its own transaction: agentpolicy.settings.updated, agentpolicy.rule.created, .updated, .deleted, agentpolicy.template.applied, agentpolicy.hook.submitted, .approved, .rejected, .revoked, agentpolicy.approval.requested, .approved, .denied, .consumed, .expired, agentpolicy.revocation.set and .cleared. If the event cannot be written, the change fails. Audit metadata holds identifiers, kinds and effects, never scripts or approval targets.
  • Separate permissions for writing rules and approving actions or hooks.
  • Preview every change per engine before it reaches people.
  • Optimistic concurrency on rules: an update must carry the version it read.

API

RoutePermissionPurpose
GET, PUT /settingsread, writeGrace, lifetimes, retention, suspend, per-kind default and delegation
GET, POST /rules; GET, PUT, DELETE /rules/{id}read, writeRules
GET /templates, GET /templates/{id}, POST /templates/{id}/applyread, writeSector templates
GET /preview?engine=&user_id=&project=readEffective policy and compiled output
GET, POST /hooks, GET /hooks/{id}read, writeHooks
POST /hooks/{id}/review, /revokeapproveReview and revoke hooks
GET /approvals, GET /approvals/{id}, POST /approvals/{id}/decisionapproveApprovals; see Approvals
GET /decisionsreadDecision log
GET /coding-toolsreadVerified control level of each coding tool
GET /revocations, PUT, DELETE /revocations/{userID}read, writePer-person revocation

Paths are relative to /api/v1/orgs/{orgID}/agent-policy. Lists take limit (1 to 200, default 50) and cursor. Errors are {"error": "<message>", "code": "<code>"}. On the hosted service, ask rules with named approvers and custom hooks depend on the plan (403 not_entitled); a self-hosted installation includes everything.

Last reviewed 17 October 2026View as Markdown

Agent policy · Docs · Neural Inverse