Skip to content

Episode 3 — The Enforcement Layer

EPISODE 03 · DEPLOYMENT

The Enforcement Layer

Building the better tool wasn’t enough. The agent had to be denied the worse one.

DateJuly 2025
Triggeragents still using native Read
Diagnosistraining reflexes are sticky
FixPreToolUse hooks · exit 2
BornMCP adapter · hook installer

The CLI was working. fmap and fread were saving thousands of tokens per task. The team had quietly assumed that once you installed fsuite, the agent would prefer it — because the outputs are smaller, the chains are shorter, the answers are better.

They opened the telemetry log to confirm.

The agent was using Read 4× more often than fread. Grep 6× more often than fcontent. Glob 3× more often than fsearch.

The fsuite tools were there. The agent was ignoring them.

A coding agent’s tool selection is shaped by the millions of training examples where someone said “look at this file” and the right answer was Read. Whatever the local environment offers, the model’s first instinct is the tool that won the training distribution. Installing fsuite made it available. It did not make it preferred.

This is the same pattern that makes humans keep typing ls even after they know fls does the same job better — muscle memory beats new-feature-awareness for the first few hundred uses. Agents have the same problem, except the muscle memory is baked in at the weights and you cannot retrain it on a per-project basis.

You can, however, deny the alternative.

Claude Code supports PreToolUse hooks — small scripts that run before any tool call and can reject it with a message the agent sees. The fix was almost embarrassingly simple:

\{
"PreToolUse": [
\{
"matcher": "Read",
"hooks": [\{
"type": "command",
"command": "echo 'Use fsuite fread instead. Example: fread --symbol NAME path' >&2; exit 2"
\}]
\}
]
\}

Exit code 2 is the magic. It tells Claude Code “block this call, but show this message to the agent, not the user.” The agent reads the message, learns, switches tools — instantly, no retraining needed.

Apply the same pattern to Read, Write, Edit, Grep, Glob. Five hooks. Forty lines of JSON. Done.

Before / After · same task, same agent, hooks installed mid-session measured

// before hooks agent Read(“src/handler.ts”) 612 lines, ~6000 tok agent Read(“src/utils.ts”) 340 lines, ~3300 tok agent Grep(“doTheThing”) 47 matches, ~2000 tok                                           total: ~11,300 tok · // after hooks installed (same prompt, fresh session) agent Read(“src/handler.ts”) hook └─ blocked · “use fsuite fread instead” agent fmap(“src/handler.ts”) 14 symbols, ~280 tok agent fread(… symbol: doTheThing) 38 lines, ~320 tok agent fcontent(“doTheThing”) ranked top-12, ~450 tok                                           total: ~1,050 tok (10.7× reduction)

Hooks block native tools. They cannot route or translate calls — they just refuse. Without an alternative the agent can find, blocking is useless: the agent retries the blocked tool a few times and gives up.

That’s where the MCP adapter comes in. MCP exposes every fsuite tool with a structured JSON Schema, so when the agent’s native Read fails, the next-best-tool search lands on fread immediately. The agent sees a tool with the same job, picks it, succeeds.

Hooks alone

Block native primitives but offer no path forward. Agent thrashes, gives up, falls back to whatever it can still call.

MCP alone

Expose fsuite cleanly but do not deny the native fallback. Agent reaches for trained reflex, ignores fsuite.

Hooks + MCP

Native blocked, fsuite exposed and discoverable. Agent finds the alternative within one retry. Adoption is automatic.

Three lessons from rolling out the enforcement layer:

  1. Better tools don’t get used. Required tools get used. Build the better thing, then deny the worse thing. Both halves are necessary.
  2. The redirect message matters. A bare exit 2 with no message produces an agent that retries the blocked tool five times. A message like "Use fsuite fread instead — fread --symbol NAME path" produces an agent that switches on the first try.
  3. Hooks should redirect, not punish. Exit 2 (block + show message to agent) is correct. Exit 1 (block + show error to user) is hostile. The hook’s job is to teach, not to scold.

Episode 1 capped output. Episode 2 added structure. Episode 3 enforced adoption. Together they form a system: the agent is forced off bad reflexes, finds budget-aware tools, and uses them in a chain that compounds.

That synthesis is the Lightbulb — when the team realized fsuite wasn’t a CLI, it was a delivery vehicle for a discipline.