# Write a policy on the fly

Make a policy while you work: ask your agent to run the policy-init skill, scaffold one with a single chock new, or fill the templates in the browser. A recorded run writes a guard and feeds it an agent's hook event.

A policy can start as one sentence in the middle of a task. There are three ways to turn it into a folder chock enforces. The recorded run below writes a guard against one shell command and shows the hook refusing it.

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

## Three ways

- **Ask your agent.** `chock init` installs the `policy-init` skill at `.agents/skills/policy-init/`. Ask your agent to use it, in your own words: for example, "use the policy-init skill: never let the agent run terraform destroy". This page quotes its procedure below. Its default target is `<repo_root>/generated/`. Name `.agents/policies/<id>/` instead, the folder `chock sync` compiles. Read what it wrote, then run `chock check --repo .` and `chock sync --repo .` yourself.
- **One command.** `chock new policy <id> --kind <kind>` writes a tested template. Edit it, then eval and sync it. The run below does this for a guard, whose script is the engine's [guard template](https://github.com/open-coder-ai/chock/blob/5a4b6287a46e07f9f856d9bbe10622bd1ff2e021/src/chock/scaffold/data/kinds/guard/implementations/%5F%5FID%5F%5F.py.tmpl).
- **In the browser.** The [policy author](/coding/author/) asks a few questions, fills the same templates, and writes a setup script you download, read and run yourself. It builds a plugin. The site compiles nothing.

## A guard in one sitting, recorded

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 skills it installs include `policy-init`. 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 guard from the tested `guard` template: a script that checks a shell command before the agent runs it. Ask the user before running the commands below:

```text
$ chock new policy no-tf-destroy --kind guard
INDEX.md: ~285 tokens (chars/4, max 2000)
Created policy 'my-no-tf-destroy' at /work/demo/.agents
Next:  python -m chock sync --repo .
       python -m chock check --repo .
```

Step 3, exit code 0. The three files it wrote: the manifest, the eval suite and the guard's script. In the script, `BLOCK` and `ASK` are the patterns of the commands it refuses, or asks about first. Edit both. Ask the user before running the commands below:

```text
$ git status --short --untracked-files=all .agents/policies/my-no-tf-destroy
?? .agents/policies/my-no-tf-destroy/evals/suite.yaml
?? .agents/policies/my-no-tf-destroy/implementations/my-no-tf-destroy.py
?? .agents/policies/my-no-tf-destroy/manifest.yaml
```

Step 4, exit code 0. Replay its four eval cases against the script. Ask the user before running the commands below:

```text
$ chock eval my-no-tf-destroy

my-no-tf-destroy  [deterministic]
  PASS  tc-001                             authored  my-no-tf-destroy: this command is refused. Run it yourself after re…
  PASS  tc-002                             authored  my-no-tf-destroy: this command is refused. Run it yourself after re…
  PASS  tc-003                             authored  my-no-tf-destroy: this command needs a person's yes first.
  PASS  tc-004                             authored  guard exit 0
  score 1.00

1 policies: 4 pass
```

Step 5, exit code 0. Compile it and register it with 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
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 1 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 1 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: ~285 tokens (chars/4, max 2000)
Recompiled 1 policies
my-no-tf-destroy:
  claude: best-effort (live-run)
  copilot: best-effort (live-run-partial)
  gemini: best-effort (vendor-source)
