Chapter 1 · The Ratchet & the Practice Loop · Lesson 1.3
Hooks, Not Instructions
When a rule must hold every single time, don't ask the model to remember it - enforce it with a script.
- 0.1-0.3 · Sprint Zero (models, harness, spec-driven dev)
- 1.1 · The ratchet
- 1.2 · CLAUDE.md as a pilot's checklist
- 1.3 · Hooks, not instructions
- 1.4 · Spec before code
- 1.5 · Context firewalls & subagents
- 1.6 · Cross-agent review
What a hook is
A hook is a small script wired to a point in the agent's lifecycle - after every file edit, before a commit, on every message - that runs automatically, every time. You don't ask for it and the agent can't skip it. It just fires. A rule in a rules file is advice the model reads; a hook is plumbing that runs whether the model likes it or not.
That difference is the whole lesson. There are two places you can put a "do this" into an agent, and they behave completely differently:
Why enforce instead of ask
Here's the hard number that makes hooks exist. An instruction you put in a prompt or rules file is followed only about 70-90% of the time - the model usually complies, but not always. A hook fires 100% of the time, because it isn't a request; it's code that runs (per the same write-up). So the rule of thumb is simple: if a thing can be wrong sometimes and that's fine, an instruction is enough. If it must be right every time, it belongs in a hook.
- Run checks after an edit - typecheck, lint, or the test suite the moment a file changes, so mistakes surface immediately.
- Block destructive commands - refuse a
rm -rfor a force-push before it runs. - Require approval before a push or a pull request - pause and ask a human at the risky moment.
- Auto-format on write - run the formatter every time a file is saved, so style is never a debate.
Design the feedback: silent success, loud failure
A well-built hook follows one rule: success is silent, failures are verbose. When the check passes, the hook says nothing and the agent moves on - the common case costs almost no words. When the check fails, the hook feeds the error text back into the loop, and the agent reads it and self-corrects. So the passing path stays cheap and the agent only stops to fix things when something is actually broken (per Osmani and My Experiments With AI).
This is where a ratcheted fix lives
Remember the ratchet from Lesson 1.1: every real mistake earns a permanent fix. A hook is the home for the kind of fix that must be enforced every time. Contrast it with a line in the rules file (Lesson 1.2), which is the home for a fix the agent only needs to know. Same failure, two possible homes - and the deciding question is always: does this need enforcing, or just knowing?
Worked example
Say your agent keeps declaring a task "done" without running the tests first, so broken code slips through. You could add a line to the rules file:
# A RULE (the agent needs to KNOW):
- Always run the test suite before saying a task is done.
# It helps - but only ~80% of the time. Some runs, the model just skips it.
# A HOOK (it must be ENFORCED):
# AFTER the agent tries to finish, run the tests.
# If they fail, feed the failures back and block completion
# until they pass.
# This fires 100% of the time. The agent literally cannot
# declare done on a red test suite.
The rule nudges. The hook guarantees. When "sometimes" isn't good enough, you promote the rule into a hook.
Check yourself
A hook is best described as something that -
A hook is a script wired to a lifecycle point (after an edit, before a commit) that fires automatically, every time - not advice the model may or may not follow.
A prompt instruction is followed roughly -
Instructions land ~70-90% of the time; a hook fires 100%. That gap is the entire reason hooks exist - for anything that must hold every single run.
Promote a rule into a hook when -
Knowledge → a line in the rules file. Enforcement every single time → a hook. If "sometimes" is unacceptable, the fix belongs in a script, not a prompt.
Do this now (5 min)
Name one thing you always want to happen when your agent works - tests run, the formatter applied, no direct commits to main. Then write it as a hook in a single line:
AFTER <event>, run <check>; if it fails, <what happens>.
# example:
AFTER each edit, run the tests; if they fail, block finishing and show the errors.
Decide two things: the trigger (which lifecycle moment fires it) and the failure behaviour (block? warn? ask a human?). Bring it back and we'll make it concrete.
Go deeper
Primary source (read this): Addy Osmani - Agent Harness Engineering. The clearest walkthrough of what to wire as a hook and why enforcement beats instruction.
Wisdom (test it on people): the HumanLayer community - good place to sanity-check which of your rules genuinely need to become hooks.