Open three pull requests from last week and you can tell, without reading the names, that three different people wrote them. Same convention behind all three, three readings of it, nobody wrong on purpose.
Writing the convention down is the obvious answer, and it has a ceiling. A document is something a person can skim, disagree with, or quietly not apply, and nothing checks. Putting it in the prompt is worse, because that is the least durable place a rule can live: true for one window, one person, one afternoon. The rung above a rule you can read is a rule you can execute.
Split by decidability, not difficulty
Take any task your team does repeatedly and ask of each rule in it:
Can a machine decide whether this was followed?
That question, not difficulty, is the line. “No em dashes in the prose” is trivial for a person and trivial for a machine, so it belongs in a script. “Is this argument any good” is hard for both and belongs nowhere near one.
What ends up on the left of that line stops varying. What is left on the right is the judgment you actually brought the assistant in for.
A worked example
This site enforces its own house rules. The rules came from measuring what the old archive did, and they are the kind of thing that would rot in a document inside a month: a prose budget, no em dashes, at least one code block, at least one diagram, never more than three prose paragraphs in a row.
None of that needs judgment. All of it is decidable, so all of it is a script.
PROSE_MAX = 8500 # measured: the longest archive post is 8,234 of prose
MAX_MONOLOGUE = 3 # consecutive prose paragraphs before something must break it
def check(path):
text = open(path).read()
body, blocks, prose = split(text) # front matter and fenced code excluded
fails = []
if len(prose) > PROSE_MAX:
fails.append(f"prose is {len(prose)} chars, over the {PROSE_MAX} budget")
if prose.count("—"):
fails.append("em dash in prose: use a comma, a colon or a full stop")
if not blocks:
fails.append("no code blocks")
if "{% include fig-" not in body:
fails.append("no diagram")
The script is boring on purpose. It has no opinions. It counts characters, looks for substrings, and returns a list of failures. Run it a thousand times and it answers identically a thousand times.
What stays with the model
The same house rules include things no counter can settle. Whether an example is the right one. Whether the angle is fresh or has been written a hundred times. Whether the reason given for a rule is the real reason.
Those live in a document the assistant reads, and they are phrased as judgment rather than as checks:
---
name: write-post
description: Draft or revise a post so it passes the house rules first time.
---
The subject is the idea. A tool is the worked example.
Be specific and generous with examples: name the product, show its real
config, paste its real output. Just do not let it become the topic.
Two artifacts, one workflow. The script owns everything decidable and never gets it wrong. The document owns everything that needs a reason and never pretends to be enforcement.
The part that makes it stick
Splitting the work is only half of it. The other half is that the script runs whether anyone remembers it or not:
$ git push
pre-push: checking 1 post(s)
prose 3593 / 8500 code blocks 2 diagrams 1 longest prose run 2
PASS
A script nobody runs is a document with extra steps. Part 6 and part 7 are about the places to attach one so that running it is not a decision anyone makes.
When not to write the script
The pattern has an obvious failure mode, which is encoding a rule you have not actually settled. A check that fires on something you are still arguing about teaches people to route around checks, and that habit is expensive to get back.
| write the script when | leave it as judgment when |
|---|---|
| the rule has survived a few real cases | you thought of it this morning |
| a violation is obvious once pointed out | reasonable people still disagree |
| you have said it more than twice | it is genuinely one-off |
| the check is cheap to run | checking costs more than the error |
The measured version of this: every threshold in that script came from counting
the archive, not from taste. 8500 is not a round number someone liked. It is
the longest real post, plus a little air.
A rule you can defend with a number is a rule that survives someone disagreeing with it.
If your team has the same conventions written in four places and enforced in none, that’s the work I do.