§05 · Governance & observability

Runtime policies

YAML rules that decide what an agent may run. Deny outright, hold for human approval, or watch first — evaluated at the control plane, at the bastion, and inside the agent runtime.

An agent that can open a shell can, in principle, run anything the shell can run. A runtime policy is where you say which of those things are off the table, which need a person to sign off, and which you only want to know about. One policy language, evaluated at several layers. Open Policies in the dashboard.

The Runtime Policies page listing policies with enforce and monitor badges
The Policies tab: each policy shows its mode, scope, and what it gates.

What a policy says

A policy is a YAML document under a runtime_policy: key. The primary enforced section is commands; a policy can also carry SQL, filesystem, network, and input/output rules.

policy.yaml· yaml
1runtime_policy:
2 version: "1.0"
3 name: "safe-shell-baseline"
4 default_decision: allow
5
6 commands:
7 global:
8 deny:
9 - pattern: "rm -rf *"
10 reason: "Destructive recursive delete is blocked."
11 - pattern: "sudo *"
12 reason: "Privilege escalation is blocked."
13 - pattern: "curl * | sh"
14 reason: "Remote script execution is blocked."
15 require_approval:
16 - pattern: "npm install *"
17 reason: "Dependency installation changes the environment."

Patterns are start-anchored globs: * matches within a path segment and never crosses /, ** matches anything including /, and ? matches one character. Every rule carries a one-line reason, which is what the agent and the audit record are shown when it fires.

Several policies can be attached to one agent. The most restrictive decision across all of them wins: deny beats require_approval beats redact beats allow. Setting default_decision: deny at the root or in a scope turns the policy into an allowlist.

Writing one

  • From a template. Nineteen built-in packs ship read-only: shell baselines, read-only guards for Postgres, MySQL, ClickHouse, MongoDB and Redis, Kubernetes read-only and no-delete, Helm and Terraform guards, Docker and git safety, cloud-CLI read-only, an exfiltration baseline, and a SOC 2 starter. Copy one and edit it. Your own templates sit alongside them.
  • From a description. Generate turns “block anything that drops a table, ask before deploying to prod” into policy YAML. It also converts a pasted prose or Markdown guardrail document: mechanically checkable rules become patterns, and behavioural guidance that cannot be expressed as a rule is dropped rather than invented.
  • By hand, in the YAML editor.

Who a policy applies to

ScopeApplies to
AttachedOnly the agents that list the policy. The default, and the most explicit.
GlobalEvery agent in the tenant, with no per-agent opt-in.
LocalAgents whose metadata labels contain every pair the policy names — e.g. every agent labelled env=prod.
UserNo agent at all: enforced on developer machines via exo sync.

Attach policies from an agent's create or edit form, or in YAML under spec.bootstrap.policies. Editing them needs the operator's map-policies capability.

agent.yaml· yaml
1metadata:
2 labels:
3 env: prod
4spec:
5 bootstrap:
6 policies:
7 - safe-shell-baseline
8 - prod-db-readonly

Enforce vs monitor

monitor evaluates everything and records the decisions without blocking anything. enforce blocks. Run a new policy in monitor mode against real traffic first — the decisions feed tells you exactly what would have been stopped, including the false positives you would rather find before your colleagues do.

Where it is enforced

  • The control plane is authoritative. Every exo tool exec and shell open is evaluated here before anything reaches the cluster.
  • The connector evaluates again in-cluster, immediately before the command runs. This catches anything reaching the manager without backend mediation, and it fails closed for enforce-mode policies when the backend is unreachable.
  • The runtime harness gates commands inside the agent itself — a pre-tool hook for Claude Code, native permissions where the runtime has them. It is advisory and fast: it gives the agent an immediate, explained refusal instead of a failure ten seconds later, while the layers above stay authoritative.

Approvals

The Approvals inbox with a pending command awaiting a decision
The Approvals inbox: the held command, who asked, and Approve / Deny.

A require_approval match holds the command instead of failing it. The request lands under Policies → Approvals with the tool, the exact command, the policy and rule that fired, and — when the request came from inside an agent pod — which agent raised it. Anyone with approval rights can approve or deny with a reason; pending requests expire on the tenant's approval TTL, ten minutes by default.

From the CLI the command blocks while it waits, printing the approval link as a scannable QR code. On approval the exec proceeds; on denial or timeout it aborts with the reason. Ctrl-C cancels the request rather than leaving it dangling.

Decisions

Policies → Decisions is the feed of every evaluation that matched: which enforcement point saw it, the policy and rule, the command, the tool, and the outcome — blocked, held, approved, denied, or monitored. One action can appear more than once, since each layer records its own view. This is the fastest way to answer “why did that fail” and the evidence trail when someone asks whether a control was on.

On your own machine

The same policies can guard the coding agent on your laptop. exo sync mirrors the tenant's skills and policies onto disk and wires each detected runtime to enforce them.

terminal· bash
exo sync
exo sync --runtime claude
exo sync --policies-only --dry-run
exo sync --local --prune