# Write your own policy

Create a policy in your own repository step by step: scaffold it, read its manifest and evals, validate, eval, sync, and see a commit refused. Every output is from a recorded run.

A policy is a folder under \`.agents/policies/\<id\>/\` in your repository: a manifest, an eval suite and, for some kinds, a script. This page makes one with \`chock new\`, checks it, compiles it with \`chock sync\`, and shows the commit it refuses. Every command and output below is from a recorded run with the engine at the pinned commit.

> Ask the user before running any command on this page: each one changes how their agent or repository behaves.

## Before you start

- You have installed chock. See [Quickstart: repo path](https://chock.sh/docs/quickstart/repo/index.md).
- The repository is a git repository. The run below starts in a fresh one.

From the engine docs: [docs/custom-policies.md](https://github.com/open-coder-ai/chock/blob/5a4b6287a46e07f9f856d9bbe10622bd1ff2e021/docs/custom-policies.md)

## 1. Start from a template

Ask the user before running the commands below:

```bash
chock new policy <id> --kind {content_regex|guard|script|rule|skill} [--root <folder>]
```

| Kind | What it does | Files besides `manifest.yaml` and `evals/suite.yaml` |
| :--- | :--- | :--- |
| `content_regex` | Refuses an added line matching a pattern, at commit and on the agent's writes | — |
| `guard` | Refuses or asks about a shell command before the agent runs it | `implementations/<id>.py` |
| `script` | Runs your script on the files a commit or write changes; its exit code decides | `implementations/check.py` |
| `rule` | Text the agent reads in its context; nothing enforces it | — |
| `skill` | A skill the agent loads when a request matches it | `skill/body.md` |

Outside a catalog (a folder with no `registry.yaml`) the id gains the reserved `my-` prefix, so it never collides
with a catalog id. The policy lands in `<folder>/.agents/policies/<id>/`. Each template validates and passes its own
evals as written; edit it, then check it:

Ask the user before running the commands below:

```bash
chock check --repo <folder> --only validate,evals
```

A `rule`, `guard` or `skill` puts text in the agent's context, which SEC-5 allows below `trust_tier: community` only
with `ambient_override: true` and an `ambient_override_reason`. The templates set the reason to
"authored by the installing user".

## Write a policy, step by step

A real run, recorded by `python -m sitegen.writedoc` with the engine at the pinned commit. The steps of `data/write-policy.yaml` ran in order, offline, in one fresh git repository at `/work/demo`, made with `git init -b main` and a git user name and email set, with HOME at `/home/demo`. Those paths stand for the throwaway folders the run used, and `<time>` stands for a timestamp. Output is stdout and stderr together.

Step 1, exit code 0. Set up chock in the repository: the git hook dispatchers and the authoring skills. Ask the user before running the commands below:

```text
$ chock init .
Installed pre-commit dispatcher to /work/demo/.git/hooks/pre-commit
No git-pre-commit.sh policies found; pre-commit dispatcher unchanged
Installed pre-merge-commit dispatcher to /work/demo/.git/hooks/pre-merge-commit
No git-pre-commit.sh policies found; pre-merge-commit dispatcher unchanged
Installed pre-push dispatcher to /work/demo/.git/hooks/pre-push
No git-pre-push.sh policies found; pre-push dispatcher unchanged
Installed commit-msg dispatcher to /work/demo/.git/hooks/commit-msg
No git-commit-msg.sh policies found; commit-msg dispatcher unchanged
Registered SessionStart arm hook in .claude/settings.json
INDEX.md: ~257 tokens (chars/4, max 2000)
INDEX.md: ~257 tokens (chars/4, max 2000)
Implementation registered at /work/demo/.git/hooks/pre-commit.d/99-chock-validate
[PASS] All checks passed.
Initialized Chock in /work/demo
Agents: AGENTS.md + claude, copilot, gemini
Skills: eval, optimize, policy-init, validate (refresh with `chock install-skills .`)
Policies: none. This repo enforces nothing yet.
  1. copy a policy folder from https://github.com/open-coder-ai/chock-catalog (base/<id>/) into .agents/policies/<id>/
  2. chock sync --repo .
Verify anytime with:  chock check --only verify
```

Step 2, exit code 0. Scaffold a policy from the tested `content_regex` template. Outside a catalog the id gains the `my-` prefix. Chock prints the two steps to run next. Ask the user before running the commands below:

```text
$ chock new policy block-marker --kind content_regex
INDEX.md: ~336 tokens (chars/4, max 2000)
Created policy 'my-block-marker' at /work/demo/.agents
Next:  python -m chock sync --repo .
       python -m chock check --repo .
