Learning AI-DLC building a game, part 12: guards and hooks
This is part 12 of a series on learning AI-DLC by building a game, World Rift Conflict, in Godot with an AWS backend. Part 1 set it up, and part 11 looked back at the first full run. This one is a deep-dive: the guards, the part of AI-DLC that can say no.
Parts so far:
- Part 1: setup
- Part 2: a proof of concept
- Part 3: automated E2E tests
- Part 4: what the reviews caught
- Part 5: inside a stage
- Part 6: a bugfix intent
- Part 7: ideation
- Part 8: inception
- Part 9: an AppSync deploy error
- Part 10: walking skeleton
- Part 11: retrospective
- Part 12: guards and hooks (this post)
TL;DR
- AI-DLC runs as Claude Code hooks: 16 of them on 8 events. Most only record. One checks every tool call against the guards.
- The guards come in two kinds. Four are fences that the Guard Policy can lower. Two stay up whatever the policy says: the human-presence check at the approval gates and the integrity check on AI-DLC's own records.
- Every piece of work in this project ran with Guard Policy
off, the default of each scope I used. The fences stood aside 984 times, and the audit has one row for each. - The refusals I actually hit came from what stays up: eight in my build log. The integrity check matches words, so a GitHub comment that said "audit" was refused.
- No agent worked around a refusal. Twice the refusal message itself named the way out.
Hooks: where AI-DLC runs
AI-DLC has no daemon. It is a set of Claude Code hooks, commands that Claude Code runs at fixed moments. In this project's .claude/settings.json there are 16, on 8 events:
| Event | What AI-DLC does there |
|---|---|
| Before a tool call | checks the guards, hands the stage rules to a helper agent, folds usage |
| After a tool call | writes the audit row, fires the sensors, syncs the state, rebuilds the stage graph |
| When I send a message | records that a human took a turn |
| Session start and end | loads where the work stands; closes the session record |
| Before compaction | checks the state file and leaves a recovery note |
| When a helper agent stops | logs the agent |
| When a turn ends | keeps the workflow going until the engine says it is done |
Most of these only watch. Across the six pieces of work in this project, the audit holds 2,035 rows for artifacts written, 419 sensor firings and 675 human turns. The guards are the hooks that can stop something, and they run before the tool call, so a refused action never happens.
Two kinds of no
The first kind is the fences. Each enforces one ordering rule that the stage instructions also state in words. The hook source says why each was added: the prose lost. One comment reads, "a conductor generated the code first and backfilled the plan".
| Fence | What it stops |
|---|---|
| Plan approval | code written before I approve the plan in Code Generation |
| Review freeze | edits to a reviewed document after the review receipt, which would void the review |
| Reviewer read scope | a reviewer of one unit reading other units' files |
| State transition | a helper agent running the workflow's lifecycle and routing commands |
The second kind stays up. The human-presence check refuses an approval unless I sent a message since the gate was shown. That is what the 675 human turns are for: an agent cannot approve its own gate. The integrity check protects AI-DLC's own records: the audit trail, the plan approval record, the hooks and the runtime. Its source is plain about it: "no Guard Policy word, lowered fence, or presence bypass turns it off."
Guard Policy off: 984 stand-asides
Each scope sets a default Guard Policy. poc, bugfix, mvp and classic, the four scopes this project used, all say guard_policy: off, and I never changed it. The mvp scope file says what that means: "plan approval, review freeze, state transition, and reviewer read scope are lowered for undirected work. Human presence stays up."
A lowered fence does not go silent. When its rule finds an action it would have stopped, it stands aside: the action goes ahead, the person gets one line, and the audit gets a GUARD_STOOD_ASIDE row. The decision table in the engine says it this way: "Never 'are you sure': the switch is already off."
Counted from the audit of all six pieces of work:
| Fence | Stood aside |
|---|---|
| Plan approval | 709 |
| Review freeze | 165 |
| Reviewer read scope | 108 |
| State transition | 2 |
| Total | 984 |
- 778 were in Code Generation. The rest were in the design stages, where the review freeze and the reviewer scope apply.
- 640 were actions by helper agents, mostly the developer agent editing code and the reviewers reading. Those lines went to the main session, not to me, so I saw very few of them.
- The
mvprun alone had 712.
I did not check the 984 rows one by one, so I cannot say how many were harmless. What I can say is what they mean. With the policy off, the fences became a record, not a stop. If I want a fence to hold, I ask for it, for one piece of work or for the project. For example, "turn the review freeze check back on".
What still said no
The audit has no row for a refusal that I could find, so this list comes from my build log and may be incomplete. Each one came from a check that stays up, or from a fence whose rule had nothing to stand aside for, because no approval existed yet.
- Deleting
.claudeafter the install (2026-10-25). "AIDLC runtime records and hooks belong to the harness … use the official updater or an external terminal." - The part 1 draft (2026-10-26). A whole-file write was refused. The text described AI-DLC's hooks and records, the likely match, but the exact rule was not identified. The part 2 draft, written after that workflow ended, went through.
- Two shell commands that said "audit" (2026-10-27): a GitHub issue comment and a build-log append, both in a shell heredoc. "The audit trail under
aidlc/spaces/<space>/intents/<intent>/audit/is written only by the framework's own tools…" Neither one touched the audit trail. - A reviewer who could not run the tests (2026-10-28). A hook blocked its shell after the first command, "because the recovery question is open". The current message for this case reads: "Code changes wait while AI-DLC's recovery question is open."
- Reading code before the plan existed (2026-10-30). In Code Generation, until the plan file was written, even
catandgrepwere refused: "Code generation cannot run mutation-capable shell commands for unit u2-accounts-profile because … code-generation-plan.md is missing or empty". The guard cannot tell a harmless shell command from one that writes, so it refuses them all. The file tool still worked. - An erased approval (2026-10-31). The agent rewrote the questions file whole, which erased the engine's Plan Approval section with it. Then every shell command was refused: "code-generation-questions.md has no Plan Approval section". The message named the way back: print the same approval fingerprint, write the section back, and ask me the plan question again. I approved it again.
- A log file named after an audit (2026-11-05). The agent could not run the pre-push script, because a new log file was called
pre-push-check-audit.log. A PR description passed in a heredoc was refused the same day, because it said "audits". - Three edits to
scripts/pre-push.sh(2026-11-06). Refused with the harness-records message. The rule that matched was not identified. The agent asked me, and I made the edit by hand.
A check that reads words
Numbers 3 and 7 have the same cause, and number 8 may too: scripts/pre-push.sh mentions "audit" three times, though its refusal showed a different message. The integrity check reads the command's text, not what the command will do. A GitHub comment about an audit looks, to it, like a write to the audit trail.
The source says so itself:
Defense in depth for harness-owned runtime records and hooks, not a sandbox. Hooks and tool calls run as the same user, so an agent with unrestricted execution can always find a path around a lexical check. The outer boundary is the harness's permission model and the person's review of what the agent runs.
So the check is a tripwire, not a wall, and it trips on innocent text. The fix each time was simple: write the text to a file and pass the file, as in gh pr create --body-file, or use the editor tool instead of a heredoc. What mattered more is that no agent looked for a way around. In number 8, the agent stopped and asked me, which is what a tripwire is for.
What I would tell myself
- Know your Guard Policy. It comes from the scope. With
off, the four fences only record. That suited a solo project where I read every gate. A team project may wantstrict. - The useful refusals came with a remedy. The plan guard's message named the exact commands to recover the approval. A refusal that says what to do next costs minutes. The ones that only say no cost a search.
- Keep the word "audit" out of shell commands. Put long text in a file. That one habit would have avoided two of the eight refusals here.
- Read the audit, not your memory. I would have guessed a few dozen stand-asides, not 984.
What is next
The UI work from part 11 is still in progress: six units are built, including a layout that fits the window, a world map, flags and a new proposals panel, but none of it is released yet. The next milestone post comes when that piece of work closes. Until then, more deep-dives like this one, from the same records.