```

Step 6, exit code 0. Feed the hook the event Claude Code sends before a Bash tool call running `terraform destroy`. The command is the one `chock sync` wrote for the Bash tool in `.claude/settings.json`, without the git alias around it that first checks the launcher exists. The guard refuses: the hook prints the reason and a `deny` decision. Ask the user before running the commands below:

```text
$ printf '%s' '{"hook_event_name": "PreToolUse", "tool_name": "Bash", "tool_input": {"command": "terraform destroy"}}' | sh .chock/bin/launch.sh .chock/bin/claude_code.py --guard .agents/policies/my-no-tf-destroy/implementations/my-no-tf-destroy.py
my-no-tf-destroy: this command is refused. Run it yourself after review.
{"hookSpecificOutput": {"hookEventName": "PreToolUse", "permissionDecision": "deny", "permissionDecisionReason": "Blocked by chock policy: my-no-tf-destroy: my-no-tf-destroy: this command is refused. Run it yourself after review."}}
```

Step 7, exit code 0. The same event for `terraform plan` prints nothing, so the call goes ahead. Ask the user before running the commands below:

```text
$ printf '%s' '{"hook_event_name": "PreToolUse", "tool_name": "Bash", "tool_input": {"command": "terraform plan"}}' | sh .chock/bin/launch.sh .chock/bin/claude_code.py --guard .agents/policies/my-no-tf-destroy/implementations/my-no-tf-destroy.py
```

Step 8, exit code 0. The decision log counts the refusal. Ask the user before running the commands below:

```text
$ chock gate-log
policy            surface       events  allow  block    ask   warn  last block
my-no-tf-destroy  pre-tool-use       1      0      1      0      0  <time>

Allowed tool calls: 1 (each counted once, never toward a policy)
```

From the engine docs: [src/chock/packs/_skills/policy-init/SKILL.md](https://github.com/open-coder-ai/chock/blob/5a4b6287a46e07f9f856d9bbe10622bd1ff2e021/src/chock/packs/_skills/policy-init/SKILL.md)

## Procedure

1. classify(request) using `references/taxonomy.md`; present user_facing_class: hook, rule, skill (single-purpose capability), subagent, multi-agent workflow / orchestrator. Map user selection to internal_class: hook → hook, rule → rule, skill → skill, subagent → subagent, multi-agent workflow / orchestrator → workflow skill. announce(internal_class, enforcement, deterministic_parts → script placement, reason, effects). For multi-agent workflow / orchestrator, default the display name to `Orchestrator / <Pattern Name>`; invoked skills retain regular names. determinism_scan: split deterministic_parts from judgment_parts; route mechanical parts to a code/hybrid skill with a committed script, never bury them in prose inside an NL skill. if stakes ∈ {security, compliance}: enforce ∈ {verify, block}. if any effect ∈ {writes_external, irreversible}: enforce ∈ {verify, block} AND add approval wiring.

2. interview(`references/interview.md`, target); default target: `<repo_root>/generated/`; never generate inside the invoking framework's own `.agents/` or `subagents/` folders; skip answered questions.

3. if code referenced: mine 3–5 call sites into `examples/`; convert prompts per `references/authoring-techniques.md` §D; set `security.processes_external_content: true` when the skill processes external/repo/user content.

4. draft evals FIRST: trigger + negative_trigger; add adversarial case for stakes or processes_external_content; add behavior case for non-none effects (enforce ≥ verify + approval).

5. generate using `assets/templates/`; construct paths with OS separators (never concatenate raw strings). Scaffold the full folder structure per `references/scaffold-layout.md`; placeholder files are required during scaffolding and must be marked with `<!-- SCAFFOLD: replace or delete before review -->`. Multi-agent workflows also follow `references/multi-agent-scaffold.md`; advanced patterns (failure handling, checkpoint/resume, dry-run, rollback) in `references/orchestration-patterns.md`. Agentic skills apply token/context/memory optimization per `references/agentic-optimization.md`.
   - set status: draft, trust_tier: sandbox; body = activation surface; depth → `references/`
   - default SKILL.md `## Rules` starts with a YAGNI rule: confirmed requirements only, delete unused

6. wire: install hooks for block gates, add ambient lines for rules, optional AGENTS.md fallback section; comment `compiled by chock`.

7. clean temporaries; verify only deliverable + wiring remain.

8. self-check: run `chock check --repo <repo_root>` and `npx skills-ref validate <folder>`; fix failures.

9. hand off: report folder, wiring, smoke test, promotion via PR; suggest `validate` after edits.

## Next

- The step-by-step page for a commit-time policy: [Your own policy](https://chock.sh/docs/write/own-policy/index.md).
- Every field: [Manifest reference](https://chock.sh/docs/write/manifest/index.md), [Evals reference](https://chock.sh/docs/write/evals/index.md).
- What a hook can and cannot see: [What Chock doesn't stop](https://chock.sh/docs/bypasses/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).
