Context Diet: putting your global instruction file on a diet

We keep adding, and almost nobody looks back

If you work alongside an AI, you have run into the same question everyone does: how do you set the rules for the agent working next to you. Every tool’s answer looks about the same these days. An instruction file for your standing rules (Claude Code’s CLAUDE.md, Cursor’s rules, Copilot’s instructions), plus a set of skills you can pull in as needed. You write down the things you would otherwise repeat, the AI reads them every time, and with luck it does things your way.

Then the articles start showing up. This skill from some big name is incredible, you have to install this plugin, write your instructions like this and you will never get a bad output again, a hundred thousand people were stunned. Those headlines keep coming, and some of them really do hit a need you have, or look close enough. I am generally in favor of trying things when there is no harm in it, and I have installed plenty myself.

The problem is that if you never look back, your instruction file and your skill list only ever get longer.

I split this cleanup into two posts. This one is about the instruction file, the skill list comes next. They are worth separating because the evidence you judge them on is completely different, and mixing them means switching subject every other paragraph.

They deleted 80% of their own

Anthropic published The new rules of context engineering in July. In it they say they removed over 80% of Claude Code’s own system prompt with no measurable loss on their coding evaluations. Note the qualifier in the original: this was done for the Claude Opus 5 and Fable 5 generation of models, not as a universal ratio.

Worth sitting with for a second: same results, far fewer tokens. Their explanation is that newer models were being over-constrained, and that giving them room to judge beats enumerating rules, with the details moved somewhere they load only when needed.

I recommend reading it. And it is worth asking yourself what your first thought is when you finish. Mine was not “let me go cut mine too”. It was: how do I work out which parts are surplus, and how do I trim without damaging what it was doing for me, the boundaries and behavior I actually rely on.

Three layers, and only one of them is yours

Before talking about cutting, be clear about what you are cutting. What an agent reads falls into roughly three layers:

Layer Written by Can you change it When it loads
System prompt The vendor Basically no (Claude Code lets you append with --append-system-prompt, but not replace) Always there
Instruction file (CLAUDE.md and friends) You Completely In full, at the start of every session
Skill You, or anyone whose work you found Completely Description always resident, body loads on trigger

This whole post is about the middle layer. Skills get the next post. The first layer is not ours to touch, but it is the baseline every judgment is made against, so it is worth defining.

A system prompt is the block of instructions the vendor writes ahead of time: who this agent is, what it can do, how it should talk. It is usually not published, but this time there is a real one to look at, because that Anthropic post quotes a section of Claude Code’s system prompt before and after the rewrite. The old version:

In code: default to writing no comments. Never write multi-paragraph docstrings or multi-line comment blocks — one short line max. Don’t create planning, decision, or analysis documents unless the user asks for them — work from conversation context, not intermediate files.

The new version replaces the whole thing with one line:

Write code that reads like the surrounding code: match its comment density, naming, and idiom.

That contrast is the point of this post: the old version enumerates rules, the new one leaves room to judge. It is what a generation of model improvement buys you, and it raises the question this post is really about. How do we adjust our own global instruction files to match what the current models can do, instead of keeping them tied down? I will get to that.

Instruction files go by different names in different tools, but one trait is enough to recognize one: a reference document the user can edit, and one that gets loaded at the start of every session. CLAUDE.md, Cursor’s rules and Copilot’s instructions all work this way.

The system prompt example above happens to describe exactly the shift I mean. Go back and read your own instruction file. If you have written things like “don’t add unnecessary comments” or “don’t generate a pile of planning documents”, those are already in force upstream, and writing them again downstream just means paying twice. You were not wrong to write them. They simply stopped being yours to write.

When does an instruction file load? The documentation is explicit: it is loaded into the context window at the start of every session (Memory). Which is to say, whatever you happen to be doing this time, all of it is in there.

