Rules for AI partnership
I recently wrote about rediscovering flow through AI coding, where I mentioned developing strict rules for working with AI. Several people asked what those rules actually look like and how I figured them out. Here’s the story of how I moved from prompt engineering to context engineering out of necessity.
Living in fear of context loss
When I first started using Claude Code, I was only vaguely aware of how context worked. I’d start a session with a goal in mind and live in constant fear that the next context compaction would lose something useful. Important implementation details, design decisions, half-finished features - all of it felt fragile.
About Context
If you’re not familiar with context, or context windows, and how they relate to LLMs, here’s my rough, but mostly accurate explanation:
Context is all the stuff you tell the LLM — plus whatever it “remembers” during the session — whether you hand it over directly in a prompt, sneak it in via a tool call, or feed it through a web search.
The context window is just how much of that stuff the LLM can keep in its head at once. When it runs out of room, it does something called compaction.
Compaction is the mysterious process whereby the LLM reviews its notes and carefully throws out everything you cared about. The end result is a model that understands your problem in the same way a dog understands calculus — or in the same way I understand the internals of LLMs.
If you want the technically correct version, go read The rise of “context engineering” on LangChain’s blog.
My initial solution for saving state between compactions was to dump everything into a CLAUDE.md file in my project repo. Status updates, design notes, development rules, research findings, random thoughts - anything that seemed important went into that file. It worked pretty well initially, serving as a context cache for the stuff I cared about most. After compaction I’d just have the agent re-read my CLAUDE.md and I’d get more reliable behavior from the agent.
But the file grew unwieldy fast.
The problem with everything files
After a few weeks of different agents “updating” my CLAUDE.md file, it became a mess. Repetitive context, outdated status information, and layers of rules that contradicted each other. Worse, agents would start working on features mentioned briefly in old context that no longer existed in the code. I’d remove a feature one week, and forget to update CLAUDE.md, then I’d have some helpful future agent reimplementing the old feature, because it was still in the context I was loading with every session.
I found myself pruning the file frequently, trying to figure out what was still relevant. But I never had a clear sense of what should stay versus what should go.
Separating concerns
Pure experimentation led me to try a different approach. Instead of one giant context file, I started using different types of markdown documents:
- Short-term files (never committed) for implementation details and notes during active development
- Long-term planning docs with broader scope and less detail, often serving as checklists
- Persistent repo docs that stick around, including user-facing documentation and behavioral specifications
I’m not convinced this is the best overall strategy, but it’s worked well for plonk and some Terraform work I’ve been doing. The key insight was giving agents the right scope rather than dumping everything on them.
The behavioral firewall
This experimentation taught me that CLAUDE.md should contain only development rules - things that every agent must know. Not status, not plans, not implementation details. Just rules.
But how do you figure out what rules you need?
For me, it was pain-driven development. Every rule in my CLAUDE.md represents a specific frustrating experience where an agent did something I had to ask it to stop doing multiple times.
War story: The testcontainers disaster
I was working on integration tests for plonk. I’d explained the problem: keep tests isolated from the user’s system while thoroughly testing the CLI. We did detailed planning and came up with testcontainers-go as the solution.
I gave the agent pretty free rein to build the tests. Everything looked good until I realized what it had actually built: tests that spun up a container and then ran plonk on my local machine. The agent correctly understood the goal (isolated testing) and picked good tooling, but then failed completely to implement the tests to run in the container, which violated the constraint (don’t touch the developer’s system).
I told the agent over and over and over again, that integration tests couldn’t run on a developer’s local machine because it risked changing installed packages and dotfiles. The agent completely missed this as a critical rule, and yet when I prompted it to explain why the implementation it had created was a failure, it immediately responded that it had violated the constraint to not change a developer’s tool setup in tests.
That’s when I learned that vague warnings don’t work. You need clear, unambiguous rules.
War story: Emoji whack-a-mole
I had a similar experience with emojis. I told Claude to remove all emojis from plonk’s UI, which it did. Then it promptly started adding new ones in other parts of the code. I got it to stop adding emojis to command output, only to find emojis in all my tests. Then in all my documentation.
When I called this out, the agent explained that it knew not to add emojis to plonk’s output, but thought I was okay with them in tests and documentation. I didn’t see it at first, but I assumed that my preference would carry across the whole project, even though I had only told the agent not to put emojis in my UI. It followed the letter of the law, not the spirit.
The format breakthrough
After repeatedly having to explain the same behavioral expectations, I started working with Claude to draft clearer rules. The agent suggested formatting rules as lists of REQUIRED, FORBIDDEN, ALLOWED, and EXAMPLE statements.
In hindsight, the rules read a bit like firewall rules, which made sense. I was essentially creating access control policies for AI behavior.
Here’s what a typical rule looks like now:
### No Emojis in Output
- **FORBIDDEN**: Using any emoji in ANY output, code, comments, commit messages, or communication
- **FORBIDDEN**: Emoji in file contents, terminal output, logs, or any generated text
- **REQUIRED**: Use plain text alternatives for status indicators and emphasis
- **EXAMPLE**: Use "✓ installed" not "✅ installed"
The strong language (MUST, FORBIDDEN) turned out to be crucial. Softer language was interpreted as suggestions rather than requirements.
The meta-rule problem
Once I had a working CLAUDE.md with clear rules, a funny thing happened: agents started wanting to add more rules. They’d try to codify implementation details or add behavioral preferences I intended to last only for the current session.
This forced me to create a meta-rule, a rule about making rules:
### This File is for Development Rules Only
- **REQUIRED**: CLAUDE.md must contain ONLY development rules and guidelines
- **FORBIDDEN**: Using CLAUDE.md to store project status, todo lists, future plans, or any other context
- **REQUIRED**: Store project status and plans in appropriate files (docs/planning/*.md, TODO.md, etc.)
Apparently I needed a rule to prevent rule creep.
What’s working now
My current rule set has been stable through several weeks of development on plonk. The rules cover things like:
- Never adding unrequested features (my biggest frustration)
- Safety constraints for testing (don’t break my environment)
- Output formatting standards (no emojis, professional tone)
- File creation preferences (edit existing rather than create new)
- Scope boundaries (do exactly what’s asked, nothing more)
The key insight is treating your rule file as a small but critical set of behavioral constraints, not a dumping ground for context.
Cross-project lessons
I’ve been using GitHub Copilot with the Claude model at work, but I use the same general pattern: separate concerns between rules (loaded automatically) and everything else (referenced as needed).
Some rules seem universal, like not adding unrequested features or maintaining professional output. Others are domain-specific, like plonk’s safety constraints around package management.
Practical advice
If you’re starting with AI coding, here’s what I’d suggest:
- Start with scope, not rules - give agents clear boundaries for what they should accomplish
- Let rules emerge from pain - don’t try to anticipate every problem, just solve the ones you actually encounter
- Use absolute language - MUST and FORBIDDEN work better than “please try to”
- Separate rules from everything else - context, plans, and status belong in other files
- Expect meta-problems - you may need rules about making rules
The most important thing is giving agents the right scope. What seems to make AI collaboration most effective is management of context. If I want more exploration I might provide fairly limited context, but when I am implementing code, I need predictability over novelty.
Looking ahead
At the rate LLMs are advancing, this whole approach might be moot in a couple months. Better context management, more sophisticated reasoning, who knows what’s coming.
But for now, treating AI partnership like any other engineering problem, with clear interfaces, defined behaviors, and explicit constraints, has made the collaboration both more productive and less frustrating.
The rules aren’t about limiting AI creativity. They’re about creating a framework where that creativity can be productive rather than chaotic.