I have to apologize for the clickbait title. But honestly, it's not clickbait — it's what I've learned after months of running up to 15 concurrent AI agents across three different platforms (Cursor, Claude Code, Gemini CLI) in a production enterprise environment.
Every agentic engineering tutorial starts the same way: create a rules file, stuff it with instructions, trust the model. Cursor has .mdc rules. Claude Code has CLAUDE.md. Gemini CLI has GEMINI.md. I wrote beautiful, detailed rules files. Carefully crafted, ALL-CAPS warnings. "CRITICAL: NEVER expose credentials." "YOU MUST update CHANGELOG.md with UEAH attribution — No exceptions."
And then I watched my agents ignore every single one of them.
What I Actually Observed
I'm not exaggerating for effect here. I tracked compliance across my agent fleet for a month and this is what the data showed:
The root cause was embarrassingly simple once I saw it: declarative rules without procedural enforcement are just suggestions. I'd built this gorgeous governance library — 20+ Cursor rules, a multi-page CLAUDE.md, carefully organized security guidelines — and none of it mattered because nothing enforced it at the moment the agent was about to do something.
The rules live in the agent's context window. Context gets compacted. Attention drifts. The agent's "CRITICAL: NEVER do X" instruction is sitting somewhere below the fold, exerting zero influence on the next tool call. I was building a Potemkin village — beautiful governance documentation with no police force behind it.
What Changed Everything
Here's the good news. Every agent operation — every single one — passes through a finite set of action boundaries. Moments where the agent transitions from thinking to doing:
- About to execute a shell command? That's a boundary.
- Just modified a file on disk? Boundary.
- About to call the Atlassian MCP server? Boundary.
- Starting a new session? Boundary.
- Spawning a sub-agent? Boundary.
These are the enforcement points. If you can intercept the agent's action at the boundary — before it actually executes — you can validate, block, warn, or augment in real time. The agent doesn't get to choose whether to comply. The hook fires whether the agent remembers the rule or not.
I spent about two weeks building this out and the difference was night and day.
How I Organized It: Four Families of Hooks
I ended up grouping all enforcement into four families, each handling a different class of problem:
Compliance hooks are the attribution police. The changelog-ueah hook is a hard block — if an agent writes to CHANGELOG.md without a proper UEAH tag (UEAH-CUR-YYYYMMDD-HHMMSS-rand4), the write is rejected. Not warned. Rejected. The para-links hook warns when Jira or Confluence writes are missing cross-platform context footers.
Security hooks handle the scary stuff. Injection scanning on incoming context. Unicode sanitization that strips zero-width joiners and bidirectional text markers (the kind that can hide malicious instructions from human review). A token exposure guard that scans all outputs for API keys and secrets before they reach any external endpoint.
Quality hooks keep things operational. Sound notifications at action boundaries (so I actually know when agents are working). Context drift detection that warns when an agent has wandered way off task. Skill structure validation for new skills.
Orchestration hooks coordinate the multi-agent chaos. Anti-spiral detection catches agents stuck in recursive loops (this was a weekly occurrence before hooks). Handoff validation ensures structured handoffs between agents. Task pickup authorization prevents agents from claiming work they're not qualified for.
executor.py: The Actual Machinery
At the center of everything is a Python script called executor.py that receives events from any agent platform via JSON stdin, runs the appropriate validators, and returns a decision via JSON stdout.
Here's the flow for every agent action:
- Agent initiates action (write a file, call an MCP server, run a shell command)
- The platform hook fires (Cursor's
hooks.json, Claude'ssettings.json, Gemini'ssettings.json) executor.pyreceives the event as JSON — event type, tool name, arguments, context- Validators run across all four families
- The adjudication engine decides: enforce, warn, or monitor
- Result goes back to the platform: exit 0 (allow), exit 1 (warn), exit 2 (block)
That adjudication engine piece is important — it supports gradual rollout. A new hook starts in monitor mode (just logs, never blocks), graduates to warn (shows a warning but lets it through), and finally reaches enforce (hard block). This saved me from breaking all my agent workflows when I was iterating on new hooks. You can be aggressive about creating hooks but conservative about enforcing them.
Hooks Don't Work Alone (This Is the Part Most People Skip)
Here's where I think most devs stop short. They see that Cursor supports hooks.json, they wire a bash one-liner to an event, and they call it a day. Maybe a grep for a bad pattern, maybe an echo to a log file. And then they're confused when it doesn't scale.
A hook by itself is just an event trigger. It fires when something happens. That's it. It's like wiring a smoke detector that goes off but isn't connected to a sprinkler system. The detection is worthless without the response.
What makes this actually work is a three-layer architecture, and each layer has a different job:
Layer 1: Hooks (the event triggers). These are the .cursor/hooks.json, .claude/settings.json, and .gemini/settings.json configs. They define when something fires. They contain zero enforcement logic — they just point at executor.py and pass it the event context as JSON.
Layer 2: Validators (the enforcement logic). These are Python and Bash scripts that actually know how to check things. security_validators.py knows how to scan for credential patterns, detect Unicode homograph attacks, and find injection vectors. compliance_validators.py knows what a valid UEAH attribution tag looks like and can regex for it in a CHANGELOG diff.
Layer 3: Guard YAMLs (the rule definitions). These are data files that tell the validators what to look for. The security validator reads cli-command-guard.yaml to know which shell patterns are blocked. The compliance validator reads changelog-ueah.yaml to know the UEAH regex pattern and whether violations should block or warn.
This separation matters more than it might seem at first. Last month I needed to block a new class of shell command — agents were running curl | bash patterns to install packages without approval. Without the three-layer split, I would have had to edit Python code, test it, hope I didn't break the regex for the 15 other patterns already in there. Instead, I opened cli-command-guard.yaml, added two lines, and it was live. No Python changes. No testing the validator module.
I currently have 7 security guard YAMLs, 7 compliance YAMLs, 4 orchestration YAMLs, and 4 quality YAMLs — 22 config files total. And the Python validators that consume them total maybe 500 lines across four modules. That's the whole enforcement engine.
I'll go much deeper into the validator internals and YAML schemas in Part 2. But the point I want to make here is: if you deploy hooks without this layered structure, you're going to hit a wall fast. The hooks are necessary. But the validators and the guard YAMLs are what make them real.
Before and After
I'll let the numbers speak for themselves:
But the real change wasn't any single metric. It was the shift from hoping agents comply to knowing they do. When changelog-ueah is in enforce mode, the agent physically cannot write to CHANGELOG without attribution. It's not a suggestion anymore.
One System, Three Platforms
Every major agentic IDE now supports hooks natively, which makes this whole approach viable:
Cursor uses .cursor/hooks.json with events like beforeShellExecution, afterFileEdit, beforeMCPExecution, and stop.
Claude Code uses .claude/settings.json with PreToolUse, PostToolUse, and SessionStart.
Gemini CLI uses .gemini/settings.json with BeforeTool, AfterAgent, and SessionStart.
All three route through the same executor.py. A security rule I define once protects all agents equally. No "this agent has different rules" drift — the guard YAMLs are the single source of truth.
(Side note: getting the event names right was its own adventure — I initially documented Cursor events as preToolUse and sessionStart which don't exist in Cursor's schema. They're Claude Code event names. The actual Cursor events are beforeShellExecution, beforeMCPExecution, etc. I wrote a whole ADR about this after it caused a silent security gate bypass. Does that make sense? The documentation of the enforcement system had a bug that defeated the enforcement system. Turtles all the way down.)
What I'd Tell You If You're Starting From Zero
If you're managing AI agents in production — even just one Cursor instance — here's what I'd suggest:
- Figure out your action boundaries. Where does the agent go from thinking to doing? Those are your enforcement points.
- Write validators. Python or Bash scripts that check inputs/outputs at those boundaries. They don't have to be fancy.
- Define rules as data. YAML configs that validators read at runtime. Separate the "what" from the "how."
- Wire hooks to your IDE. Every major platform supports this now. It's not experimental anymore.
- Roll out gradually. Monitor → warn → enforce. Don't hard-block on day one.
Your rules files still matter — they teach the agent what to do. But hooks ensure it actually does it. Suggestions become constraints. Theater becomes enforcement.
And you can stop worrying about whether "CRITICAL: NEVER expose credentials" is landing in the right part of the context window, because there's a Python script that blocks the action before it happens regardless.
What's Next
This post covered the why and the what — why rules files alone fail, what enforcement at action boundaries looks like, and how hooks + validators + guard YAMLs compose into a system that actually works.
In the next post, I'm going to go deep on the how. The actual Python validator modules. The YAML guard schema I've converged on after months of iteration. The adjudication engine that supports gradual rollout. The federation pipeline that compiles all of this into per-platform configs so one set of rules governs Cursor, Claude Code, and Gemini CLI identically. And the health checks that catch when a hook or validator has drifted out of spec.
If that sounds interesting — or if you've built something similar and want to compare notes — I'm at johnclick.ai and johnclick.dev.
This is based on T-ADR-038 from my Agentic Developer Toolkit. The 15-slide visual companion was generated using NotebookLM and covers the full architecture in more detail.
I'm a DevOps / IT Platform Engineer building agentic governance infrastructure for enterprise AI agent deployments.