So when should you review it? That is genuinely hard to answer. You notice a need and you add something, but almost nobody thinks “the models seem to have gotten better lately, maybe I should trim the file”. Which is why it only ever grows in one direction.

First, measure, and measure per section

To improve something you have to be able to observe it. Measure once before, cut, measure again after. That is the most basic version of the scientific method: a repeatable, consistent standard to evaluate against.

How to measure

/context gives you a total, but you cannot cut a total. You need to know which section is eating it. So take one more step: split the file on markdown headings, estimate each section, sort them. The expensive ones are where the knife goes. Any agent will write you a dozen lines that do this. Mine looks roughly like this:

import re, sys, unicodedata

# Calibrate these two. See the second point below.
WIDE, NARROW = 1.4, 0.56

def estimate(text):
    wide = sum(1 for ch in text if unicodedata.east_asian_width(ch) in ('W', 'F'))
    return wide * WIDE + (len(text) - wide) * NARROW

def sections(path):
    lines = open(path, encoding='utf-8').read().split('\n')
    cur, buf = '(preamble)', []
    for line in lines:
        if re.match(r'^#{1,3} ', line):
            if buf: yield cur, '\n'.join(buf)
            cur, buf = line.strip(), [line]
        else:
            buf.append(line)
    if buf: yield cur, '\n'.join(buf)

for path in sys.argv[1:]:
    rows = [(estimate(body), name) for name, body in sections(path)]
    for n, name in sorted(rows, reverse=True):
        print(f'{n:7.0f}  {name[:60]}')
    print(f'{sum(n for n, _ in rows):7.0f}  == {path}\n')

Two things are worth saying up front.

The first is that any file pulled in by an import has to go in too. Measuring only the file you opened misses everything it imports, and that is usually the single largest block. The @ section below explains why.

The second is that a home-made estimator is worth nothing until it has been calibrated against a real number. Counting characters is an approximation by construction, and the coefficients in my first version were off by a factor of two in the direction of “your file is fine”. I only found out by checking them against /context. So the real process has two layers: /context gives you a total you can trust, the script gives you the relative weight of each section, and you read them together. On its own, the script will give you a confidently wrong idea of how big your file is.

One test: take it out, does the behavior change

The easiest mistake to make once you have numbers is to start competing on how short you can get. Would writing no instructions and installing no skills be the shortest of all? Yes. Is it what we want? Obviously not. (There is a ready-made example: OpenAI says its own models, chasing a good score on a security benchmark, escaped the test sandbox and broke into Hugging Face’s production systems to steal the answers. Researchers call this reward hacking. It makes a decent case for getting your evaluation criteria right. We make the same mistake ourselves: once “shorter is better” becomes the target, cutting a rule that actually changed behavior starts to look like progress.)

The test I used is this: take this line out, and does the agent’s default behavior change in any noticeable way? If not, it can safely go. Run that over the file and every section lands in one of three piles.

Keep: things the model would not know on its own and that matter to how I work. Never let a single file grow unbounded. Never ship emoji characters in a deliverable, draw inline SVG instead. Keep UI copy to what the end user needs to know. These are things I have corrected repeatedly, and the repetition is itself the signal: if a user has to keep saying it, the model has no default to fall back on, so it needs to be written down.

Demote: useful in specific situations, loaded in all of them. Grading tables, format templates, implementation specs. Not worthless, they just do not need to be present every time. This has a name, progressive disclosure: the resident layer keeps only the routing, the detail sits where it can be read on demand. It is the same thing the Anthropic post means by moving details somewhere they load only when needed, and the same thing as the conditional pointer in the @ section below.

Delete: already stated somewhere else. Two sources, mostly. Either I had already written it in another file, or the model’s system prompt now says it better than I did.

This pass is the agent’s work, not mine

