Keeping an Agent in Check26 of 37
This exercise is optional, for readers who know a little Python.
In [Hooks: Rules That Always Run](hooks-rules-that-always-run) you saw a hook that blocks rm -rf, and in [Asking Permission](asking-permission) you saw rules like allow npm run test * and deny git push *. Here you'll write the heart of a hook that applies such rules to every shell command an agent wants to run.
## The input
First the rules, one per line: deny or allow, a space, then a pattern. Then a line with ---. Then the commands the agent wants to run, one per line:
``text
deny rm -rf *
deny git push *
allow git *
allow npm run test *
---
git status
git push origin main
ls -la
`
In a pattern, * stands for any characters, or none. Everything else must match exactly, and a pattern must fit the **whole** command, from its first character to its last. The function matches(pattern, command) is already written for you and does exactly this.
## Your task
Write decide(command, rules). It works the way Claude Code's rules do:
1. Check the deny rules first, in the order they were written. The first one that matches **blocks** the command, whatever any allow rule says.
2. If no deny rule matches, check the allow rules in order. The first one that matches **allows** the command.
3. If no rule matches at all, the agent must **ask** you.
It returns the verdict and the rule that decided. The rest of the program prints one line per command:
`text
ALLOW git status (allow git *)
BLOCK git push origin main (deny git push *)
ASK ls -la (no rule)
`
One surprise to notice: the rule deny rm -rf * does **not** block sudo rm -rf /tmp/x, because a pattern must match from the start of the command, and this one starts with sudo. (echo rm -rf /`, which only prints some text, isn't blocked either, and rightly so.) Simple patterns are easy to slip past, which is one reason real agents use rules and hooks together, with a sandbox underneath.
Sign in to try this exercise.