Chock.sh Build your plugin

Write policies

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.

Before you start

  • You have installed chock. See Quickstart: repo path.
  • The repository is a git repository. The run below starts in a fresh one.

From the engine docs: docs/custom-policies.md

1. Start from a template

chock new policy <id> --kind {content_regex|guard|script|rule|skill} [--root <folder>]
KindWhat it doesFiles besides manifest.yaml and evals/suite.yaml
content_regexRefuses an added line matching a pattern, at commit and on the agent's writes—
guardRefuses or asks about a shell command before the agent runs itimplementations/<id>.py
scriptRuns your script on the files a commit or write changes; its exit code decidesimplementations/check.py
ruleText the agent reads in its context; nothing enforces it—
skillA skill the agent loads when a request matches itskill/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:

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.

$ 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.

$ 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.

$ 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.

$ 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.

$ chock validate
[PASS] All checks passed.

Step 6, exit code 0. Run the policy's evals. Each case passes.

$ 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.

$ 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.

$ 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.

$ git add -A

Step 10, exit code 0. Commit. Chock's pre-commit hook runs, and the commit lands.

$ 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.

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

Step 12, exit code 0. Stage it.

$ git add notes.py

Step 13, exit code 1. Chock refuses the commit with the policy's message, and git exits 1.

$ 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.

$ echo 'x = 1' > notes.py

Step 15, exit code 0. Stage it again.

$ git add notes.py

Step 16, exit code 0. The commit lands.

$ 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:
chock eval my-<id>
chock sync --repo .
chock check --repo .

Manifest reference and Evals reference list every field. The files chock new copies are the engine's manifest template and eval suite template.

Next

Built from engine 5a4b6287a46e and catalog 1e60d211c1bb.