Counting tokens per section is the script’s job, but the judgment call, “take it out, does the behavior change”, is the agent’s. My part is the instruction and the final say. It makes sense to delegate that particular call because the agent can see something I cannot: its own system prompt, right there in its context. So when I ask “if this rule goes, does your default behavior change”, it is comparing rather than guessing. For lines like “say so when you don’t know” or “be honest when you can’t reach a source”, it can point straight at the default instruction that already covers them. I have no way to verify that myself.

The asymmetry is worth stating plainly: the agent can read that system prompt and you cannot. It is in its context, so it can go through your file line by line against it. You generally cannot get hold of that text, and the agent will not reproduce the whole thing for you either, since it is not its to hand out. Which makes this one specific question, “is this rule of mine already covered by the defaults”, better delegated than done yourself. It has the control group and you do not.

Do not mistake that advantage for a guarantee. When it says “that one is already covered”, it might have compared carefully, or the two might just read alike. That is exactly how my estimate ended up predicting more cuts than I made: on the first pass it swept whole sections and said “this sort of thing is covered by the defaults”, and only line by line did it turn out that my wording was more specific than the default. Scanning and verifying have to be two separate passes, and the first pass is a hypothesis.

What I actually asked for looked roughly like this:

Measure these files section by section. Then for each section judge three things. One, if it were removed, would your default behavior change. Two, is this already stated in another file, and go open that file to check rather than going on impression. Three, is it only useful in specific situations. Sort them into keep, demote and delete, with a reason and an estimated saving for each. Change nothing yet, I will look first.

That last sentence in bold is the point. Keep the scan and the cut separate, with a gate you control in between. Otherwise what you get back is a file that has already been fixed for you, and you have lost the chance to judge whether it cut the right things, or even to see what changed and why.

Two things I actually got wrong

@ is one character, and it is not a pointer

My instruction file had a line saying “details in @some-file.md”. I thought I had left a pointer.

I had not. In a Claude Code memory file, @some-file.md pastes that file’s entire contents into every session. The same path without the @ is the actual pointer, and costs nothing until something goes and reads it. The documentation puts my misunderstanding in one sentence: splitting into @path imports helps with organization but does not reduce context, because imported files load at launch (Memory).

One character of difference, and you have quietly added a whole extra file. The entire point of pointing at a file instead of inlining it goes away, and you load the whole thing anyway.

The fix is easy. Drop the @, and add the condition that triggers it: “when X comes up, read that file first”. A pointer with a stated condition routes just as reliably as an import, and costs one line.

Some things are worth paying for every single time

One class of rule behaves differently from the rest: the ones you cannot undo. Deleting files, deploying, changing permissions, spending money.

Push “the resident layer is only pointers” all the way down and the details of these rules should be demoted too. But that produces a genuinely bad outcome: the prohibition is missing at exactly the moment it is needed. The reason an agent does the thing it should not do is that it does not know it should not. And not knowing, it will not go and read the file that says so.

So for these the rule inverts. For anything with irreversible consequences, the trigger conditions and the list of what is forbidden stay resident. Only the procedure gets demoted.

Deletion, for example. What stays in my resident layer is the principle itself: do not delete blindly, search first, delete only what the search matched, and if nothing matched then nothing gets deleted. It has to be present at the moment the agent is about to act. How to list the affected files, what format to show me, that is procedure and can be read when it is needed. Deploying works the same way. “Do not deploy unless I say so” stays resident, the deployment steps do not.

What the estimate said, and what actually happened

Before cutting, the AI estimated that about 40% could go. What actually came out was a bit over 30%.

I had it record its per-section judgment and the amount it expected each one to save. Measuring again afterwards with the same script and putting the two numbers side by side tells you how far off you were and which category you were off in.

The gap was concentrated in two sections of the instruction file, the one about how I want to be communicated with and the one about destructive operations. Skimming at altitude, it looks like a fair amount of that might be covered by the model’s defaults. Reading into the detail, those entries turned out to be more specific than the default. Things like “give me the conclusion before the reasoning”, or “when I am stuck between options, don’t just list pros and cons, name the criterion that actually decides it”. The system prompt’s defaults sit at a higher level than that.

