My system prompt for building software has eight versions behind it. Not one of them changed because the wording was wrong. Every one of them changed because the model had done something it never occurred to me to forbid — and the correction was always the same shape. It took most of those eight rounds before I saw it: I had been writing signs, and a sign is something you read on your way past.
The prompt I started with
This is roughly what I handed over at the beginning, with the detail a real ticket carries.
This is a refactoring task
the context:
The performance of our endpoint has been pretty bad lately and customers
feel the website getting slower. It happens mostly on the dashboard,
where we load the user and everything attached to them. Support says it
started around the last release but nobody has confirmed that. We use
Spring Boot and JPA, the data is in Postgres, and there is a Redis cache
somewhere in the user service that may or may not still be in use.
what needs to be done:
- Check the user fetching data process
- Check what causes the performance issue
- Look at the queries and the caching
- Find a fix and optimize
what I want you to do:
- Create the fix
- Refactor the parts that are in the way
- Check if performance has been better
- Tell me what you changed
Read it as a brief for a person and it holds up. There is a situation, a diagnosis to make, a list of what I want back. I would have handed that to a contractor without blushing.
What is not in it is one word about what must not happen. So the response shape changed, because nothing said other teams were reading it. The Redis cache got taken apart, because I had written Look at the queries and the caching, and it looked. A dependency appeared, for a problem I had not asked about. Every one of those was a fair reading of what I wrote.
The prompt described the destination in some detail. It said nothing at all about the drop on the open side of the stairs.
Every section I added marked a place someone had already fallen
The versions that followed were not designed. They accreted. the current state: went in after an afternoon spent repairing something already repaired. what needs to be done: and what I want you to do: came apart once I noticed I had been mixing the diagnosis with the fix and getting a compromise between them. Each heading is a small monument to a specific afternoon.
Plan mode belongs to the same stretch, and it is the walkthrough before the pour: the model says what it intends to do, and I read it before anything has set. Most misunderstandings surface there, where they still cost a paragraph instead of a diff.
Then boundaries:, which changed more than the other four headings together.
the current state:
The dashboard endpoint loads the user plus every attached entity in one
call. Response times went from ~300ms to ~2.5s over two releases.
the context:
Spring Boot, JPA, Postgres. The Redis cache in the user service is still
wired up but nothing reads from it anymore.
what needs to be done:
- Find what causes the regression in the dashboard endpoint
- Show me the evidence before you change anything
what I want you to do:
- Propose a fix, then implement the one I approve
- Measure before and after with the same request
boundaries:
- Do not change the API contract. The response shape stays as it is.
- Do not touch the Redis cache in this task.
- Do not add a dependency.
- No new database migration.
- If the cause is outside the user service, stop and tell me.
The part worth explaining is not that it works. It is that it works where the same sentences, kept somewhere else, do not. A colleague put the problem exactly: “but I have written it down in CLAUDE.md and the AI still doesn’t do it.” He had. It was in there, unambiguous, in a file the model genuinely reads. CLAUDE.md is the laminated notice by the door — correct, permanent, and by the second week indistinguishable from the wall behind it.
A boundaries: block is none of those things. It is short, it is negative, it names this one task, and it stands in the same breath as the request. It is not better writing. It is the first thing in my prompt that stopped describing the work and started deciding it.
A sign tells the model what you would prefer. A railing decides what it can do. Everything that finally held was a railing.
Structure was the second thing that held
At some point I asked Claude how a good system prompt is actually structured, which turned out to be the most useful question I asked all year. The answer came back as five tags: <role>, <context>, <explanation>, <instructions>, <boundaries>.
The tags are not the point — headings would do. The split is the point. <role> and <boundaries> barely move between tasks: the same role, the same things it may never do. <context>, <explanation> and <instructions> are new every time. Once the stable half was visibly separate, I stopped rewriting it by accident, and it stopped drifting.
It was also the first time I had put any of that down deliberately — a role stated rather than assumed, and boundaries written as negative space, describing a piece of work by what stands around it instead of by what sits in the middle.
Then the instructions grew phases, in order, each one naming what it has to produce.
<phase-2>
Implement the approved brief.
→ Delegate to `frontend-engineer`: "Implement this technical
overview." Pass: the approved overview, the acceptance criteria,
and the target surfaces.
<expected-output>
The diff, plus a change summary naming every file touched.
</expected-output>
</phase-2>
<phase-3>
Delegate a second, independent `frontend-engineer` instance a REVIEW
task, not a build task. Hand it the diff and the house rules.
Verdict: {clean: true|false, findings: [...]}
Proceed only on {clean: true}; otherwise loop back to phase-2.
🛑 HUMAN GATE — STOP. Wait for approval before anything ships.
</phase-3>
“Do a good job” is a sign. “The diff, plus a change summary naming every file touched” is something the output can be held against — by me, and by whatever runs next. And the last two lines of phase 3 are the only ones in the file that are not advice at all: a verdict the pipeline branches on mechanically, and a gate that stops the session until someone with the key opens it.
A fresh mind at every hop
Once the roles were written down they became agents. A role is mostly an identity grounded in this system and not in general. “You are a senior engineer” returns generic competence. Seniority defined as fluency in this codebase’s conventions, with the conventions attached, returns code that looks like the code beside it. The rest is the lane: what it decides, what it must hand to someone else instead, and the standard its work is held to coming back.
Registered and callable, the agents read in the aggregate like a development team. An orchestrator that owns the run. A validator that speaks for the product owner. Frontend, backend and data engineers, and a QA engineer. Around them a business analyst, a test-plan author, a software architect, and one agent whose entire job is to argue with the constraints it has been handed.
One technical fact governs all of it. Every agent starts with a fresh context window, and its output flows up exactly one level. The subagent has never seen the conversation. Only its caller sees what came back.
That is why the craft moved out of the wording. It is now deciding what a fresh mind needs and handing over exactly that. Nothing else arrives.
And it is why the lane gets written twice. The validator’s role file says so in plain words: it reviews, it does not implement. That is a sign. Its tools: field has no Edit in it. That is the railing. The agent does not decline to write — it cannot.
The pipeline prompts then got long enough that writing them became a job of its own, so that got delegated too. /build-prompt takes a short intake — the shape of the pipeline, the context, the explanation — and returns the run-ready prompt in the house format. On its first pass it also gathers the implementation detail I do not have and writes it in. Two more came out of the same afternoon of tedium: /create-agents, which grounds a fresh set of roles in whatever codebase you point it at, and /walkthrough, which walks me through an implementation step by step until I can say what was built.
The gain, and I will state it as what it is — my own recollection of my own work, not a measurement. Before the pipelines, around 70% of what came back was right, and the rest was wrong assumptions, code written a second time next to code that already existed, and detail that had simply been invented. With the pipelines I would put it above 80%. My explanation is the fresh window: the QA and validator agents did not sit through the long session that produced the work, so they do not inherit its decay, and several of them exist for no other purpose than to check.
The price is time. A medium-sized implementation used to take me thirty to sixty minutes end to end. A full pipeline, with every review and every gate, can take most of a day, sometimes more. That is a real bill, and I would not pay it to change a label. It buys correctness with time, and time is the cheaper currency only when being wrong is expensive — when the work touches money, or personal data, or a decision someone will later ask you to justify.
I will say the plain thing as well: building the skills and the tools has been genuinely enjoyable, and the work has become more efficient for it — efficient in the corrections I am not making twice, not in hours.
Where to start
Not the tags, not the agents, not a pipeline. Add a boundaries: block to the next prompt you write, and put in it the things you would have been annoyed to find changed — the contract another team reads, the file that is fragile for reasons nobody wrote down, the dependency list, the migration you are not ready for. Five lines, most of them starting with “do not”. It is the cheapest change in this whole post and it has returned more than any of the others.
None of it stays inside a prompt, which is the part I did not expect. An organisation performs the same substitution on itself, one incident at a time, and calls the result governance. It can also be done deliberately, on an afternoon when nothing has gone wrong yet.
A sign is written. A railing is built. Only one of them holds when nobody is reading.