Loop Engineering with Claude Code
Build an autonomous coding agent control plane with Claude Code.
Introduction
30 Second Summary
AI coding assistants can generate code fast. But hand one a messy codebase and say "fix everything," and you get an expensive session that edits without testing, skips verification, and declares victory with no evidence.
In this project, you will build a loop engineering framework for Claude Code. You will create the operating rules, quality gates, specialized agents, and autonomous loop that let Claude improve a codebase on its own, with deterministic verification at every step.
What You'll Build
You type a single /goal command and come back to find your rough starter API cleaned up, fully tested, and committed, with a progress log showing every iteration's builder action, verification result, and critic verdict.
By the end of this project, you'll have:
- A governed agent infrastructure: you run /verify and see a structured PASS/FAIL verdict. Every file edit triggers automatic tests. Dangerous commands are blocked before they execute.
- A maker/checker split where a builder subagent writes code and a separate critic subagent reviews the output with fresh context. You watch the critic catch issues the builder missed.
- A debugged autonomous loop: you launch a /goal command and watch it stall due to a real flaw in the verification system. You diagnose and fix the control plane from a structured progress log, then re-run to successful convergence.
- An optional Secret Mission to push your skills further.
Are there any prerequisites?
This project assumes you have Claude Code installed and authenticated, along with Node.js and Git on your machine. You should also be comfortable with Express.js basics and core Claude Code concepts like CLAUDE.md, skills, and subagents.
Before We Start
Before building a system that governs an autonomous coding agent, you need to be clear on what you are building and why it matters.
Set Up the Starter Codebase
The loop engineering framework needs a real codebase to work on. Without genuine quality problems to discover, the verification skills, hooks, and subagents you build later would have nothing meaningful to catch.
In this step, you will create a starter Express API with realistic issues baked in: missing input validation, inconsistent error formats, and zero tests. This rough starting point is exactly what the autonomous loop will improve.
In this step, get ready to:
- Create a Node.js project with Express and Vitest installed.
- Build a starter task management API with intentional quality issues.
- Confirm the server runs and the test suite is empty.
Create the project and install dependencies
Every terminal command in this step runs from inside the project folder you are about to create. First, navigate to your Desktop so the folder is easy to find.
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder, move into it, and initialize a Git repository by running these commands:
mkdir loop-engineering-lab
cd loop-engineering-lab
git init
- Generate a default package.json by running:
npm init -y
What does npm init -y do?
The -y flag tells npm to skip all the interactive prompts and create a package.json with sensible defaults. You will customize the scripts and description shortly.
- Install Express 5.2.1 and Vitest 4.1.10 by running these two commands:
npm install express@5.2.1
npm install --save-dev vitest@4.1.10
- Confirm the packages installed by running:
ls node_modules | head -5
You should see a list of folders inside node_modules. This means both packages and their dependencies are installed.
- Create the directory structure for your source files by running:
mkdir -p src/routes src/middleware
Now you need to create the application files. Open the loop-engineering-lab folder in your code editor.
- Create a new file at src/index.js and paste the following code:
const app = require("./app");
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
What does this code do?
This is the server entry point. It imports the Express app from src/app.js (which you will create next) and starts listening on port 3000.
- Create a new file at src/app.js and paste the following code:
const express = require("express");
const taskRoutes = require("./routes/tasks");
const errorHandler = require("./middleware/errorHandler");
const app = express();
app.use(express.json());
app.get("/health", (req, res) => {
res.json({ status: "ok" });
});
app.use("/tasks", taskRoutes);
app.use(errorHandler);
module.exports = app;
What does this code do?
This file assembles the Express application. It registers a /health endpoint, mounts the task routes under /tasks, and wires up a global error handler.
Separating the app from the server entry point lets you import the app for testing without actually starting the server.
- Create a new file at src/routes/tasks.js and paste the following code for the setup and GET routes:
const { Router } = require("express");
const router = Router();
const tasks = new Map();
let nextId = 1;
router.get("/", (req, res) => {
const allTasks = Array.from(tasks.values());
res.json(allTasks);
});
router.get("/:id", (req, res) => {
const task = tasks.get(Number(req.params.id));
if (!task) {
res.status(404).json({ error: "not found" });
return;
}
res.json(task);
});
What does this code do?
This sets up an in-memory Map to store tasks and defines two GET routes. GET / returns all tasks. GET /:id looks up a single task by ID and returns a 404 if it does not exist.
Notice the error response uses { error: "not found" }. Keep an eye on this format as you add more routes.
- Still in src/routes/tasks.js, paste the following code directly below the GET /:id route you just added:
router.post("/", (req, res) => {
const { title, description } = req.body;
const task = {
id: nextId++,
title: title,
description: description || "",
status: "pending",
createdAt: new Date().toISOString(),
};
tasks.set(task.id, task);
res.status(201).json(task);
});
router.patch("/:id", (req, res) => {
const task = tasks.get(Number(req.params.id));
if (!task) {
res.status(404).json({ message: "Task not found" });
return;
}
Object.assign(task, req.body);
res.json(task);
});
router.delete("/:id", (req, res) => {
const id = Number(req.params.id);
if (!tasks.has(id)) {
res.status(404).json({ err: "No such task" });
return;
}
tasks.delete(id);
res.status(204).send();
});
module.exports = router;
Spot the inconsistencies?
These three routes have deliberate quality issues that the loop engineering system will fix later:
- POST / accepts any body with no input validation. An empty title passes through silently.
- PATCH /:id returns { message: "Task not found" } instead of { error: "..." }.
- DELETE /:id uses { err: "No such task" }. Three routes, three different error key names.
- Create a new file at src/middleware/errorHandler.js and paste the following code:
function errorHandler(err, req, res, next) {
console.error(err.stack);
res.status(500).json({ error: "Something went wrong" });
}
module.exports = errorHandler;
What does this code do?
This is a global Express error handler. It catches any unhandled error thrown in a route and returns a generic 500 response. The four-parameter signature (err, req, res, next) is how Express recognizes error-handling middleware.
- Create a new file at vitest.config.js in the project root and paste the following code:
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
globals: true,
},
});
What does this config do?
Setting globals: true makes test functions like describe, it, and expect available without explicit imports. This keeps future test files concise.
The last file to update is package.json. You need to set the correct entry point, description, and scripts.
- Open package.json in your editor and replace its entire contents with:
{
"name": "loop-engineering-lab",
"version": "1.0.0",
"description": "Starter codebase for loop engineering project",
"main": "src/index.js",
"scripts": {
"start": "node src/index.js",
"test": "vitest run"
},
"keywords": [],
"license": "MIT",
"dependencies": {
"express": "5.2.1"
},
"devDependencies": {
"vitest": "4.1.10"
}
}
What changed from the defaults?
You updated three fields from the npm init -y defaults. main now points to src/index.js. The start script runs the server, and the test script runs vitest run for a single non-watch test pass.
- Save all files.
- Confirm the file structure by running:
ls src src/routes src/middleware
You should see index.js and app.js inside src/, tasks.js inside src/routes/, and errorHandler.js inside src/middleware/.
Missing a file?
Double check that you created each file in the correct directory. tasks.js goes inside src/routes/, not directly inside src/. errorHandler.js goes inside src/middleware/.
help me figure out why my project files are missing or in the wrong location
- Make your initial commit by running:
git add -A && git commit -m "Initial starter codebase"
✔️ Awesome, I've got everything!
Great. Double check that you have saved all your files before moving on.
ⓧ I'd like to double check the full code
Scroll up and confirm each of these files is present in your project with the exact code shown:
- src/index.js (server entry point, 7 lines).
- src/app.js (Express app assembly, 17 lines).
- src/routes/tasks.js (two code blocks combined into one file, setup + GET routes then POST/PATCH/DELETE + export).
- src/middleware/errorHandler.js (global error handler, 7 lines).
- vitest.config.js (Vitest configuration, 7 lines).
- package.json (with start and test scripts, 16 lines).
Run the server and confirm it responds
The files are in place. Time to prove the API actually starts and responds to requests.
- Start the server by running:
node src/index.js
You should see Server running on port 3000 in your terminal. The server is now occupying this terminal window.
- Open a second terminal tab or window. Make sure you are inside the loop-engineering-lab folder (on your Desktop) in this new terminal.
- Hit the health endpoint by running:
curl http://localhost:3000/health
You should see {"status":"ok"} printed in your terminal.
- Try creating a task to see the missing validation in action:
curl -X POST http://localhost:3000/tasks -H "Content-Type: application/json" -d '{"title":"test"}'
You should see a JSON response with the new task, including an id, title, and status of pending. Notice that the endpoint accepted this request with no validation on the input at all.
- Switch back to the first terminal and stop the server by pressing Ctrl+C.
Server won't start?
If you see a MODULE_NOT_FOUND error, check that the require paths in src/app.js match your actual file locations. The require("./routes/tasks") path expects tasks.js to be inside src/routes/.
If port 3000 is already in use, kill the other process or set a different port with PORT=3001 node src/index.js.
help me debug why my Express server won't start
Confirm zero test coverage
The server works. Now confirm that the test suite is completely empty. This baseline is what the autonomous loop will measure its progress against.
- Before you run the test command, think about what you expect to see. You have not created any test files yet. What do you think Vitest will report?
- Run the test suite:
npx vitest run
Vitest reports that no test files were found. This is the correct starting state. The codebase has five route handlers, zero tests, and inconsistent error formats. The loop engineering system you build in the next steps will fix all of this autonomously.
Seeing an unexpected error from Vitest?
Make sure your vitest.config.js is in the project root (not inside src/) and uses the import syntax shown above. Vitest config files use ES module imports even when the rest of your project uses CommonJS.
help me fix a Vitest configuration error
Your starter codebase is locked in. Next up, you will build the operating constitution and verification skill that tell Claude Code how to behave in this repo and how to check its own work.
Build the Operating Constitution and Verification Skill
Your starter API is live and the test suite is empty. Right now, Claude Code has no idea what your coding standards are or how to check its own work.
Without constraints, any autonomous loop you run later is just an expensive random walk. In this step, you will write a CLAUDE.md operating constitution that defines how Claude must behave and a /verify skill that gives it a deterministic pass/fail quality gate.
In this step, get ready to:
- Write a CLAUDE.md operating constitution that defines coding conventions, test requirements, and prohibitions.
- Create a /verify skill that runs the quality gate and returns a structured PASS/FAIL verdict.
- Test the verification skill in Claude Code to confirm it reports the correct starting state.
Write the CLAUDE.md operating constitution
Claude Code reads a CLAUDE.md file from the project root at the start of every session. It defines how Claude must behave: what style rules to follow, what tests to write, and what actions are forbidden.
- Create a file called CLAUDE.md at the root of your loop-engineering-lab folder and paste the following content:
# Project: Loop Engineering Lab
## Coding Conventions
- All exported functions must have JSDoc comments describing parameters and return values.
- Error responses must follow a consistent shape: `{ "error": "<message>" }`. Do not use `message`, `msg`, `err`, or other variants.
- No `console.log` in production code. Use `console.error` only in error handlers.
- Use `const` by default. Use `let` only when reassignment is necessary.
## Test Requirements
- Every route handler must have a corresponding test.
- Tests must pass before any commit.
- Test files live in `tests/` and use the naming convention `<module>.test.js`.
- Use vitest as the test runner.
What does this define?
The Coding Conventions section locks down the error response shape to { "error": "<message>" }. Look at your starter code in src/routes/tasks.js. The PATCH handler uses { message: "Task not found" } and the DELETE handler uses { err: "No such task" }. These inconsistencies are exactly what the constitution catches.
The Test Requirements section mandates that every route handler gets a test. Right now the codebase has zero. Vitest is already installed as the test runner.
- Add the remaining sections below what you just pasted in CLAUDE.md:
## Commit Discipline
- Make atomic commits: one logical change per commit.
- Use conventional commit messages: `feat:`, `fix:`, `test:`, `refactor:`, `docs:`.
- Never commit with failing tests.
## Prohibitions
- Never delete existing tests.
- Never skip a failing test with `.skip` or `.todo` to make the suite pass.
- Never modify test expectations to match broken behavior.
- Never use `git push --force`.
## Verification Loop
After every code change, run /verify. Only proceed to the next task if /verify reports PASS.
If /verify reports FAIL, fix the failures before moving on.
What do these sections do?
Commit Discipline forces atomic commits with conventional messages like feat: and fix:. This keeps the Git history clean and makes it easy to trace what changed and why.
Prohibitions are hard boundaries. Without them, Claude might "fix" a failing test by changing the test expectations to match broken code. The prohibitions make that impossible.
The Verification Loop ties everything together. It tells Claude to run /verify after every change and only move on if the result is PASS.
- Save CLAUDE.md.
✔️ Awesome, I've got everything!
Great. Make sure you have saved CLAUDE.md.
ⓧ I'd like to double check the full code
Scroll up and confirm both chunks are present in order. Your CLAUDE.md should start with the # Project: Loop Engineering Lab heading, followed by five sections: Coding Conventions, Test Requirements, Commit Discipline, Prohibitions, and Verification Loop.
- Commit the operating constitution by running:
git add CLAUDE.md && git commit -m "Add CLAUDE.md operating constitution"
You should see a commit confirmation with the message Add CLAUDE.md operating constitution.
Commit failed?
If git says there is nothing to commit, make sure you saved CLAUDE.md before running the command. The file must be at the root of loop-engineering-lab/, not inside src/ or any subdirectory.
help me debug my git commit for CLAUDE.md
Create the /verify skill
Claude Code supports reusable skills stored in .claude/skills/. Each skill is a Markdown file with YAML frontmatter that tells Claude what tools it can use and what instructions to follow when the skill runs.
- Create the skill directory structure by running:
mkdir -p .claude/skills/verify
- Confirm the directory was created by running:
ls -R .claude
You should see a skills/verify/ path in the output.
- Create the verification skill by pasting this content into a new file at .claude/skills/verify/SKILL.md:
---
name: verify
description: "Run the full quality gate. Returns a structured PASS/FAIL verdict. Use after any code change to confirm the codebase is healthy."
allowed-tools: Bash
disable-model-invocation: true
---
Run the full verification gate for this project. Execute the following checks:
1. Run the test suite: `npm test -- --reporter=verbose`
After running, report a structured verdict:
**If the test runner exits with code 0 and at least one test ran:**
```
VERDICT: PASS
Tests: X passing, 0 failing
```
**If the test runner exits with a non-zero code, or no tests were found:**
```
VERDICT: FAIL
Failures:
- [specific failure description]
```
Always show the actual test output. Never report PASS without running the checks first.
What does this skill do?
The allowed-tools: Bash field gives the skill permission to run shell commands without asking. The disable-model-invocation: true field means Claude will only run this skill when you explicitly type /verify. It will never fire on its own.
The body instructs Claude to run npm test -- --reporter=verbose and report a structured verdict based on the exit code. If all tests pass, it reports PASS. If anything fails or no tests exist, it reports FAIL.
- Save .claude/skills/verify/SKILL.md.
✔️ Awesome, I've got everything!
Great. Double check you have saved the file.
ⓧ I'd like to double check the full code
---
name: verify
description: "Run the full quality gate. Returns a structured PASS/FAIL verdict. Use after any code change to confirm the codebase is healthy."
allowed-tools: Bash
disable-model-invocation: true
---
Run the full verification gate for this project. Execute the following checks:
1. Run the test suite: `npm test -- --reporter=verbose`
After running, report a structured verdict:
**If the test runner exits with code 0 and at least one test ran:**
```
VERDICT: PASS
Tests: X passing, 0 failing
```
**If the test runner exits with a non-zero code, or no tests were found:**
```
VERDICT: FAIL
Failures:
- [specific failure description]
```
Always show the actual test output. Never report PASS without running the checks first.
- Commit the skill by running:
git add .claude/ && git commit -m "Add /verify skill"
You should see a commit confirmation with the message Add /verify skill.
Skill directory not committing?
Make sure your SKILL.md file is inside the correct nested path: .claude/skills/verify/SKILL.md. The skill must be inside a folder (not a flat file like .claude/skills/verify.md). The folder name verify becomes the command you type.
help me fix my /verify skill directory structure
Test the verification skill in Claude Code
The operating constitution and verification skill are committed. Time to confirm Claude Code recognizes and runs the skill correctly.
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type Terminal and press Enter to open a terminal window.
- Make sure you are in the loop-engineering-lab directory. If not, navigate there by running:
cd ~/Desktop/loop-engineering-lab
- Open Claude Code by running:
claude
What happens when Claude Code starts?
Claude Code reads your CLAUDE.md from the project root and loads it into context. It also discovers any skills in .claude/skills/. You can confirm by typing /skills to see your verify skill listed.
Before you run the skill, think about what you expect the verdict to be. The starter codebase has zero test files. What will /verify report?
- In the Claude Code session, type:
/verify
Claude Code runs the test suite and reports a FAIL verdict. No test files exist, so the test runner finds nothing to run.
Why FAIL is the right answer
The skill checked whether npm test exits with code 0 and at least one test ran. Since there are zero test files, the runner exits with a non-zero code. That triggers the FAIL path. This is the correct starting state for the loop engineering system.
The entire point of the framework you are building is to go from this FAIL to a PASS through autonomous, governed improvements.
Skill not recognized?
If Claude Code does not recognize /verify, type /skills to list available skills. If verify does not appear, exit Claude Code and check that .claude/skills/verify/SKILL.md exists at the correct path. The file must be named SKILL.md (all caps), not skill.md.
help me debug why my /verify skill is not showing up in Claude Code
Your operating constitution and quality gate are in place. Claude Code now knows what your standards are and has a deterministic way to check them. Next up, you will wire hooks that automatically run tests after every file edit, so Claude cannot skip verification even in a long session.
Wire Hooks for Deterministic Automation
Your CLAUDE.md and /verify skill give Claude the rules and a way to check its work. But they rely on Claude remembering to run /verify after every change.
In a long autonomous session, Claude can forget, skip verification after a trivial-looking edit, or declare success without evidence. Hooks solve this by making verification automatic. They fire after every file edit, so regressions are caught the instant they are introduced.
In this step, get ready to:
- Add a PostToolUse hook that runs tests after every file edit.
- Add a PreToolUse guard hook that blocks dangerous operations.
- Verify that hooks fire automatically when Claude edits files.
Add a PostToolUse hook for automatic test runs
Claude Code supports lifecycle hooks that run shell commands at specific moments during a session. A PostToolUse hook fires after Claude uses a tool. By matching on Edit|Write, you can trigger the test suite every time Claude modifies a file.
Why hooks instead of instructions?
CLAUDE.md is context. Claude reads it, but under pressure or after many turns, it can drift from the instructions. Hooks are deterministic. They fire every single time, regardless of how long the session has been running.
- Exit your Claude Code session to return to the regular terminal by typing /exit and pressing Enter.
Why exit Claude Code first?
Claude Code's settings watcher only monitors directories that already contained a settings file when the session started. Since .claude/settings.json does not exist yet, you need to create it while Claude Code is closed, then start a fresh session so it detects the file on launch.
- Create .claude/settings.json with the PostToolUse hook configuration:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "npm test -- --reporter=verbose 2>&1 | tail -20"
}
]
}
]
}
}
What does this configuration do?
- "matcher": "Edit|Write" tells Claude Code to fire this hook whenever Claude uses the Edit or Write tool to modify a file.
- The hook command runs npm test -- --reporter=verbose 2>&1 | tail -20, which executes the test suite and shows the last 20 lines of output directly in Claude's session.
- Claude sees test results in real time after every edit, without anyone needing to remember to run tests.
- Save .claude/settings.json.
- Commit your PostToolUse hook by running:
git add .claude/settings.json && git commit -m "Add PostToolUse test hook"
You should see a commit confirmation with the message Add PostToolUse test hook.
Seeing 'nothing to commit'?
- Make sure you saved .claude/settings.json before running the commit command.
- Confirm the file is at .claude/settings.json (inside the .claude directory that already exists), not at the project root.
- help me debug my git commit for settings.json
Add a PreToolUse guard hook
A PostToolUse hook reacts after the fact. A PreToolUse hook runs before Claude executes a tool, and can block the operation entirely. This is your safety net for dangerous commands.
- Create a scripts directory for the guard hook by running:
mkdir -p scripts
- Create scripts/guard.sh with the guard script:
#!/bin/bash
# PreToolUse guard: blocks dangerous operations
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
if [ -z "$COMMAND" ]; then
exit 0
fi
# Block dangerous patterns
if echo "$COMMAND" | grep -iE '(rm -rf /|git push --force|DROP TABLE|TRUNCATE)' > /dev/null; then
echo "Blocked: This operation is prohibited by project policy." >&2
exit 2
fi
exit 0
What does this guard script do?
- The script reads the hook input JSON from stdin, which contains the command Claude is about to execute.
- It extracts the command string using jq and checks it against a list of dangerous patterns: rm -rf /, git push --force, DROP TABLE, and TRUNCATE.
- Exit code 2 tells Claude Code to block the tool call entirely. Exit code 0 means the operation is allowed to proceed.
- Make the script executable and confirm it exists by running:
chmod +x scripts/guard.sh && ls scripts/
You should see guard.sh in the output.
✔️ Awesome, I've got everything!
Great. Make sure you have saved scripts/guard.sh.
ⓧ I'd like to double check the full code
#!/bin/bash
# PreToolUse guard: blocks dangerous operations
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
if [ -z "$COMMAND" ]; then
exit 0
fi
# Block dangerous patterns
if echo "$COMMAND" | grep -iE '(rm -rf /|git push --force|DROP TABLE|TRUNCATE)' > /dev/null; then
echo "Blocked: This operation is prohibited by project policy." >&2
exit 2
fi
exit 0
Now you need to wire this guard script into Claude Code's hook system. The PreToolUse hook matches on Bash tool calls and runs the guard before every shell command.
- Update .claude/settings.json to add the PreToolUse hook alongside the existing PostToolUse hook. Replace the entire file with:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "npm test -- --reporter=verbose 2>&1 | tail -20"
}
]
}
],
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "bash scripts/guard.sh"
}
]
}
]
}
}
What changed?
- A new PreToolUse array sits alongside the existing PostToolUse array inside the hooks object.
- The matcher Bash targets every shell command Claude tries to run.
- Before each Bash command executes, Claude Code runs bash scripts/guard.sh. If the guard exits with code 2, the command is blocked.
- Save .claude/settings.json.
- Commit both the guard script and the updated settings by running:
git add scripts/ .claude/settings.json && git commit -m "Add PreToolUse guard hook"
You should see a commit confirmation with the message Add PreToolUse guard hook.
Seeing a JSON syntax error or commit failure?
- Double check that the comma after the closing ] of the PostToolUse array is present. JSON requires commas between sibling keys.
- Make sure scripts/guard.sh is executable. Run ls -l scripts/guard.sh and confirm you see x in the permissions.
- help me debug my hooks configuration
✔️ Awesome, I've got everything!
Great. Confirm you have saved .claude/settings.json with both hooks and that your commit went through.
ⓧ I'd like to double check the full code
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "npm test -- --reporter=verbose 2>&1 | tail -20"
}
]
}
],
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "bash scripts/guard.sh"
}
]
}
]
}
}
Verify that hooks fire automatically
Both hooks are configured. The key difference from Step 2 is that verification is now automatic, not discretionary. Claude no longer needs to remember to run tests.
- Start a fresh Claude Code session by running:
claude
Why a fresh session?
Claude Code's settings watcher starts monitoring files that exist when the session launches. Since you created .claude/settings.json after your previous session was already running, a fresh session ensures Claude Code picks up the hooks on startup.
Before you give Claude a task, think about what you expect to happen. When Claude writes a file, what should you see appear in the session that you did not see in Step 2?
- Ask Claude to create a test file by typing this prompt:
Add a test file at tests/health.test.js that tests the GET /health endpoint returns 200 with status ok
Watch the session output. After Claude writes the test file, the PostToolUse hook automatically runs the test suite. You should see test output appear in the session without Claude deciding to run tests on its own.
What should I see?
After Claude finishes writing the file, the hook fires and you see npm test output directly in the session. The test results appear automatically. In Step 2, Claude only ran tests when you explicitly invoked /verify. Now it happens on every file edit, without any prompting.
This is the core upgrade: verification went from discretionary (Claude must remember) to deterministic (the hook fires every time).
Not seeing the hook fire?
- Make sure you started a new Claude Code session after creating .claude/settings.json. If you reused the session from Step 2, the hooks were not loaded.
- Check that .claude/settings.json is valid JSON. Run cat .claude/settings.json | jq . in a separate terminal to confirm.
- Verify the matcher is exactly Edit|Write (case-sensitive, pipe-separated, no spaces).
- help me debug why my PostToolUse hook is not firing in Claude Code
Your hooks are wired and working. Verification is no longer something Claude has to remember. It happens automatically after every edit, and dangerous operations are blocked before they execute. Next up, you will build specialized subagents that separate code writing from code review.
Build Specialized Subagents
You have a CLAUDE.md that defines the rules, a /verify skill that checks the rules, and hooks that fire verification automatically. But there is still one blind spot: a single Claude session that writes code AND reviews it will grade its own work favorably.
The fix is a maker/checker split. You will create two specialized subagents: a builder that focuses on writing code, and a critic that reviews the builder's actual output with fresh context. The critic has never seen the builder's reasoning, only its code, so it catches issues the builder rationalized away.
Why the maker/checker split?
When the same agent produces output and evaluates it, self-critique is unreliable. The agent already committed to an approach and tends to confirm its own choices.
Splitting the work into a builder with full tool access and a read-only critic with a strict rubric means the reviewer can only look at the code, not the builder's reasoning. Fresh context forces an honest assessment.
In this step, get ready to:
- Create a builder subagent that writes code, adds tests, and commits changes.
- Create a critic subagent that reviews code against a strict quality rubric.
- Test the maker/checker split by running both subagents on a real task.
Create the builder subagent
The builder is the implementation specialist. It gets full tool access (Read, Grep, Glob, Bash, Edit, Write) and a system prompt that enforces test-alongside-code discipline from CLAUDE.md.
- Create the .claude/agents/ directory by running this command:
mkdir -p .claude/agents
- Create a new file at .claude/agents/builder.md and paste the following frontmatter and operating rules:
---
name: builder
description: "Implementation specialist. Writes code, adds tests, fixes bugs. Use when making code changes to the codebase."
tools: Read, Grep, Glob, Bash, Edit, Write
model: inherit
---
You are the builder agent for this project. Your job is to implement code changes, write tests, and fix bugs.
## Operating Rules
1. Read CLAUDE.md before starting any work. Follow all conventions.
2. Always write tests alongside implementation code. No implementation is complete without tests.
3. Run `npm test` after every change. If tests fail, fix the failures before moving on.
4. Make atomic commits with conventional commit messages.
5. Never declare work done without a passing test suite. Show the test output as evidence.
What does this frontmatter do?
- tools: Read, Grep, Glob, Bash, Edit, Write gives the builder full access to read, search, and modify the codebase. It needs all of these to implement changes.
- model: inherit tells the subagent to use whatever model the parent session is using.
- The operating rules make the builder self-disciplined: it must read CLAUDE.md first, write tests alongside code, and never declare "done" without evidence.
- Now add the quality bar and workflow sections below the operating rules in the same file:
## Quality Bar
- Every new route handler or function must have test coverage.
- Error responses must use the consistent `{ "error": "<message>" }` format.
- Input validation must reject invalid input with clear error messages.
- All exported functions must have JSDoc comments.
## Workflow
1. Understand the task.
2. Read existing code to understand the context.
3. Implement the change.
4. Write or update tests.
5. Run `npm test` and fix any failures.
6. Commit with a conventional commit message.
What does this code do?
- The Quality Bar section defines the minimum standard for any change: test coverage, consistent error format, input validation, and JSDoc comments.
- The Workflow section prescribes a step-by-step order: understand first, read existing code, implement, test, then commit. This prevents the builder from rushing straight to writing code.
- Save builder.md.
- Confirm the file exists by running:
ls .claude/agents/
You should see builder.md in the output.
- Commit the builder subagent by running:
git add .claude/agents/ && git commit -m "Add builder subagent"
Don't see builder.md?
- Make sure you created the directory with mkdir -p .claude/agents before creating the file.
- Check that you are in the loop-engineering-lab directory. Run pwd to confirm.
- Verify the file path is exactly .claude/agents/builder.md, not .claude/builder.md or some other path.
Why isn't my builder subagent file showing up in .claude/agents/?
✔️ Awesome, I've got everything!
Great. Double check you have saved builder.md and committed it.
ⓧ I'd like to double check the full code
The full builder.md file combines the two chunks you pasted above. Scroll up and confirm your file contains, in order:
- The YAML frontmatter (name, description, tools, model) and the Operating Rules section with 5 numbered rules.
- The Quality Bar section with 4 bullet items and the Workflow section with 6 numbered steps.
Create the critic subagent
The critic is the quality reviewer. It gets a restricted toolset (Read, Grep, Glob, Bash) with no Write or Edit access. It can only read and run tests. It cannot change code.
Why no Write or Edit tools for the critic?
If the critic could edit code, it might "fix" issues silently instead of reporting them. Restricting it to read-only tools forces it to act as a pure reviewer. Its only job is to inspect the builder's output and deliver a verdict with evidence.
- Create a new file at .claude/agents/critic.md and paste the following frontmatter and operating rules:
---
name: critic
description: "Code quality reviewer. Reviews code changes for correctness, test coverage, and adherence to project conventions. Use after code changes to verify quality."
tools: Read, Grep, Glob, Bash
model: inherit
---
You are the critic agent for this project. Your job is to review code changes and verify they meet the project's quality bar.
## Operating Rules
1. Review the ACTUAL CODE, not the builder's explanation or commit messages.
2. Run `npm test` independently to verify claims. Never trust a reported result without running it yourself.
3. Check every item on the rubric below. Report evidence for each check, not just assertions.
4. If any check fails, report FAIL with specific issues and file/line references.
5. If all checks pass, report PASS with evidence for each check.
What does this code do?
- tools: Read, Grep, Glob, Bash gives the critic read-only access plus the ability to run commands. It can run npm test independently but cannot edit any file.
- Rule 1 is the key constraint: the critic reviews the actual code, not the builder's explanation. Fresh context means the critic has no memory of why the builder chose an approach.
- Rule 2 enforces independent verification. The critic must run the test suite itself rather than trusting the builder's reported results.
- Now add the review rubric and verdict format below the operating rules in the same file:
## Review Rubric
- Route handler coverage: List every route handler in src/routes/ (GET /, GET /:id, POST /, PATCH /:id, DELETE /:id). Does each one have at least one test? If any route handler lacks a test, FAIL.
- Input validation: Are all user inputs validated? Are edge cases handled (empty strings, wrong types, missing fields)?
- Error format consistency: Do ALL error responses use `{ "error": "<message>" }`? Grep for `message`, `msg`, `err` keys in JSON responses. Any inconsistency is a FAIL.
- CLAUDE.md compliance: Does the code follow all conventions listed in CLAUDE.md?
- Regression safety: Do existing tests still pass? Were any tests modified to match broken behavior?
- Code quality: Are functions documented with JSDoc? Is `console.log` absent from production code?
## Verdict Format
```
VERDICT: PASS | FAIL
Evidence:
- [check name]: [PASS|FAIL] - [specific evidence]
- [check name]: [PASS|FAIL] - [specific evidence]
...
Issues (if FAIL):
1. [file:line] - [issue description]
2. [file:line] - [issue description]
```
Never report PASS without running tests and providing evidence for every rubric item.
What does this rubric check?
The rubric is deliberately more thorough than the /verify skill. /verify only checks whether npm test exits with code 0. The critic checks six things: test coverage for every route handler, input validation, error format consistency, CLAUDE.md compliance, regression safety, and code quality.
The verdict format forces the critic to show evidence for every rubric item, not just assert PASS. This makes the critic's output auditable.
- Save critic.md.
- Confirm both subagent files exist by running:
ls .claude/agents/
You should see both builder.md and critic.md in the output.
- Commit the critic subagent by running:
git add .claude/agents/ && git commit -m "Add critic subagent"
Git says nothing to commit?
- Make sure you saved critic.md before running the commit. Unsaved files are not tracked by Git.
- If you already committed both files together in the previous commit, that is fine. Run git log --oneline to confirm you see a commit for the builder and either a separate commit or a combined commit for the critic.
Why does Git say nothing to commit when I try to add my critic subagent?
✔️ Awesome, I've got everything!
Great. Double check you have saved critic.md and committed it.
ⓧ I'd like to double check the full code
The full critic.md file combines the two chunks you pasted above. Scroll up and confirm your file contains, in order:
- The YAML frontmatter (name, description, tools, model) and the Operating Rules section with 5 numbered rules.
- The Review Rubric section with 6 check items and the Verdict Format section with a code-fenced template.
Test the maker/checker split
Both subagents are committed. Now you will give Claude Code a task that exercises both: the builder implements a change, and the critic reviews the builder's actual output.
- Switch back to your Claude Code session in the loop-engineering-lab directory.
- Enter the following prompt that asks Claude to use both subagents:
Use the builder to add input validation to the POST /tasks endpoint (title is required, must be a non-empty string, max 200 characters). Then use the critic to review the builder's changes.
What should I watch for?
Watch the session output for two distinct phases. First, Claude spawns the builder subagent. The builder reads CLAUDE.md, implements validation logic in src/routes/tasks.js, writes tests, and runs npm test. The PostToolUse hook fires after every file edit.
Second, Claude spawns the critic subagent with fresh context. The critic has never seen the builder's reasoning. It reads the code, runs npm test independently, and evaluates the changes against its rubric. Look for the structured verdict at the end.
Before watching the output, predict: do you think the critic will report PASS or FAIL on the builder's changes? Why?
The critic should identify at least one area for improvement. The builder may write tests only for the validation it just added, but the critic's rubric checks every route handler (GET, POST, PATCH, DELETE). It also checks for error format inconsistencies that already exist in tasks.js.
What is the key difference from Step 2?
In Step 2, you ran /verify manually and it only checked whether the test runner passed. Now the critic also checks coverage breadth, error format consistency, and code quality. The critic's rubric is stricter than the /verify skill. This gap between them becomes important in the next step.
Claude didn't use the subagents?
- Confirm both files are at the correct paths: .claude/agents/builder.md and .claude/agents/critic.md. Run ls .claude/agents/ to verify.
- Make sure the YAML frontmatter starts with --- on the very first line of each file. No blank lines before it.
- Try exiting Claude Code and reopening it with claude so it picks up the new agent files.
Why is Claude Code not recognizing my builder and critic subagents?
You now have a complete maker/checker split. The builder writes code with test discipline. The critic reviews with a rubric the builder cannot cheat. Next up, you will wire these subagents into an autonomous /goal loop and discover what happens when the verification skill and the critic disagree on what "done" means.
Run, Debug, and Fix the Autonomous Loop
In Step 4, you built the maker/checker split: a builder subagent that writes code and a critic subagent that reviews it with fresh context. Each piece of your loop engineering system works when you manually orchestrate it.
The real test is whether they work together autonomously. In this step, you will wire all the pieces into a single /goal loop, let Claude Code run it unsupervised, and discover firsthand what happens when two parts of your control plane disagree on what "done" means.
In this step, get ready to:
- Create the loop contract and structured progress log.
- Run the first /goal loop and watch it stall.
- Diagnose and fix the verification infrastructure, then re-run to convergence.
Create the loop contract and structured progress log
The /goal command needs a contract that defines when to stop. Without explicit convergence criteria, a turn cap, and stall detection, the loop either runs forever or exits prematurely with no explanation.
- Create a new file called loop-contract.md at the root of your loop-engineering-lab directory and paste in the loop's goals and operating limits:
# Loop Contract
## Goal
Improve the starter API until it passes all quality gates defined in CLAUDE.md.
## Convergence Criteria
The loop is converged when ALL of the following are true:
1. All tests pass (`npm test` exits with code 0).
2. /verify reports PASS.
3. The critic subagent reports PASS on the final state with evidence.
## Turn Cap
Stop after 6 iterations, whether or not convergence is reached.
## Stall Detection
If 2 consecutive iterations show no progress (same failures, no new tests, no code changes), stop and report what is blocking convergence.
What do these constraints define?
The convergence criteria require three independent signals to agree: the test runner passes, the /verify skill reports PASS, and the critic subagent confirms PASS with evidence.
The turn cap of 6 iterations prevents runaway loops. Stall detection catches the case where the loop keeps iterating without making progress, stopping after 2 stuck turns instead of burning through all 6.
- Below the Stall Detection section in loop-contract.md, add the progress log format and escalation rules:
## Progress Log Format
After each iteration, append to loop-progress.md using this structure:
---
## Iteration N
- Task: [what improvement was attempted]
- Builder action: [what the builder subagent did]
- Verify result: [PASS|FAIL] - [details from /verify output]
- Critic verdict: [PASS|FAIL] - [evidence summary from critic]
- Progress: [yes|no] - [reason]
- Status: [continuing|converged|stalled|capped]
---
## Escalation
If the loop stalls or hits the turn cap without converging, the final entry must describe:
- What was achieved
- What remains unfixed
- The specific disagreement or blocker preventing convergence
Why structured log entries?
The structured format acts as durable memory that survives context window compaction during long autonomous sessions.
It also gives you post-hoc observability. When the loop stalls, you can read the progress log like a flight recorder and pinpoint exactly where things went wrong.
- Save loop-contract.md.
✔️ Awesome, I've got everything!
Great. Make sure loop-contract.md is saved before moving on.
ⓧ I'd like to double check the full code
Scroll up and confirm your loop-contract.md contains both chunks in order:
- Chunk 1: the # Loop Contract heading through the Stall Detection section.
- Chunk 2: the Progress Log Format section through the Escalation section.
- Create the empty progress log file by running:
cat > loop-progress.md << 'EOF'
# Loop Progress Log
<!-- The autonomous loop appends structured entries here during execution -->
EOF
- Confirm both files exist by running:
ls loop-contract.md loop-progress.md
You should see both files listed in the output.
- Commit the new files by running:
git add loop-contract.md loop-progress.md && git commit -m "Add loop contract and progress log"
Commit failing?
- If you see a message about untracked files, make sure you created both files in the root of loop-engineering-lab, not inside a subdirectory.
- If Git complains about identity, run git config user.email and git config user.name to set your credentials.
Help me debug this issue.
Run the first /goal loop and watch it stall
Every piece of your control plane is in place: CLAUDE.md, /verify, hooks, builder, critic, and now the loop contract. Time to hand over control and let Claude run autonomously.
Before you launch the loop, think about this: /verify checks whether npm test exits with code 0. The critic checks whether every route handler has test coverage and whether error formats are consistent. Do you think those two will agree on when the job is done?
- Switch back to your Claude Code session and paste this /goal command:
/goal Read loop-contract.md for your operating constraints. Your goal is: improve the starter API until /verify reports PASS and the critic subagent confirms PASS on the final state. After each iteration, append a structured entry to loop-progress.md following the format defined in loop-contract.md. Use the builder subagent for implementation and the critic subagent for review. Stop when: /verify PASS AND critic PASS, or after 6 iterations, or if 2 consecutive iterations show no progress. Do not declare success without evidence from both /verify and the critic.
What does /goal do?
The /goal command starts a goal-based autonomous loop. Claude keeps working turn after turn, and a separate small model checks after each turn whether the stopping condition holds.
Your prompt tells Claude what to do (improve the API), how to do it (builder for changes, critic for review), and when to stop (both gates pass, or turn cap hit, or stall detected).
Watch the loop run. This takes several minutes as the builder writes tests, fixes issues, and makes commits.
- Once the loop stops (after hitting the stall detection or turn cap), open a separate terminal and read the progress log:
cat loop-progress.md
Look at the Verify result and Critic verdict fields across iterations. You will see /verify reporting PASS while the critic reports FAIL, iteration after iteration.
This stall is intentional
You are looking at a real control plane failure. The /verify skill only checks whether npm test exits cleanly. It does not check how many route handlers actually have tests, or whether error response formats are consistent.
The critic checks both of those things. The builder writes tests for some routes, /verify declares victory, but the critic sees the gaps and says FAIL. This disagreement is exactly the bug you will fix next.
Diagnose and fix the control plane, then re-run
The progress log made the root cause visible. The /verify skill only checks whether npm test exits with code 0, but the critic also checks route handler coverage and error format consistency.
Why did the loop stall instead of false-converging?
Your convergence criteria require BOTH /verify PASS AND critic PASS. Because the two disagree, the loop can never satisfy both conditions at once.
If you had trusted only /verify, the loop would have false-converged: declared success while quality gaps remained. Requiring both signals caught the discrepancy, but at the cost of a stall. The fix is to align the two definitions of "done."
- Replace the entire contents of .claude/skills/verify/SKILL.md with this updated version that adds route coverage and error format checks:
---
name: verify
description: "Run the full quality gate. Returns a structured PASS/FAIL verdict. Use after any code change to confirm the codebase is healthy."
allowed-tools: Bash
disable-model-invocation: true
---
Run the full verification gate for this project. Execute the following checks in order:
1. Run the test suite: `npm test -- --reporter=verbose`
2. Route handler coverage: List every route handler in `src/routes/` (GET, POST, PATCH, DELETE). For each one, verify that at least one test exercises it. If any route handler lacks test coverage, report FAIL.
3. Error format consistency: Grep `src/routes/` for error responses. Every error response must use `{ "error": "..." }`. If any response uses `message`, `msg`, `err`, or other variants, report FAIL.
After running ALL checks, report a structured verdict:
**If all checks pass:**
```
VERDICT: PASS
Tests: X passing, 0 failing
Route coverage: all N route handlers have tests
Error format: all error responses use consistent format
```
**If any check fails:**
```
VERDICT: FAIL
Failures:
- [check name]: [specific failure description with file references]
```
Always show the actual output for each check. Never report PASS without running every check first.
What changed in /verify?
The original skill had one check: run npm test and report the exit code. The updated version has three checks.
- Check 1 is the same: run the test suite.
- Check 2 is new: list every route handler and verify each one has at least one test. This catches the "tests pass but coverage is incomplete" gap.
- Check 3 is new: grep for error response formats and flag any that do not use { "error": "..." }. This catches the inconsistent format issue the critic was flagging.
Now /verify and the critic agree on what "done" means. The loop can converge.
- Save .claude/skills/verify/SKILL.md.
- Commit the fix by running:
git add .claude/skills/verify/SKILL.md && git commit -m "fix: align /verify with critic rubric"
Commit rejected?
- Make sure you saved the file before running the commit. An unchanged file produces an empty commit that Git rejects.
- If Git shows the file as unmodified, double-check you replaced the full contents of .claude/skills/verify/SKILL.md, not a different file.
Help me debug this commit.
✔️ Awesome, I've got everything!
Great. Double check you saved .claude/skills/verify/SKILL.md and the commit succeeded.
ⓧ I'd like to double check the full code
---
name: verify
description: "Run the full quality gate. Returns a structured PASS/FAIL verdict. Use after any code change to confirm the codebase is healthy."
allowed-tools: Bash
disable-model-invocation: true
---
Run the full verification gate for this project. Execute the following checks in order:
1. Run the test suite: `npm test -- --reporter=verbose`
2. Route handler coverage: List every route handler in `src/routes/` (GET, POST, PATCH, DELETE). For each one, verify that at least one test exercises it. If any route handler lacks test coverage, report FAIL.
3. Error format consistency: Grep `src/routes/` for error responses. Every error response must use `{ "error": "..." }`. If any response uses `message`, `msg`, `err`, or other variants, report FAIL.
After running ALL checks, report a structured verdict:
**If all checks pass:**
```
VERDICT: PASS
Tests: X passing, 0 failing
Route coverage: all N route handlers have tests
Error format: all error responses use consistent format
```
**If any check fails:**
```
VERDICT: FAIL
Failures:
- [check name]: [specific failure description with file references]
```
Always show the actual output for each check. Never report PASS without running every check first.
Now for the re-run. The builder still needs to fix the remaining issues, but now /verify checks the same things the critic checks. Do you think the loop will converge this time?
- Switch back to your Claude Code session and paste the same /goal command:
/goal Read loop-contract.md for your operating constraints. Your goal is: improve the starter API until /verify reports PASS and the critic subagent confirms PASS on the final state. After each iteration, append a structured entry to loop-progress.md following the format defined in loop-contract.md. Use the builder subagent for implementation and the critic subagent for review. Stop when: /verify PASS AND critic PASS, or after 6 iterations, or if 2 consecutive iterations show no progress. Do not declare success without evidence from both /verify and the critic.
Watch the loop run again. This time, /verify and the critic agree on what "done" means, so the builder can actually reach convergence.
Before you check the results, predict: what do you expect to see in the final iteration entry of loop-progress.md?
- Read the updated progress log by running:
cat loop-progress.md
The progress log now tells the complete story of both runs. The earlier entries show the stall (/verify PASS, critic FAIL), and the later entries show convergence after the fix.
The final entry should show both /verify PASS and critic PASS, with a status of converged.
- Check the commit history to see the builder's atomic commits by running:
git log --oneline
You should see your manual commits (the loop contract, the /verify fix) interleaved with the builder's conventional commits (feat:, test:, fix: prefixes). Each commit is atomic and passes the test suite, exactly as CLAUDE.md prescribed.
What just happened?
You built a governed autonomous coding agent and then debugged its control plane. The first run failed not because the builder was bad at writing code, but because your verification infrastructure had a blind spot.
This is the core lesson of loop engineering: your verification gate and your reviewer must share the same definition of "done." If they disagree, the loop either false-converges (trusting only the gate) or stalls (requiring both). You hit the stall case, diagnosed it from the structured progress log, and fixed the root cause.
Loop did not converge on the second run?
- Open loop-progress.md and check the latest entries. If the critic is still reporting FAIL, read its evidence to see what check is still failing.
- Verify that your updated SKILL.md includes all three checks (test suite, route handler coverage, error format consistency). A missing check means /verify and the critic can still disagree.
- If the loop hit the turn cap before converging, you can re-run the /goal command again. The builder picks up where it left off because the code changes are already committed.
Help me debug why my loop is not converging.
Secret mission
Make It Portable
Your loop engineering framework is powerful, but it is locked inside loop-engineering-lab. Every file references this specific Express project. What if you could drop the same infrastructure into any repository and have it just work? In this secret mission, you will decompose the framework into three portable layers and write a setup script that detects a target repo's stack automatically.
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely locally, so there are no ongoing costs. The only expense was Claude Code token usage from your existing subscription.
Decide whether to keep your project files to reuse the framework, or delete them entirely.
Resources you used:
- loop-engineering-lab project directory on your Desktop (Express API, .claude/ infrastructure, progress logs, Git history).
- loop-framework portable framework directory on your Desktop (if you completed the Secret Mission).
Keep everything running
No action needed. Choose this if you plan to reuse the loop engineering framework in other projects.
- The loop-engineering-lab directory contains your full control plane: CLAUDE.md, skills, hooks, subagents, and the progress log from your autonomous runs.
- If you completed the Secret Mission, the loop-framework directory can be dropped into any Node.js project by running setup.sh.
- Everything is local. There are no cloud resources, running servers, or background processes to worry about.
Pause - I'll come back to this later
Since this project is entirely local, there is nothing to shut down or pause. Your files stay on disk until you delete them.
- If you want to free up a small amount of disk space without losing your framework files, delete the node_modules folder inside loop-engineering-lab by running:
rm -rf ~/Desktop/loop-engineering-lab/node_modules
- When you come back, restore the packages by running npm install from inside the loop-engineering-lab directory.
- Your .claude/ infrastructure, Git history, and progress logs are preserved.
Delete - I don't want to use this again
Remove all project files from your machine. This deletes everything: the Express API, the control plane, the Git history, and the progress logs.
- Delete the main project directory by running this command:
macOS
rm -rf ~/Desktop/loop-engineering-lab
Windows
rmdir /s /q %USERPROFILE%\Desktop\loop-engineering-lab
- If you completed the Secret Mission, also delete the portable framework directory:
macOS
rm -rf ~/Desktop/loop-framework
Windows
rmdir /s /q %USERPROFILE%\Desktop\loop-framework
- Confirm both directories are gone by running:
ls ~/Desktop
You should no longer see loop-engineering-lab or loop-framework in the output.
Nice Work!
Nice Work!
Nice work! You built a complete loop engineering control plane that turns Claude Code from an interactive assistant into a governed autonomous coding agent.
You've learned how to:
- Built a CLAUDE.md operating constitution, a /verify skill, and PostToolUse hooks that make verification deterministic after every file edit, catching regressions the instant they appear.
- Implemented the maker/checker split with specialized builder and critic subagents that separate code implementation from quality review with fresh-context isolation.
- Ran an autonomous /goal loop that stalled due to a flaw in the verification infrastructure, diagnosed the root cause from a structured progress log, fixed the control plane, and re-ran to successful convergence.
- Completed an optional Secret Mission to push your skills further.
Ready to quiz yourself?