Reference

Policy format

A policy is YAML, compiled to a deterministic rule object at load. Evaluation is pure and synchronous — no network call ever happens on the decision path — so a policy always produces the same answer for the same request.

Shape

policy.yaml
agent: claude-code
on_behalf_of: you@example.com
grants:
  - tool: github
    allow: [repo:read, pr:comment]
    require_approval: [pr:create]
    deny: [repo:delete, actions:*]

budget:
  max_actions_per_hour: 100

schedule:
  timezone: Europe/Berlin
  windows:
    - days: [mon, tue, wed, thu, fri]
      start: "09:00"
      end: "18:00"

How a decision is reached

For each request Grenz resolves the tool and action, then evaluates the matching grant. The clauses are checked in a fixed order, and the first one that matches wins:

  1. deny — an explicit denial. Nothing later can override it, including a temporary grant.
  2. require_approval — the request blocks while a human decides.
  3. allow — forwarded with the real credential injected.
  4. Nothing matched — denied, with the reason no_matching_allow.

That last case is the important one. There is no implicit allow anywhere in Grenz: a request you did not think about is denied, not forwarded. The same is true of failures — a malformed policy, a vault that will not decrypt, an unreachable rule — each produces a denial with a structured reason code rather than a guess.

Patterns

Actions are noun:verb pairs and support glob matching. secrets:* covers every action on secrets; pr:* every pull-request action. Prefer listing actions explicitly and widening only when a denial proves you need to — grenz policy lint will flag a grant that is broader than it appears.

Budgets

A budget caps how many calls an agent can make in a rolling hour. They compose from broad to narrow, and every applicable ceiling must pass:

ScopeApplies to
budget.max_actions_per_hourEvery action this agent takes, across all upstreams
budget.per_upstreamOne upstream's ceiling, so a shared quota cannot be exhausted
budget.per_agentOne named agent's own ceiling
budget.per_delegationWhat a single delegated sub-token may spend
budget.weightsCostlier actions counting for more than 1

A delegated sub-token carries its own ceiling on top of its parent's, so a child can never spend more than the parent had left.

Schedules

A schedule closes the window outside the hours you name. A request arriving at 3am is denied with schedule_closed. The timezone is explicit and required — a schedule that silently follows the host clock is a schedule that breaks when you travel.

Approval

An action under require_approval blocks the agent's request in-flight and pushes a prompt to Slack or the CLI. The default TTL is five minutes; if nobody decides, it expires and is denied. If the client disconnects while waiting, the pending approval is cancelled and the action is never performed.

Optional remember_seconds reuses a recent human decision for an identical request, so an agent retrying the same call does not re-prompt you. Quorum is also available where one approver is not enough.

Delegation

An agent can mint sub-tokens for the sub-agents it spawns, with a strict subset of its own actions — a child can never hold a permission the parent lacks. Children carry the parent's identity for policy and budget, and revoking the parent cascades to every child it created.

Testing a policy

Policies are code, so treat them that way. grenz policy test asserts that a given (tool, action, target) produces the decision you expect, and runs offline against the pure engine:

policy.test.yaml
cases:
  - tool: github
    action: repo:read
    expect: allow
  - tool: github
    action: secrets:read
    expect: deny

Before rolling a change out, grenz policy diff replays a candidate against your real request history and shows what would have changed. And grenz run --shadow runs a policy without enforcing it, logging what it would have blocked — a safe way to tighten a policy on a live agent.

Related: CLI reference for the full command set, and the security model for what policy can and cannot bound.