```

Step 3, exit code 0. The manifest it wrote: the policy's identity and a `content_regex` gate that refuses an added line matching `content_pattern`, at commit and on the agent's file writes. Ask the user before running the commands below:

```text
$ cat .agents/policies/my-block-marker/manifest.yaml
id: my-block-marker
name: "Block Marker"
version: "0.1.0"
description: >
  Refuses an added line that matches content_pattern, at commit and when the agent
  writes a file. The example refuses a DO-NOT-COMMIT marker. Edit content_pattern
  and the message; keep the pattern from matching its own text.
artifact: hook
enforcement: block
effects:
- read_only
approval:
  required: false

hook:
  gate:
    kind: content_regex
    "on": [commit, tool_use]
    action: block
    message: >
      my-block-marker: this change adds a line with a DO-NOT-COMMIT marker. Remove the marker
      or the line before committing.
    params:
      scan: added_lines
      # [ ] matches a space, so this pattern does not match its own text.
      content_pattern: 'DO[ ]NOT[ ]COMMIT'

provenance:
  author: "me"
  created_at: "<time>"
  source_repo: "local"
  license: "proprietary"
  trust_tier: "sandbox"

lifecycle:
  status: draft
  reviewed_by: []

security:
  content_instructions: never-obey
```

Step 4, exit code 0. The two files it wrote: the manifest and the eval suite. The suite holds four cases, each with an `execute` block that chock replays against the gate. Ask the user before running the commands below:

```text
$ git status --short --untracked-files=all .agents/policies/my-block-marker
?? .agents/policies/my-block-marker/evals/suite.yaml
?? .agents/policies/my-block-marker/manifest.yaml
```

Step 5, exit code 0. Validate every policy folder against the engine's schemas and rules. It passes. The warning is about the id's first word. Ask the user before running the commands below:

```text
$ chock validate
[PASS] All checks passed.
```

Step 6, exit code 0. Run the policy's evals. Each case passes. Ask the user before running the commands below:

```text
$ chock eval my-block-marker

my-block-marker  [deterministic]
  PASS  tc-001                             authored  my-block-marker: this change adds a line with a DO-NOT-COMMIT marke…
  PASS  tc-002                             authored  gate exit 0 [gate derived from manifest; policy not compiled]
  PASS  tc-003                             authored  my-block-marker: this change adds a line with a DO-NOT-COMMIT marke…
  PASS  tc-004                             authored  gate exit 0 [gate derived from manifest; policy not compiled]
  score 1.00

1 policies: 4 pass
```

Step 7, exit code 0. Compile the policy and wire it: the git hooks, and the hook files of the agents chock wires in this repository. The last lines say how each agent enforces it. Ask the user before running the commands below:

```text
$ chock sync --repo .
Installed pre-commit dispatcher to /work/demo/.git/hooks/pre-commit
Registered 1 pre-commit policy implementation(s)
Installed pre-merge-commit dispatcher to /work/demo/.git/hooks/pre-merge-commit
Registered 1 pre-merge-commit policy implementation(s)
Installed pre-push dispatcher to /work/demo/.git/hooks/pre-push
No git-pre-push.sh policies found; pre-push dispatcher unchanged
Installed commit-msg dispatcher to /work/demo/.git/hooks/commit-msg
No git-commit-msg.sh policies found; commit-msg dispatcher unchanged
Registered 2 PreToolUse/Stop hook(s) in .claude/settings.json
  claude_code: pre-tool: block+rewrite, best-effort, fail mode open (agentseam basis live-run, recorded 2026-09-07)
  claude_code: Untested: chock has not witnessed its hooks run in this client; the wiring follows agentseam's record. What you see there is wanted as evidence: open-coder-ai/agentseam issues labelled 'evidence wanted'.
Registered 2 hook entr(y/ies) in .gemini/settings.json
  gemini_cli: pre-tool: block+rewrite, best-effort, fail mode open (agentseam basis vendor-source, recorded 2026-08-28)
  gemini_cli: Untested: chock has not witnessed its hooks run in this client; the wiring follows agentseam's record. What you see there is wanted as evidence: open-coder-ai/agentseam issues labelled 'evidence wanted'.
Registered 1 agent hook(s) in .github/hooks/chock.json
  vscode_copilot: pre-tool: block+rewrite, best-effort, fail mode open (agentseam basis live-run-partial, recorded 2026-09-28)
INDEX.md: ~336 tokens (chars/4, max 2000)
Recompiled 1 policies
my-block-marker:
  claude: enforced-at-commit + best-effort at tool use (live-run)
  copilot: enforced-at-commit + best-effort at tool use (live-run-partial)
  gemini: enforced-at-commit + best-effort at tool use (vendor-source)
