Most teams write their charter on day one, from best practices, in one sitting. That document is a list of guesses, and it will be argued away the first time it is inconvenient, because nobody can say what it cost to learn. Good charters are not authored. They accrete, one incident at a time, and every rule carries the date and the damage that put it there. Here is one, in full, annotated line by line.
CLAUDE.md, a rules directory, or a system promptThe best practices you would put in it on day one are the ones the model already follows. The rules that matter are the ones your codebase taught you, and you cannot know those yet.
Watch what happens to a day-one charter. It says "write clean, maintainable code" and "follow existing patterns" and "add tests". Every line is true, none of it changes any behaviour, because the model was already doing roughly that. Then a real problem arrives, somebody adds a paragraph about it, and now the document is nine tenths ornament and one tenth load-bearing, with no way to tell which is which from the inside.
The alternative is uncomfortable and works: start with nothing, and add a rule only after something has gone wrong. The resulting document is shorter, every line has a story, and when someone proposes dropping one under deadline pressure there is an answer that is not an opinion.
AUTHORED
"Prefer clear names." ← already true. costs nothing, does nothing
"Handle errors appropriately." ← unfalsifiable
"Write maintainable code." ← nobody has ever done otherwise on purpose
ACCRETED
"Money is integer cents." ← #412, silent rounding loss on refunds
"Payment calls fail fast." ← INC-88, 1,140 double charges
"Only paths named in the ticket."← #388, unreviewed rename broke two teams
Can you name the specific thing that went wrong without it? If not, you are writing a wish. Wishes are free to add and impossible to remove, which is the exact combination that produces a charter nobody reads.
A billing team, three years in, after roughly a dozen incidents. This is what is left after the deletions.
# Billing · agent charter
#
# Every rule names the incident that caused it. No incident, no rule.
# Adding one requires removing or merging one. Reviewed each January.
## Money
Money is integer cents. Percentages resolve through Decimal, then
round half-up to cents. Never float on a money path.
gate money.integer-cents
2026-03-04 · #412 · float refund rounding, ~$40/day lost silently
## Failure
Payment calls fail fast. No retries on any non-idempotent path.
gate payments.no-retry
2025-11-22 · INC-88 · retry storm double-charged 1,140 customers
## Scope
A change touches only paths named in the ticket. Renames, reformats
and refactors are separate changes with their own review.
check scope.allowed-paths
2025-08-09 · #388 · a rename rode along and broke two other teams
## Tests
Every behaviour change ships a test that goes red when the change is
reverted. A test that cannot fail is not a test.
check test.must-fail-when-reverted
2026-01-15 · #401 · 300 lines of coverage asserting against its own mock
## Secrets
Never read .env, credentials, or key material. Never write a literal
token, even in a fixture.
gate secrets.no-read · secrets.no-literals
2025-06-30 · INC-71 · test fixture key leaked to a public fork
## Comprehension
Don't merge a change you can't explain. Four claims, in the PR.
human see the Comprehension Standard
2025-04-02 · standing · adopted team-wide, not incident-driven
## Refusal
When the ticket and the code disagree about what the rule is, stop
and ask. Do not pick the one that is easier to implement.
none unenforceable, and the most valuable line here
2026-02-11 · INC-94 · agent implemented the ticket; the ticket was wrong
Seven sections, six of them traceable to a specific bad day. Notice how much is absent: nothing about style, nothing about naming, nothing about architecture, no instructions to be careful or thorough. §5 is about why.
The rule text is the part everyone writes and the least important of the four.
Money is integer cents. Percentages resolve through Decimal,
then round half-up to cents. Never float on a money path.
└─ 1. THE RULE — behavioural, testable, and about this codebase
gate money.integer-cents
└─ 2. THE ALTITUDE — where it is enforced, and its identifier
2026-03-04 · #412
└─ 3. THE DATE AND THE LINK — so it can be looked up
float refund rounding, ~$40/day lost silently
└─ 4. THE DAMAGE — in the units the business uses
gate, check, human, or none. Writing it down forces the question "is this actually enforced anywhere", which is uncomfortable roughly a third of the time.
Six months from now somebody will propose relaxing this rule because it is inconvenient in one place. "It caused a bug" loses that argument. "It quietly cost forty dollars a day for three weeks before anyone noticed" wins it, and more importantly it lets you lose the argument correctly on the occasions when forty dollars a day is genuinely the right price.
Same document, three enforcement strengths, and the choice is not about how much the team cares. It is about what the failure is shaped like.
| Rule | Altitude | Why not lower | Why not higher |
|---|---|---|---|
| Money is integer cents | gate | Reached production once and cost money. Prose already failed here. | Nothing higher exists. |
| Only paths in the ticket | check | Happens constantly; prose alone was ignored under deadline. | Legitimate exceptions exist, so a hard refusal would be routinely overridden, and a gate everyone overrides is worse than a check. |
| Stop and ask when the ticket is wrong | none | Nothing lower than prose. | Not mechanically detectable. A gate here would be a lie. §4. |
A gate whose rule has become second nature can go back down to a check, or out entirely. Almost nobody does this, which is why charters only grow. See the demotion section for the version of this argument that applies to harness code.
"When the ticket and the code disagree about the rule, stop and ask." No gate can detect this. It stays anyway, and it is the line that has prevented the most expensive failures.
INC-94 is worth telling in full because it is the failure mode that survives every mechanical control on this page. A ticket asked for discount codes to apply to shipping. The code had a rule, three years old and undocumented, that shipping is never discounted because of how the carrier contract settles. The agent implemented the ticket. Every test passed, every gate passed, review approved it, and the company spent five weeks under-billing carriers before anyone connected the two.
No gate catches this. The change was correct with respect to the ticket, correct with respect to the tests, and consistent with the codebase style. The only thing that catches it is somebody noticing that two sources of truth disagree and stopping to ask which one is wrong. That is the whole skill, and it is why the exercises in this catalog exist.
There is a tidy-minded instinct to strip a charter down to only the mechanically enforced
rules, on the grounds that the rest is decoration. Resist it. The unenforceable lines are
where the expensive failures live, precisely because they are the ones no gate is watching.
Marking them none is honest. Removing them is not.
Train for it. A rule marked none is a standing instruction to the humans, and
the way it gets installed is reverse-review and
the workshop, not a hook. Charter rules at the
none altitude should each point at how the team practises them.
A charter is defined by its omissions. Every line you add dilutes the ones that matter, so the omissions have to be argued for as carefully as the inclusions.
Instructions near the top of a long document are followed more reliably than instructions near the bottom. You can respond to that by carefully ordering a long charter, or by keeping it short enough that ordering does not matter much. The second is more robust, because the first is a property of today's models that you would have to re-verify every time one changes.
Nothing in the ordinary operation of a team ever removes a charter rule. Without a hard constraint the document grows monotonically until it stops working, and nobody can point at the day it did.
The mechanism is one line at the top of the file: adding a rule requires removing or merging one. It is crude, it is occasionally annoying, and it is the only thing that reliably works, because it converts every addition into a comparison. Not "is this rule good" but "is this rule better than the weakest rule currently in here", which is a question with an answer.
A rule that becomes a check or a gate stops needing to be prose at all. The agent does not have to remember it, the document gets a line shorter, and the constraint gets stronger. Promotion to the harness is the same idea worked through five different mount points.
One afternoon a year, with the audit trail open. The question for each rule is not "is this still a good idea" but "when did this last do anything".
This is why the trail records passes as well as blocks. A rule that has never blocked anything is either perfectly internalised or completely dead, and those look identical from the outside until you have the numbers.
| Rule | Fired | Blocked | Last block | Verdict |
|---|---|---|---|---|
| money.integer-cents | 1,204 | 31 | 6 days ago | keep · load-bearing |
| payments.no-retry | 890 | 4 | 3 months ago | keep · rare and catastrophic |
| scope.allowed-paths | 1,204 | 147 | today | keep · but 147 is a smell, investigate |
| test.must-fail-when-reverted | 1,204 | 0 | never | demote to check-only, revisit next year |
| logging.structured-only | 1,204 | 0 | never | delete · superseded by the linter in March |
Two judgement calls in that table are worth naming. A rule with four blocks in a year stays, because the metric is not frequency but expected damage, and a double-charge event dwarfs a hundred scope warnings. And a rule with a hundred and forty-seven blocks is not a success story: something is systematically producing changes that violate it, and the rule is absorbing a problem rather than solving it.
Additions happen naturally, because adding a rule is how a team demonstrates it took an incident seriously. Deletion has no such motivation behind it and will not happen spontaneously. Put a name and a date on it, or accept that the charter grows forever.
Genuinely empty, apart from the header that says how the file works.
# Charter
#
# Every rule names the incident that caused it. No incident, no rule.
# Adding one requires removing or merging one. Reviewed each January.
#
# Format:
# the rule, in behavioural terms
# altitude: gate | check | human | none
# date · link · what it cost
(no rules yet)
It will not stay empty for long, and every line that arrives will have earned its place. Two exceptions are reasonable on day one: a comprehension bar, because that one is a policy decision rather than a lesson, and anything you are carrying over from a previous team with the original incident still attached.
Do not rewrite it. Go through it once and put a date and a cause on every line you can. The lines where you cannot are your answer: some of them are load-bearing and merely undocumented, and the rest are the ones to delete first. That exercise takes an hour and is usually the most informative hour anyone has spent on the document.