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
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:
deny— an explicit denial. Nothing later can override it, including a temporary grant.require_approval— the request blocks while a human decides.allow— forwarded with the real credential injected.- 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:
| Scope | Applies to |
|---|---|
budget.max_actions_per_hour | Every action this agent takes, across all upstreams |
budget.per_upstream | One upstream's ceiling, so a shared quota cannot be exhausted |
budget.per_agent | One named agent's own ceiling |
budget.per_delegation | What a single delegated sub-token may spend |
budget.weights | Costlier 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:
cases:
- tool: github
action: repo:read
expect: allow
- tool: github
action: secrets:read
expect: denyBefore 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.