I used to treat repository instructions as the finished product.
Write a careful AGENTS.md
. Explain the architecture. Ban any
. Require named
exports. Tell the coding agent to keep files small and update the ADR when a
contract changes.
The document could be excellent and the next pull request could still violate
it.
That is not always the agent's fault. A rule that exists only as prose asks
every contributor, human or automated, to remember it at exactly the right
moment. Review becomes the first place where the repository discovers that the
rule was ignored.
I started treating the instruction as the explanation and a gate as the
enforcement.
The tempting response is to automate everything. That creates a different
problem: a wall of noisy checks that nobody understands and everyone learns to
bypass.
I now start with a failure that has already happened.
Suppose an agent uses any
to get a TypeScript change through quickly. The
repository rule says to use unknown
, narrow the value, and validate external
input at runtime. The reviewer catches the shortcut, asks for a revision, and
the same mistake returns two pull requests later.
That is a good gate candidate because the rule is:
“Choose the right abstraction” is not a good gate candidate. It still needs
engineering judgment.
I published the executable gates from
Agents Playbook as a
zero-dependency CLI. You can test one gate without adopting the rest of the
playbook.
Create a small fixture:
mkdir -p /tmp/playbook-gate-demo/src
printf 'export const answer: number = 42\n' \
> /tmp/playbook-gate-demo/src/example.ts
Run only the no-any
gate:
npx --yes @agentskit/playbook@0.1.0 \
run no-any \
--cwd /tmp/playbook-gate-demo
The valid file passes:
no-any: OK. 0 escape-hatched.
Now replace it with the shortcut:
printf 'export const unsafe: any = 42\n' \
> /tmp/playbook-gate-demo/src/example.ts
npx --yes @agentskit/playbook@0.1.0 \
run no-any \
--cwd /tmp/playbook-gate-demo
The command exits non-zero and points to the violation:
src/example.ts:1:22 — `any` in type position.
Use `unknown` + a runtime schema parse, or a specific type.
The useful part is not that a script can search for any
. Mature TypeScript
repositories should normally use the AST-based
@typescript-eslint/no-explicit-any
rule. The useful part is the shape of the
feedback:
The policy and the gate have different jobs, but they point in the same
direction.
An absolute rule often becomes dishonest.
There are boundaries where a legacy dependency, generated type, or migration
really does require a temporary exception. If the only available responses are
“rewrite the dependency” or “disable the check,” people disable the check.
The reference gate accepts an explicit same-line exception:
export const legacyValue: any = input // allow-any: upstream SDK has no types
That comment is not a magic phrase that makes the type safe. It makes the
decision visible. The gate counts escape hatches, and a repository can keep a
baseline so the number does not quietly grow.
This is the difference between an exception and an invisible bypass:
A gate that cannot represent a legitimate exception will eventually be worked
around outside the gate.
I want the shortest useful loop to be available before a pull request.
For a repository using the Playbook CLI, the package scripts can stay small:
{
"scripts": {
"check:agent-rules": "agents-playbook run no-any named-exports",
"precommit": "pnpm check:agent-rules"
},
"devDependencies": {
"@agentskit/playbook": "0.1.0"
}
}
CI should run the same command. A pre-commit hook is a convenience, not the
trust boundary: hooks can be skipped, while the required CI check protects the
shared branch.
The fast local path matters for coding agents in particular. If an agent can
run a focused gate immediately after editing, it can repair a violation while
the relevant context is still active. Waiting for a large repository pipeline
turns a five-second correction into another review cycle.
The no-any
gate can prove that it did not find the pattern it recognizes. It
cannot prove that the replacement type is correct.
The named-export gate cannot prove that an API is well designed. The file-size
gate cannot prove that splitting a file improved cohesion. A secret scanner
cannot prove that authorization is correct.
This boundary is important when agents are involved. Machine-checkable rules
are attractive because they produce a clean pass or fail, but engineering
quality is larger than the set of things that are easy to count.
I use three categories:
| Rule | Best enforcement |
|---|---|
| Deterministic syntax or repository invariant | Automated gate |
| Contextual design choice | Review checklist and examples |
| High-impact ambiguous decision | Explicit human approval |
If a design rule cannot be checked without guessing intent, I leave it in the
review layer. Automating a weak proxy can be worse than admitting that a human
decision remains.
The Playbook currently includes 13 zero-dependency reference gates covering
concerns such as secrets, file size, named exports, ADR/RFC requirements,
internationalization, design tokens, native HTML, and PR intent.
I would not enable all 13 on day one.
The sequence I use is:
That keeps governance attached to evidence. Every check should be able to
answer: “Which failure are you here to prevent?”
I created and maintain Agents Playbook, so this is not a neutral tool review.
The CLI and all 13 reference gates are open source, and the narrow experiment
above is intentionally easy to reproduce without an API key or model call.
Preparation disclosure: I used AI tools to help organize and critique this
draft. I ran the commands against the published package, inspected the gate
source and tests, checked the claims against the public repository, and stand
behind the final text.
If you already enforce a coding-agent rule in CI, I am interested in the rule
that produced the most useful failure message—not the largest policy file.