I’ve taught prompt writing to my team twice. An hour each time, and most of that hour went on how to phrase the sentence you type.
Then I built a demo to explain context windows, and it put that sentence at one to five percent of what the model actually reads. I’d been teaching people to polish five percent.
Took me a while to admit that. Longer to work out which half of what I’d taught still deserved the hour.
The ordering was wrong, not the advice
None of it was false. Clear instructions do beat vague ones. A model given an example does follow it. What was wrong sat underneath the advice: the quiet assumption that the sentence you type is where quality comes from.
Your prompt sits in the window next to the system prompt, the tool descriptors, few-shot examples, retrieved documents, memory files, and every turn of the conversation so far. It’s the smallest thing in there most of the time.

Two evenings. Teaching people to polish the blue part.
Three things I kept
Few-shot examples. Two input/output pairs pin behaviour harder than any paragraph describing that behaviour, and the reason is boring once you see it: an example isn’t phrasing, it’s content. You’re adding to what the model reads instead of rewording your request. Of everything I used to teach, this is the one I’d still call load-bearing.
Constraints. Max 20 lines. No third-party libraries. Kotlin. Models drift toward the elaborate. A limit is the cheapest thing that holds them still.
Output format. Ask for a table and you get a table.
It took me embarrassingly long to spot what those three have in common. They’re all checkable. I can look at what came back and tell you whether the format held, whether it stayed under twenty lines, whether it followed the examples. Nothing else on my list had that property.
Role prompting, and why I dropped it
Act as a senior iOS developer. I taught that with a straight face, twice, and I can’t point at a single output where I showed it did anything. There’s no observable difference between the role working and the role not working. It might help. I have no way to tell, nobody I taught it to had a way to tell either, and I was presenting it as technique.
Chain-of-thought deserves more care. The research behind it is real and on smaller models the phrase still earns its keep. But current reasoning models do the step-by-step work internally whether you ask or not, so the instruction buys less than it did in 2023. It’s a technique with a shrinking domain now, not a habit.
Where more prompt made it worse
So I ran one task through three different system prompts. Task fixed: turn an A/B test post-mortem into a 150-word brief for a VP of Product.
| System prompt | Size | What came back |
|---|---|---|
| Empty | none | 250–450 words, the point buried near the end |
| Bloated | 3,200 tokens, 80+ rules | every box filled, the narrative gone |
| Calibrated | one principle, one worked example | 120–180 words, sendable |
Sit with the middle row. It obeyed. Every rule I’d written was followed, and what came out read like a form somebody had completed rather than something a person wrote. That was also the version I’d have bet on beforehand, because it had the most prompt engineering in it.
Fifteen lines beat a better sentence
Before any of that, I’d run two clones of the same Kotlin Multiplatform app side by side. One had no project context file. The other had fifteen lines in it: the stack, a naming convention, routing through Stinsen rather than anything else, side effects in actors, and the note that SQLDelight had replaced Realm.
Same prompt into both.
The one with fifteen lines put the file in the right module, named the view model the way the codebase names view models, went through the router, and picked SQLDelight. The other guessed, and guessed the way a stranger would.
That’s the one that reordered everything for me. Fifteen lines of context beat anything I could have done to the sentence, and they took five minutes to write.
Some rules shouldn’t be prompts at all
The clearest case is anything that has to hold every single time.
Please run the formatter after you edit this file. Sometimes. Always run the tests after writing code. “I’ll try my best,” then it skips them. A prompt is a request aimed at a probabilistic system, and a request is the wrong shape for a rule you need on run number four hundred.
So I stopped asking nicely. The check that keeps magic numbers out of Swift files, where design tokens belong, is a PreToolUse hook now. It reads the pending write, greps it, and exits 2 when it finds .padding(16).
# .claude/hooks/design-tokens.sh
INPUT=$(cat)
FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // ""')
[[ "$FILE" != *.swift ]] && exit 0
# ... grep the pending content, exit 2 on a violationExit 2 blocks the operation outright, so the file never gets written and the agent is told why. A sibling hook blocks a push when it spots @unchecked Sendable with no TODO beside it, same mechanism. Both hold on every run. No amount of rewriting the sentence gets you that.
The demo that settled it
Cheapest thing I built, and the one that ended the argument in my head.
Three facts buried in a long document at 30, 50 and 70 percent of its length, surrounded by distractors with deliberately similar codenames. The question needed all three combined, so lookup wouldn’t save it.
Version A got the whole document, about 101,000 tokens. Version B got the three relevant paragraphs, about 1,400. Same local model, same question, same correct answer out of both.

72× more, every single call
Tokens spent for the same answerThe prompt didn’t change between those two runs. It couldn’t have. It was the same prompt.
If you want the version of this that shows up on a monthly bill instead of in a demo, I wrote about what actually drains a Claude Code session separately.
What I’d teach now
Same material, reversed order. What goes into the window, and what should be pulled back out of it. What gets stored so nobody re-explains the project every morning. Which rules matter enough to be hooks instead of requests. Then the sentence, at the end, in about five minutes.
The sentence still matters. I just can’t defend giving it the whole hour any more, and I gave it the whole hour twice before I bothered to check.
Key takeaways
- Keep what you can verify. Few-shot examples, constraints and output format survive because you can look at the output and see whether they held.
- Drop what you can’t check. If there’s no observable difference between the technique working and not working, it isn’t technique.
- More rules is not more steering. 3,200 tokens of instructions produced a filled-out form; one principle with one example produced something sendable.
- Project context outranks phrasing. The model needs your conventions more than it needs a well-worded request.
- A rule that must always hold isn’t a prompt. A
PreToolUsehook with exit 2 blocks the write; an instruction politely asks a probabilistic system to remember. - Curation beats capacity. Same answer from 1,400 tokens as from 101,000, and only one of those is affordable at volume.
Sources: Andrej Karpathy on context engineering (opens in a new tab) · Anthropic: Effective context engineering for AI agents (opens in a new tab) · Liu et al., “Lost in the Middle”, TACL 2024 (opens in a new tab) · Drew Breunig: How contexts fail (opens in a new tab) · Lance Martin: Context engineering (opens in a new tab)
