Skip to main content
Version: MVP

Capability Grants

5 min readFor policy operatorsUpdated 2026-05-19

What you'll learn

  • Why Craik refuses ambient authority and what a capability grant is.
  • The three practical outcomes of a grant check.
  • The grant shape, with a worked example.
  • Which capability hooks the v0.1.0 runtime enforces today.

Capability grant

An explicit, scoped permission to perform a specific capability against a specific target. Grants compose with policy profiles: the profile sets default posture, grants narrow that posture to what's authorized for the current task.

No ambient authority

Craik does not grant agents ambient authority. An agent can't "write to the docs because it's a docs agent." Side effects require a matching grant that names:

  1. The capabilityrepo.write.docs, shell.execute, github.write, memory.write.
  2. The target — a path glob, a remote URI, a memory scope.
  3. The operationswrite, execute, propose, etc.
  4. The reason — short, human-readable. Reviewers read this first.
  5. The approver — the operator identity that authorized the grant.

When the check runs, there are three outcomes:

Outcome
Meaning
What the runtime does
allowed
grant matches
Action proceeds; a receipt is sealed with the grant id.
denied
no matching grant
Action is refused; a receipt may be sealed with status: denied for audit.
requires_approval
approval gate
Denied until explicit approval metadata and a matching grant exist. Override metadata alone is not sufficient.

The grant shape

grant_docs_write · repo.write.docs
{
"schema": "craik.capability_grant",
"version": "0.1.0",
"id": "grant_docs_write",
"task_id": "task_docs",
"capability": "repo.write.docs",
"target": {
"paths": ["docs/**"],
"exclude": ["docs/adr/**"]
},
"operations": ["write"],
"reason": "Documentation update for task_docs.",
"approved_by": "user:maintainer"
}

Read the fields top-to-bottom. The grant is reviewable on its own — you don't need to chase down a separate "what does this mean?" reference.

Immutable paths: grants alone are not enough

ADR directories and other declared immutable paths are denied by default under every profile. Writing to one requires both:

Override metadata

  • approved_by — the operator identity that authorized the override.
  • reason — short, human-readable justification.
  • Attached at the action site, not buried in policy config.

A matching grant

  • Capability must be repo.write.immutable (not repo.write.docs).
  • Target must match the immutable path being changed.
  • Grant is task-scoped — it expires with the task.

This belt-and-suspenders rule is intentional. Immutable evidence is the backbone of audit; "I forgot the grant" should not be able to overwrite an ADR by accident.

The four capability hooks today

repo.write.docs

Documentation writes to declared doc roots. Most common grant.

repo.write.immutable

Writes to immutable paths. Requires override metadata and the matching grant.

shell.execute

Shell command execution. Sandbox backend enforces additional restrictions.

github.write

GitHub side effects: PRs, issues, comments, reviews.

memory.write

Direct memory writes. Without this grant, runs leave proposals instead.

The v0.1.0 hook layer checks whether a matching grant exists. Runtime provider and tool-call paths call these checks before performing side effects — there is no "try and fail" path that lets the side effect happen under a denied posture.

Denied receipts

When a check fails, Craik can produce a receipt with status: denied. This is intentional — the audit trail captures attempted-and-refused actions the same way it captures successful ones. The denied receipt names the missing capability, the target, and the operator who would have authorized it.

craik receipts list --status denied gives you the queue of denied attempts. Frequent denials in the same shape are usually a signal that the policy envelope or grants need to widen narrowly — not that the runtime should be looser overall.

What's next