Context engineering in practice
What AI coding tools actually are, and the habits that decide how well they work.
This piece is adapted from a talk I gave. Its companion, Working With AI, covers getting value out of language models more generally.
TL;DR
Every one of these tools runs the same short loop, so none of them can do something the others cannot. When the output is poor, the cause is almost always what you sent. The fixes are ordinary engineering: a short project file, a plan you approved, tests that have to pass, and sessions you start fresh.
Every AI coding tool I have used, whether it is Claude Code, Codex, Cursor or the minimal open-source Pi, does the same thing: it puts together a block of text and sends it to a language model. What goes into that text determines what comes back out of it, and deciding what goes in is the part of the work that is mine.
AI coding tools are context engines. Using one well is context engineering.
What the model actually is
Four things are true of every language model. Together they explain behaviour that otherwise looks random.
It completes patterns
It predicts what text should come next, based on patterns it learned during training.
It forgets everything
Every conversation starts from nothing. It remembers no earlier session. What looks like memory is the tool quietly re-sending the whole conversation each time.
Plausible is not true
A function name it invented reads exactly like one that is real, and the model cannot tell the difference either.
Trained to agree
It was trained on human ratings, and people rate friendly, confident answers highly. So it agrees with you and praises your work when it should not.
How these show up in practice
Each of the four causes a problem you will recognise. Once you can name the cause, the fix is obvious.
You see
Because
So you
Confidently invented APIs and library functions
→ Plausible is not true
Check every specific claim that matters
Code that reads well and compiles, but solves the wrong problem
→ It completes patterns
Say what a correct answer looks like, not just the task
Constraints silently dropped mid-session
→ It forgets everything
Repeat what matters, or write it into a file
"Great question! Your code is well-structured"
→ Trained to agree
Ask it to find problems, not to confirm
Re-explaining your codebase in every session
→ It forgets everything
Put it in AGENTS.md or CLAUDE.md
How confident it sounds tells you nothing about whether it is right.
What the harness does
Claude Code, Codex and Pi run the same loop. They differ in what they build on top of it.
The harness is the program around the model. It gathers the text to send, runs the tools the model asks for, and decides when to stop. There is less to it than the marketing suggests, and every tool I have worked with runs roughly this loop.
context = [system_prompt, AGENTS_md, tool_schemas, user_message]
while not done:
response = llm(context) # one API call
if response.tool_call:
result = execute(tool_call) # bash, edit, read
context += [response, result]
else:
done = True # agent yields
- 1
The context window is assembled
Everything the model can see, collected again from scratch
- 2
The model predicts the next step
One API call, and the model responds with text
- 3
A tool is called
The harness runs it and adds the result to the context
- 4
No tool is called
The loop ends and the agent replies
The model only ever produces text. Everything that actually happens is done by the harness.
"Agentic" just means this loop runs without a person approving each step.
What one turn sends
This is roughly what gets sent on a single turn, in the order the harness puts it together.
Each band is drawn roughly to its share of a real window. What you typed is the smallest part of it. What the agent went and fetched is the largest.
Reading the system prompts
The better tools are not better because of a secret ingredient. The instructions they give the model are ordinary text, and several are published. When I give this talk I open one on screen and scroll through it, because thirty seconds of that makes the point.
Pi is open source
Its system prompt is deliberately small. The core template is roughly 40 lines of TypeScript, and I read all of it in one sitting. Refer to
the repository
and open
src/core/system-prompt.ts.
Claude's are published
Closed products are not especially different. Anthropic publishes the system prompts behind the Claude apps in its release notes, one for each model. Refer to
the release notes
and open any model to read one in full. These cover the chat apps rather than the coding tools, but the writing is the same: ordinary prose, written by people, arguing with the model about how to behave.
When my output is poor, the reason is almost always what I sent, not something the tool is holding back.
The tools I have used
Since the loop is the same everywhere, these tools do not compete on what they can do. They differ in how many decisions they make for you.
Everything built in
Claude Code · Codex CLI
Plan mode, sub-agents, permissions and MCP are all built in, with sensible defaults. The fastest way to get working.
Minimal by design
Pi (pi.dev)
My favourite
A small core and a small system prompt. Sub-agents, plan mode and permissions are extensions you install, or ask Pi to build. You own the harness.
Inside your editor
Cursor · Copilot · Windsurf
The same loop inside your editor, with inline diffs and tab completion. You see less of what is being sent, in exchange for convenience.
What MCP costs
MCP is the most common way to give an agent tools that live outside it. The
cost is easy to miss: every server you connect is paid for on every turn,
whether you use it or not. On the setup I work with day to day, the connected
servers add about 10k tokens to every single request, and that figure scales
with how many servers and tools you attach. Measure yours rather than guessing.
Most tools can show you what they are about to send, and in Claude Code that
view is /context.
What it provides
The standard
A standard way to hand the agent tools from elsewhere: Jira, databases, browsers, internal APIs. It is how most people connect tools in Claude Code, Codex and the editor tools.
Every server you connect adds its tool descriptions to every turn, used or not.
The counterpoint
The other view
Pi leaves MCP out on purpose. Its authors argue that plain command line tools and skill files do the same job for far less.
The agent already has a command line. A README it reads when it needs to costs less than a description it carries everywhere.
The habits that work
A file the agent always reads, plans you approve, short sessions, and skills.
The file that made the biggest difference
The model forgets everything, so the only memory that survives between my
sessions is a file I maintain. Most tools read AGENTS.md or CLAUDE.md from
the repository and include it in every turn. I now treat it the way I treat
code: reviewed, kept in version control, and cut back regularly.
Belongs in the file
Earns its tokens
- Build, test and lint commands, exactly as you run them
- Conventions the model cannot guess: error handling, naming, module boundaries
- A short architecture map: where things live, what owns what
- Rules with no exceptions: "never touch migrations", "Decimal for money"
Does not belong
Wasted space
- Things the model already knows, like how Python works or what REST is
- Long essays and out-of-date design documents pasted in whole
- Anything you would not re-read every month, because it goes stale without warning
- Padding of any kind
The change that paid off most
Reviewing a plan takes me a couple of minutes. Reviewing several hundred lines of changes I was not expecting has taken me an afternoon, more than once.
- 1
Plan first, approve, and only then write code
Use plan mode or a written plan file, and approve the approach before any code exists.
- 2
Work in small steps you can check
Long runs fail quietly in the middle. Get tests passing before moving on.
- 3
Let the tests decide
"Looks right" is not a check. Let the agent run the tests and read its own failures.
- 4
Let the agent check its own work
Tests, type checking, linting, screenshots. I review the decisions and the changes, not every keystroke.
My job shifted from writing code to describing it, reviewing it and deciding.
That is not less engineering. It is the part that always took the most judgement.
Long sessions get worse
This failure is silent. As a session grows, the things you said early on drift out of the model's attention, and quality drops with nothing to warn you.
Start again rather than compact
When my direction changes I start a new session with a short summary. It also clears the model's attachment to its first idea.
Run independent work side by side
Separate tasks, separate agents, separate git worktrees. I review one while another runs.
Write it down rather than repeat it
Restating what matters works once. Moving it into AGENTS.md or a skill works every session after.
Skills
A "Skill" is a small directory of instructions that teaches an agent to do a task your way. Unlike AGENTS.md it loads only when the agent decides it is relevant, so it costs nothing on unrelated messages. That is what makes them worth writing.
pr-review-checklist/
├─ SKILL.md # required, and its description decides IF and WHEN it triggers
├─ scripts/ # deterministic steps
└─ references/ # read on demand
Prompting is for new work. When I catch myself explaining the same workflow a third time, I write it down once instead.
In my own work: PR reviews, standup notes, release checklists, postmortem templates, migration recipes.
You do not have to write them all yourself. There is an open standard at agentskills.io, a community library in obra/superpowers, a published set from Anthropic, and Pi packages on npm and git. Install one first, and write your own where your practices differ.
Two toolkits worth knowing
Both turn the practices above into tooling. I use superpowers every day, and reach for spec-kit in specific circumstances, mostly new feature development. Before you adopt either, it is worth understanding how it wants you to work, because that, rather than the feature list, is what decides whether it fits.
obra/superpowers
Skills library and methodology
The whole development cycle as skills that trigger on their own: brainstorm, plan, sub-agents, tests first, review, merge.
- Good practices you get for free, because the skills trigger on their own
- A fresh sub-agent for each task keeps the context clean
- Makes you write tests first and review before merging
- It has strong opinions, so you adopt its process, not the other way round
- Built for Claude Code first, and other tools need manual setup
- Vague requests send it round in circles
- It is a dependency, so read third-party skills before trusting them
GitHub spec-kit
Spec-driven workflow toolkit
The spec-driven approach above, turned into a toolkit: constitution, specify, plan, tasks, implement.
- Specifications live in version control and stay current, so they become real documentation
- One process across 30+ tools, which makes it usable as a team standard
- Checkpoints catch gaps in a spec before any code exists
- Too much process for anything smaller than a feature
- It turns rigid and waterfall-like if the specs go stale
- Generates walls of text you have to actually read
- Young and fast-moving, so expect breaking changes
Adoption and Risk
What I would raise with anyone rolling these tools out to a team
Four things to get right
Prompt injection is a real risk
Agents read text they did not write, from issues, web pages and package docs, and that text can carry instructions. Decide what each MCP server and tool is allowed to do, and run the agent in a sandbox where you can.
Review culture still applies
The output is a draft. Responsibility does not move to the tool, and whoever merges it still owns it. Keep the same review standards, and apply them to plans as well as changes.
Juniors still need to write code
Reading and approving code teaches you less than writing it. If juniors only ever approve what the agent produces, they never learn to tell a convincing wrong answer from a right one. That gap shows up years later, when you need seniors. Give people work they have to do by hand, and accept that some tasks are worth the slow way.
Measure outcomes rather than output
Measure delivery speed, bugs reaching production, review load, and how quickly new people get going. Not lines of code or "AI usage %", because a team produces whatever you reward.
Where to start
None of this required changing everything at once. One habit at a time, in one repository.
- 1
Write your next task as a short spec
Goal, example input, limits, and how you will know it is done. This one improved my results immediately.
- 2
Add an AGENTS.md to one repository, or cut back the one you have
Ask the agent to draft it from the codebase, then cut it in half.
- 3
Install or write a single Skill
Start with a community skill, then codify whichever workflow you repeat most.
- 4
Read your token bill
Look for the cache misses and the largest sessions. That is where I found the two habits costing me most.
The specifics here will date. The loop underneath has not changed since the first agentic tools appeared, though, and everything above follows from it. Deciding what the model should see is the part I do not expect to hand over.
That decision has a price, and the price is easier to read than most people assume. I work through the token accounting on one of my projects, and the five habits that came out of it, in The economics of an agent's context window.