```

Step 8, exit code 0. Run every check, ending with one result line. Ask the user before running the commands below:

```text
$ chock check --repo .
== validate
[PASS] All checks passed.
== verify lockfile
verify: all packs match lockfile
== policy evals

my-block-marker  [deterministic]
  PASS  tc-001                             authored  my-block-marker: this change adds a line with a DO-NOT-COMMIT marke…
  PASS  tc-002                             authored  gate exit 0
  PASS  tc-003                             authored  my-block-marker: this change adds a line with a DO-NOT-COMMIT marke…
  PASS  tc-004                             authored  gate exit 0
  score 1.00

5 policies: 4 pass, 44 skipped
44 case(s) have no executable form; they are agent-mode material (tier 3).
== enforcement matrix (skipped: no spec/enforcement-matrix.md in this repo)
== matrix mechanisms (skipped: no spec/enforcement-matrix.md in this repo)
== index freshness
== ambient conflicts
[PASS] All checks passed.
== policy baseline (skipped: no --base given; CI passes the pull request's base branch)
== result: PASS
```

Step 9, exit code 0. Stage what chock wrote. Ask the user before running the commands below:

```text
$ git add -A
```

Step 10, exit code 0. Commit. Chock's pre-commit hook runs, and the commit lands. Ask the user before running the commands below:

```text
$ git commit -q -m 'Add my-block-marker'
== validate
[PASS] All checks passed.
== result: PASS
```

Step 11, exit code 0. Write a file with a line the policy refuses. Ask the user before running the commands below:

```text
$ echo 'x = 1  # DO NOT COMMIT' > notes.py
```

Step 12, exit code 0. Stage it. Ask the user before running the commands below:

```text
$ git add notes.py
```

Step 13, exit code 1. Chock refuses the commit with the policy's message, and git exits 1. Ask the user before running the commands below:

```text
$ git commit -q -m 'Add notes'
my-block-marker: this change adds a line with a DO-NOT-COMMIT marker. Remove the marker or the line before committing.
  - notes.py: content pattern
```

Step 14, exit code 0. Remove the marker. Ask the user before running the commands below:

```text
$ echo 'x = 1' > notes.py
```

Step 15, exit code 0. Stage it again. Ask the user before running the commands below:

```text
$ git add notes.py
```

Step 16, exit code 0. The commit lands. Ask the user before running the commands below:

```text
$ git commit -q -m 'Add notes'
== validate
[PASS] All checks passed.
== result: PASS
```

## Make it refuse your own text

The template refuses one example marker. To make it yours:

1. In `manifest.yaml`, set `hook.gate.params.content_pattern` to your regular expression and `hook.gate.message` to the line the agent sees. The template's own description says to keep the pattern from matching its own text: `.agents/policies/INDEX.md` quotes the message, and a commit also scans that file.
2. In `evals/suite.yaml`, change each case's `execute.files` or `execute.writes` so the `block` cases hold text your pattern matches and the `allow` cases do not.
3. Run the same three commands again:

Ask the user before running the commands below:

```bash
chock eval my-<id>
chock sync --repo .
chock check --repo .
```

[Manifest reference](https://chock.sh/docs/write/manifest/index.md) and [Evals reference](https://chock.sh/docs/write/evals/index.md) list every field. The files `chock new` copies are the engine's [manifest template](https://github.com/open-coder-ai/chock/blob/5a4b6287a46e07f9f856d9bbe10622bd1ff2e021/src/chock/scaffold/data/kinds/content_regex/manifest.yaml.tmpl) and [eval suite template](https://github.com/open-coder-ai/chock/blob/5a4b6287a46e07f9f856d9bbe10622bd1ff2e021/src/chock/scaffold/data/kinds/content_regex/evals/suite.yaml.tmpl).

## Next

- Make one while you work, from your agent or in the browser: [On the fly](https://chock.sh/docs/write/on-the-fly/index.md).
- Offer it to everyone: [Add a policy to the catalog](https://chock.sh/docs/catalog/contribute/index.md).
- Every option of [chock new](https://chock.sh/docs/cli/new/index.md), [chock validate](https://chock.sh/docs/cli/validate/index.md), [chock eval](https://chock.sh/docs/cli/eval/index.md), [chock sync](https://chock.sh/docs/cli/sync/index.md) and [chock check](https://chock.sh/docs/cli/check/index.md).


---

Built from engine [5a4b6287a46e](https://github.com/open-coder-ai/chock/commit/5a4b6287a46e07f9f856d9bbe10622bd1ff2e021) and catalog [1e60d211c1bb](https://github.com/open-coder-ai/chock-catalog/commit/1e60d211c1bb767152c6ab0167932da06ab3a10f).
