Skip to content
← Back to blog

The Uncomfortable Reality of Reading Over a Year's Worth of My Own Conflicting Rules

AI ToolingDeveloper ExperienceConfigurationContext Engineering

Three times in one week the agent did something I was certain I'd told it not to do. Three times I went looking for the rule, and three times I found it. Written by me. Sitting next to another rule, also by me, that said something slightly different. By the third one I had stopped blaming the model.

That was the week I admitted the mess was mine.

A year of writing rules on top of rules

Since January the way I use Claude has grown: hooks, skills, plugins and a memory system, none of which existed as a single structure when I started. Underneath all of this, the model has changed generation three times. Every generation was slightly different, and every one added a layer to my configuration without removing the layer before it. Memory is keyed by working directory, and I had fifteen folders of it. Rules I'd written in one project got rewritten slightly differently in the next.

The way I work has also changed shape this year. One session has become several, each with its own brief and its own branch. Review has moved from reading diffs to reading plans. Memory has gone from a nice idea to the thing holding it all together, and I've kept adding to it the way you add to a kitchen drawer.

In July I wrote that a correction you write down once stops coming back. That held for the example I used then, a raw colour value in a class name. It does not hold for plenty of others, the tired old em dash included. There is a slight nuance at play here as well, I think. With the colour, the agent is thinking about the design when it makes the decision, and a sentence of prose can change its mind. A dash is a reflex. It happens while the text is being produced, below the level where instructions live.

What the clean-up found

This whole ordeal prompted something out of me I never expected, which was to... run my own retro with Claude. As part of this "we" analysed all my historic memory files... a few hundred of them, and judged each one: still true, superseded, or contradicting another. The sprawl was expected, my contradictions... were not. Three rules about code comments, each with a different limit. A rule about which test command to run that disagreed with the repository's own instructions file. The agent picked whichever it had read most recently. The same preference about pull request descriptions written three times, and each version had a different shape.

My favourite was a rule forbidding real vendor identifiers in test data, which contained a real vendor identifier, used as the example of what not to do. Reading it felt like reading my own code from two years ago. I couldn't argue with any of it, and I couldn't explain how I'd ended up disagreeing with myself either.

Another slap in the face was that none of this was being enforced by anything either. "Enforced" had meant "the model remembered", and the model "remembered" the way a tired colleague remembers.

The rules that stopped being broken

Rather than write the dash rule a fourth time, I built a tool. Before any commit or pull request command runs, a short script I wrote reads the text about to be sent and refuses the call if it finds a dash or one of those "generated with" attribution lines. Twenty lines of Python. It has not been broken since. It can't be.

Then the memory folder got the same treatment. Every file now needs a scope tag before it can be saved, and a check runs on every write. No tag, no save.

The pattern has sparked some good introspection about my own decision-making process. If I'd written a rule more than twice, the rule was fine and I'd been putting it in the wrong place. I think I've come to realise that memory really just holds the things that need judgement (how I like a hold phrased, when to push back, what a good commit split looks like). Anything that is really a reflex should go in a hook, where the model's attention is not part of the equation.

Where the workspace ends

The second thing the clean-up surfaced was about where my setup lives. It should survive a new laptop and a change of tool, and the way I work should outlast any one job.

For most of my career my editor configuration followed me between environments. A keymap, a theme, a handful of snippets. Nobody worried about it. It only ever knew things about me. Agent memory is not like that. It learns how I like to work and who I work for in the same sentence. A rule about pull request titles carries the ticket format. A note about naming carries a product's vocabulary. The useful half and the confidential half share a paragraph, which I find to be a genuinely new corporate issue.

My answer was to stop treating memory as one pile. Every file now carries a scope: portable, work or personal. Portable is how I work. Work is anything about the employer, its people, its products or its code, and it stays exactly where it is, in the project folders on the work machine. If I can't classify something, it fails closed and defaults to work. The split is enforced the same way the dash rule is. A file without a scope can't be saved, and the only thing that ever moves between machines is the portable set, after a lint against a denylist of names and terms, a secret scan, and a read by eye. A denylist only knows the names you remembered to list, which is why the eye still matters. The convention and the three scripts are on GitHub as portable-agent-config, if the split is useful to you.

What travels

The portable layer is plain markdown now. Nothing in it is tied to Claude, which was the point. The hooks are a different story. They are the part that makes the setup trustworthy, and they only run in Claude Code. That leaves an odd split. The half I could take with me is the half that only works when the model feels like following it. The half that actually holds is the half I'll one day have to rebuild, which is something I'm thinking about solving currently.