The likely cause is that the scanning pass sorts by whether a line reads like a generality, and only the line by line pass verifies it. On that second, closer pass it moved me from cutting more to cutting less. It is the opposite error that should worry you: believing you only trimmed a little and finding out later that something behavior-changing went with it, which you only discover the day the agent does the wrong thing.

What came out

The resident cost dropped by a bit over 30%. What actually got deleted was roughly these, all from the instruction file:

  • A line pointing at a particular repo, when that repo’s own instruction file already opens with the same sentence.
  • A whole section of filing rules, when the authoritative version was already in another repo’s guidelines.
  • A paragraph explaining, for human readers, why a rule had been put there. The resident layer is not where that belongs; it was probably typed in while I happened to be editing that file.
  • Several rules along the lines of “say so when you don’t know”, “be honest if you can’t reach something”, “don’t make things up”. The current system prompt covers all of it, and puts it better than those lines did.

The compressed material is harder to describe, but it is where most of the saving came from, so here are two:

  • A rule that used to carry its implementation spec in the resident layer (which header to set, what value to give it). Now it keeps one line of principle and a path to the authoritative document, read only when someone is actually doing the work.
  • A finely detailed decision procedure, compressed to one sentence and the name of a skill. The criteria were already written out in full inside that skill; repeating them in the resident layer just meant maintaining both.

The common thread: most of what came out was duplication, not error. All of it was correct when it was written. It simply got written down somewhere else afterwards. Ordinary development produces this naturally. A local rule gets judged general and promoted up to the global file, a global rule turns out to be less general than you thought and goes back down to the local one. That two-way movement is knowledge settling into place. The problem is that the move is usually only half done: the new location gets written, the old one never gets cleared, and the same thing loads twice.

Nothing has degraded since the cut, though by the logic above that is exactly the kind of thing that takes a while to surface, so I have kept the backups and I am watching. As for the payoff, the tokens saved turn out to be the smallest part of it. The resident layer was never a large share of the context window to begin with. The real gains were elsewhere: the rules no longer duplicate and contradict each other, I know again what is actually in that file, and every line still in it has been through a check.

One caveat before you go

Everything above rests on a premise: how much you should keep depends on the tier of model you are using.

It is a bit like students. A struggling one needs more books, more references, more finely broken down steps to get the work right. A gifted one hears “do it this way” and that is enough, and handing them the whole reference library just gets in their way.

Models are the same, and they change tier every few months. So the real question is not whether to cut. It is how you know this particular student already understands, and no longer needs you to keep pouring it in. There is no permanent answer to that, only measuring again periodically, which is the method this whole post is about. My own feeling is that every month or two, or whenever a model gets upgraded, is a reasonable point to look. In an environment where the tools change this fast, reviewing your own process, and the references and rules it leans on, is what keeps your workflow from being dragged along by rules that stopped being true.

A skill you can run yourself

If you only do one thing, run /context once and look at what the memory files row costs you. Then decide whether any of the rest is worth your time.

The checks in this post are packaged as a skill: steven-skill-deck / context-diet. You can point an agent at it and have it go through your resident files item by item: which pointers are actually imports, which prohibitions must not be moved, which entries change nothing when removed, and what each section costs. The full version of the estimator above is in there too.

Installing it works like any other skill. Put that directory where your skills live (~/.claude/skills/), and prefer a symlink over a copy so that a git pull actually updates it.

The skill is itself built on progressive disclosure: SKILL.md holds only the shared procedure, and the specifics for the instruction half and the skill half sit in references/, read only when that half is running. Otherwise it would become exactly the problem this post is about.

It measures first, classifies second, then puts the proposal in front of you and waits. Nothing gets changed until you say so, and it backs the files up before it touches them. I plan to re-run it periodically, because that file is not going to get smaller on its own.