System Prompts and Rules Files
The durable instruction layer that sits above every request: what belongs in it, what does not, and why project rules files have become a standard part of working with AI tools regardless of vendor.
The durable instruction layer that sits above every request: what belongs in it, what does not, and why project rules files have become a standard part of working with AI tools regardless of vendor.
For the fifth time this morning you type the same three sentences before the actual question: answer in British English, keep it under 150 words, and say you are not sure rather than guessing. On the sixth request you forget the third sentence, and the assistant cheerfully invents a refund policy your company does not have.
Those three sentences were never part of your question. They are standing rules for how this tool should behave, and there is a place to put them so they apply to every request without you retyping them — and a way to check that place into your repository so the whole team gets the same behaviour. By the end of this lesson you will know what belongs in that layer, what must never go near it, how to tell whether your tool is reading it, and why the version other teams check in goes stale within a few months.
Everything you send a model is text, and almost every AI tool gives you two slots so you stop mixing two kinds of it.
Text that does not change.
British English, under 150 words, admit uncertainty. Read before the user's message, on every request, whether or not anyone remembers.
Vendors name it differently — system instructions, a developer message, "custom instructions" — and the mechanism is the same.
Text that differs every time.
The customer's question, the invoice they ask about, the file you are editing.
Anything that varies belongs here, however convenient it would be up above.
Think of a new colleague's first day. The briefing — who we are, what we sell, how we talk to customers, when to escalate — is given once and holds for every ticket after it. You would not repeat it at the top of each ticket, and you would not put "reply to Marta about invoice 4021" into the briefing. The system prompt is the briefing; the message you type is the ticket. Roughly:
{
"system": "You are the billing assistant for Ledgerly...",
"messages": [
{ "role": "user", "content": "Why was I charged twice?" }
]
}Field names differ between providers, but the shape holds. Two things follow. Instructions in that slot carry more weight than a line someone types into the chat box, because the model treats them as coming from the operator of the product rather than from whoever is using it — which matters the moment your assistant talks to strangers. And the text is sent every time, so a long system prompt is a bill you pay per call, forever.
A good standing layer holds four kinds of thing and nothing else. Here is one, for an assistant answering billing questions in an invoicing app:
You are the billing assistant for Ledgerly, an invoicing app
used by freelancers and small agencies. You answer questions
from paying customers about their own invoices and charges.
Always:
- Answer in British English, at most 150 words.
- Quote figures exactly as they appear in the account data
you are given, including the currency.
Never:
- Promise a refund, a credit, or a date for either. Refunds
are a human decision.
- State a policy that is not in the material you were given.
If the account data does not answer the question, say so in
one sentence and offer to pass it to a human. Do not guess.The role
Who the assistant acts as, and who it is for. Keep it functional: "you are a world-class billing genius" adds tokens and changes nothing you could check.
Standing constraints
The always and never blocks — what must hold of every answer, and the failures you actually fear.
Output conventions
Language, length, format, how to name things. Boring, and the lines that repeat most often in your typed prompts, which is why they belong up here.
The escape hatch
What to do when the model does not know. A model's default is to produce something; without permission to stop, it fills the gap with a fluent, confident, invented refund policy.
Bad — every line sounds like a rule, but no answer could ever break one.
Be helpful, accurate and professional. Keep responses to an
appropriate length and use a friendly tone. Try to avoid
mistakes and always do your best for the customer.Good — every line has an answer that visibly violates it.
At most 150 words, British English. Quote figures exactly as
given, with the currency. Never promise a refund or a date.You cannot test the first version and neither can the model. "Appropriate length" resolves to whatever it resolves to that day, and the thing you genuinely cared about — that no customer is promised a refund by a machine — was never written down.
The test is one question: does this change between two requests? If it does, it is not a standing instruction, however convenient it would be up there.
Bad — a system prompt written around one job, with that job's data baked into it.
You are the billing assistant for Ledgerly. Read the invoice
below and explain to the customer why the total changed
between April and May. Be concise.
Invoice 4021, April: GBP 240.00
Invoice 4021, May: GBP 312.00Good — the durable rules stay put; the invoice arrives in the message.
system: You are the billing assistant for Ledgerly...
(role, always, never, escape hatch — unchanged)
user: Invoice 4021 went from GBP 240.00 in April to
GBP 312.00 in May. Explain the difference.The first version works fine once. The second time you need a billing answer you copy it and edit two lines, and now there are two system prompts. By the end of the quarter there are eleven, they disagree about tone, and the day your finance lead asks you to stop letting a machine mention refunds, you fix all eleven.
One thing looks durable and is not: today's date. A system prompt asserting "the current date is 14 March" is right for one day and quietly wrong forever after, which is worse than saying nothing. Current prices and limits age the same way. Secrets belong in neither layer — those have their own lesson, What to Never Hand a Model, and the short version is that a system prompt is no place to keep a password.
Your system prompt for a chat tool lives in your account settings. It is yours. Your colleague has a different one, neither of you can see the other's, and when the assistant behaves differently for you two there is no way to find out why. Fine alone, useless the moment two people — or two machines — work on the same codebase.
So the same content moves into the project. A rules file is a plain text or markdown file, committed to your repository, that an AI development tool reads automatically whenever it works there. It is a system prompt with a different home, and the home is the whole point: the file is versioned, so you see when a rule appeared and who wrote it; reviewed, so "we use CSS Modules, never Tailwind" is argued about once in a pull request rather than in six people's private settings; shared, so everyone starts from the same brief; and scoped, because most tools also read a file inside a subdirectory and apply it only to that subtree.
This is now a standard part of working with AI dev tools whatever the vendor, though the filenames differ: one tool looks for a markdown file at the repository root, another for a directory of rule files, another for per-directory ones — check what yours documents. The shape and the job are identical, which is why a repo with two tools in use often carries two. What you write inside matters far more than the name.
Bad — true of every repository that has ever existed, so it decides nothing about this one.
Write clean, readable code. Use meaningful variable names.
Follow best practices. Add tests where appropriate. Handle
errors properly and keep functions small and focused.Good — the decisions a competent stranger would otherwise get wrong here.
Money is stored as integer pence and formatted only at the
edges. Never introduce a float for a monetary value.
Migrations are generated, never hand-edited — a hand-edited
migration passes locally and fails on the next fresh
database. If a schema change is needed, say so and stop.
Tests use the recorded fixtures in tests/fixtures/payments/,
never the live payment account.
If a decision is not covered here, ask rather than picking
one. A wrong guess about money costs more than a question.The first file costs tokens on every request and changes nothing the model was not already going to do. Worse, it teaches the team that this file is boilerplate — so on the day someone adds the line about the live payment account, it is skimmed past with everything else. Notice too that the good version ends with the escape hatch, in a different costume.
A rules file has two audiences and fails differently with each.
The tool has to actually load it: the exact filename, in the
exact place your tool looks. A thoughtful set of conventions
sitting in docs/ai-notes.md that nothing reads is a document,
not a rule, and you can lose a week wondering why you are being
ignored. Check it at the start of a session:
Before we start: what does this project say about how money
is stored?If it cannot answer, the file is not reaching it, and no rewriting of the rules will help until that is fixed.
The humans have to find it too, and there must be exactly one copy. If the same rule lives in the README, a wiki page and the rules file, the three disagree within a month and nobody knows which is real. Make the rules file the source of truth and cut the others down to a line pointing at it.
Then keep it short. Everything in it is re-read and re-paid on every request, and a file long enough that no human opens it is one the model skims too. Put the expensive rules near the top.
Every rules file is at its best the day it is written and a little worse every week after. That is not neglect; it is the predictable result of how rules get added.
A rule arrives after every bad output and nothing is ever
removed, so the file only grows. A rule outlives its reason: it
worked around a bug in a library you upgraded eight months ago,
nobody remembers that, and so nobody dares delete it. The code
moves and the rule does not — "use the helper in
utils/money.ts" survives a refactor that renamed the file,
and that rule is now provably false. And two rules added six
months apart quietly contradict each other.
The damage is not that a stale rule gets followed. It is the opposite. The first time someone notices the assistant ignoring a rule and the team shrugs — "that one is out of date anyway" — every other rule becomes a suggestion. A file that is ninety percent true is trusted exactly as much as one that is zero percent true, because nobody can tell which ninety.
What keeps it alive is treating it like code rather than documentation. Every rule earns its place by saying something a competent person would otherwise get wrong here; if it would be equally true in any repository, delete it. Write the reason into the rule wherever it is not obvious, because a rule with no reason is both undeletable — nobody knows what breaks without it — and unpersuasive, since nobody believes it. And delete a rule in the change that removes its reason: the fixtures line goes when the fixtures go.
When the assistant gets something wrong, read the file before adding to it. Usually a rule already covers the case and is buried, vague, or contradicted twenty lines down. Rewriting that line is a fix; appending a louder duplicate is how a file doubles in size and halves in authority.
WHICH LAYER
Same every request ..... standing: prompt or rules file
Changes per request .... the message: task, data, file, date
PUT IN THE STANDING LAYER
Role and audience ...... functional, never flattery
Constraints ............ true of every answer you want
Output conventions ..... format, length, language
Escape hatch ........... what to do when it does not know
KEEP OUT OF IT
The specific task ...... copies drift into eleven versions
Anything dated ......... today's date, this month's prices
Secrets ................ keys, tokens, personal data
A RULE THAT WORKS
Decidable .............. an answer could visibly break it
Specific to here ....... true everywhere means delete it
Carries its why ........ or nobody dares delete it later
Short .................. you pay for it on every request
THE FILE IN THE REPO
Committed .............. versioned, reviewed, shared
At the path tools read . test: ask it to recite a rule
One canonical copy ..... others point at it, never copy
Scoped where useful .... a subdirectory file, subtree only
Not a security control . real guards live in the tooling
WHEN IT ROTS
Symptom ................ one stale rule, all rules become
suggestions
Fix the line ........... do not append a louder duplicate
Delete with the reason . in the change that removes it
Re-read when it bites .. corrected twice in a week? open itYou can now split what you send a model into the part that repeats and the part that does not, write the repeating half so each line could actually be broken, put it where the whole team and every tool picks it up, and recognise the slow decay that turns a good file into wallpaper.
Next comes few-shot examples that teach, which answers what this lesson cannot. Some standing instructions are not expressible as a rule at all: "match the tone of our release notes" is not decidable however you phrase it, and two well-chosen examples settle it in a way no sentence will. That lesson is about choosing examples that generalise instead of ones the model copies word for word.
The thing to do now: open your last five conversations with any AI tool and find the sentences you typed in at least three of them. Move exactly those into the standing layer, leave them out of your next prompt, and watch whether the behaviour holds. If it does not, the rule was vaguer than you thought — and you have found the first line worth rewriting.