Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Policy

Definition

A policy is a declarative document — written in YAML — that states what agents are and are not allowed to do. It is not a rule list. A document is a set of independent sections, each governing one dimension of behaviour, and the gateway evaluates them in a fixed order to reach one of three outcomes: allow, deny, or requires approval. Policy is evaluated server-side, in the gateway — never by the agent or the dashboard — so the workload it governs cannot tamper with the decision.

How it works

A document may be written in either of two accepted shapes. The envelope form carries apiVersion, kind, metadata, and a spec holding the sections; this is what every file under policy-examples/ uses. The bare form puts the same sections at the top level with no envelope. The validator tries the envelope first and, when a spec key is present, parses its inner value; otherwise it reads the document flat. Both forms are equally valid.

Inside spec (or at the top level, in the bare form) the accepted keys are exactly:

keygoverns
versiondocument version tag
scopewhich agents this document applies to
networkegress domain allowlist
scheduleactive_hours — start, end, timezone
budgetper-agent / per-team / per-org daily and monthly USD caps, action_on_exceed
datasensitive_patterns, credential_action, locale_packs
toolsper-tool allow, limit_per_hour, requires_approval_if, capabilities
capabilitiescapability-string allow and deny lists
filesystemread and write path-prefix allowlists
syscallskernel syscall allow list
approval_timeout_secshow long a held action waits (default 300)
approvaltimeout_seconds and escalation_role overrides

There is no tier key and no rules key, in either form. The validator fails closed on anything it does not recognise: an unknown top-level key is a hard error naming the valid set, and a top-level rules: gets its own dedicated refusal —

unsupported rule-list policy schema (top-level 'rules:'); the gateway uses a
section-based schema (network/schedule/budget/data/tools/capabilities) and
refuses to load this document to avoid an allow-all fallback

That message is deliberate. A rule-list document that merely failed to match anything would load cleanly and permit everything, so the gateway refuses to load it at all rather than degrade to an allow-all. The same fail-closed rule applies inside each section: a typo in a nested key is rejected, never ignored, so a misspelling cannot silently drop a restriction.

Declaring the authority a tool needs

A tool entry may state the authority its calls need with capabilities:

tools:
  web_search: { allow: true, capabilities: [network_egress, name_resolution] }
  write_note: { allow: true, capabilities: [filesystem_write] }
  calculator: { allow: true, capabilities: [none] }   # purely logical, needs no lease

The words are the capability-lease domains: filesystem_read, filesystem_write, network_egress, name_resolution, syscall, process_creation, ipc and credential, plus none for a tool that needs no lease. none must stand alone. The list must be non-empty and free of duplicates. resource and workspace_transaction are not accepted: they are launch-boundary authority, not something one call exercises. A declaration is matched by exact tool name, so the "*" tool entry cannot carry one. process_creation and syscall are accepted when you write the policy; the governed runtime decision refuses a tool that declares them.

Leaving capabilities out means the tool is undeclared, which is not the same as [none]. A declaration states what a tool needs and grants nothing: leases are still authored separately under authority.leases, and none is ever derived from a declaration.

What this will be used for is an admission lease check on a cooperative SDK TOOL_CALL. It is not confinement: it does not stop a process that bypasses the SDK or misreports what it is doing, and a lease scoped to selectors (paths or destinations) never admits a tool, only a whole-scope lease in the declared domain does. It is read from trusted operator policy only. As of this change the field is validated and carried through policy resolution, but no runtime enforces it yet; see ADR 0038.

Policies are scoped and they cascade. A document’s scope field attaches it to one of five levels — global, org:<id>, team:<id>, agent:<uuid>, or tool:<name>. When an action is evaluated the engine walks those scopes in the order Global → Org → Team → Agent → Tool and merges them with a most-restrictive-wins rule. A broad organizational deny therefore cannot be loosened by a narrower team- or agent-level allow. Tool sits at the most-restrictive end so a policy can deny one MCP server for every agent in a team even when team-level rules would otherwise permit it.

flowchart LR
    A[Action] --> G[Global]
    G --> O[Org]
    O --> T[Team]
    T --> Ag[Agent]
    Ag --> Tl[Tool]
    Tl --> D{most-restrictive<br/>wins}
    D --> R[allow / deny / require_approval]

Budget participates in the decision, through two distinct mechanisms that are worth keeping apart. The first is reactive and applies to every governed action: once an agent’s monthly cap — or, failing that, its daily cap — is already exceeded, the evaluation stage that checks spend denies whatever comes next, whichever kind of action it is. A budget.action_on_exceed of suspend additionally suspends the agent rather than only refusing the one action. The second is pre-emptive and applies to LLM calls only: before an LLM request is allowed through, the gateway prices it, reserves the projected spend against the ancestor chain under a lock, and rewrites its own allow into a hard deny when the call cannot be afforded. Nothing is charged in that case. So “a request that would breach the limit is refused before it runs” is true of LLM calls specifically; for every other action the cap bites on the request after the one that crossed it.

Example

A document that gates writes behind human approval, caps spend, and restricts egress — using the envelope form, as the shipped examples do:

apiVersion: agent-assembly/v1
kind: Policy
metadata:
  name: medium-risk-approval-gate
spec:
  version: "1.0"
  scope: team:platform
  approval_timeout_secs: 300
  tools:
    "*":
      allow: true
    db_write:
      allow: true
      requires_approval_if: "governance_level >= L2"
    file_delete:
      allow: false
  budget:
    daily_limit_usd: 25.0
    monthly_limit_usd: 400.0
    action_on_exceed: deny
  network:
    allowlist:
      - api.openai.com
      - api.anthropic.com
  schedule:
    active_hours:
      start: "09:00"
      end: "18:00"
      timezone: Asia/Taipei

Every section is optional; an absent section imposes no restriction of its own, which is why scope and cascading matter — the restriction usually comes from a broader document. Drop the apiVersion/kind/metadata/spec wrapper and promote the sections to the top level and the same document parses identically.

Reference documents live under policy-examples/ — low-risk, medium-risk, high-risk, strict, balanced, audit-only, plus capability, filesystem, and file-delete examples. They are named for the posture they express, not keyed by a tier field; the risk level is the combination of sections each one sets, and there is no tier keyword anywhere in the schema.

  • Approval — what require_approval triggers and who can decide.
  • Audit — the record every allow and deny produces.
  • Agent — the identity policy is scoped to and evaluated for.
  • Capability matrix — capability allow/deny restrictions referenced by policy scopes.
  • API reference — aa-gateway policy engine (PolicyScope, PolicyDocument) rustdoc entry points.
  • Quickstart (tracked under AAASM-418) — applying a first policy end-to-end.

Last updated: 2026-10-24 by Chisanan232