Explicit Guardrails Become Persistent Guardrails
Day two: I asked Claude to add an API endpoint. It came back saying "Done — I also cleaned up some tests I noticed and refactored the storage service to use dependency injection while I was there."
The endpoint was what I'd asked for. The storage refactor wasn't, and now I had three concerns mixed into one PR.
I said: "Don't do that. Only do what I asked. If you notice other things worth doing, flag them, don't roll them in."
Claude agreed. Behaved that conversation. Behaved the next.
Three days later, in a different conversation, it drifted back. Small "while I was there" refactor on a different change. Nothing harmful, just not what I'd asked for.
Why corrections in chat don't persist
Each new conversation starts with no memory of the previous one. The rule I set Saturday lived inside that single conversation and went with it when I closed the tab.
Memory files solve this. A short text file in a specific folder; Claude Code reads every file in the folder at the start of every conversation.
I added user_working_style.md:
- Don't add features, refactor, or introduce abstractions beyond what the task requires. A bug fix doesn't need surrounding cleanup; a one-shot operation doesn't need a helper.
- Ask before implementing anything non-trivial. Trivial (typos, label tweaks, a new field on an existing endpoint) — just do. Non-trivial (new tables, cross-cutting refactors, multi-part changes) — sketch the plan and get a nod first.
Closed the conversation, started a new one. Asked for another endpoint. It came back — just the endpoint. At the bottom: "I noticed the storage service could use some refactoring for testability, but that seemed out of scope — flag for a future PR?"
Stayed that way across every subsequent conversation.
The signal for what to write down
If I was correcting the same behavior for the second or third time, it was memory file material.
Examples that ended up in the folder:
- "I'm new to git. Walk me through the why, not just the command." Written after the third time I asked "what does that command actually do?"
- "Push the branch, let me merge it in GitHub. Never merge from the command line." Written after one near-miss where a CLI merge collided with a simultaneous GitHub UI merge.
- "Boot the server for real before merging changes that touch startup." Written after the outage in post 01.
- "Don't use round-number sleep timers — use either short (< 5 min) or long (20+ min), nothing in between." Written after noticing
sleep 300was the worst possible value.
Each one was a specific recurrence. None happens anymore.
File structure
---
name: Short title
description: One-sentence hook, specific enough for Claude to decide if this is relevant
type: feedback | user | project | reference
---
The rule.
**Why:** The reason the rule exists, often a story about a past incident.
**How to apply:** When this kicks in during a conversation.
The "why" matters more than I expected. A rule without a reason becomes dogma — Claude follows it blindly and can't tell when it shouldn't apply.
Concrete: the rule "migrations belong in the Dockerfile, not FastAPI startup" with the reason attached ("startup timing exposed a latent deadlock"). Later I asked about a different startup task — loading a machine learning model on app boot. Claude proposed doing it in startup and noted: "the migration rule doesn't apply here because this isn't a migration and won't deadlock." That judgment only worked because the reason was attached, not just the rule.
What I'm doing now
When I find myself typing "no, do it this way instead" — I treat that as a draft of a future memory file. First time is a one-off correction. Second time is a pattern. Third time, it should already be in a file.
Memory file count went from two to eight over the first four days. Each removes a class of friction.
Takeaways
- Corrections in chat fade with the conversation. Corrections in memory files persist across every future session.
- Second or third repetition of a correction is the signal — that rule belongs in a file.
- A rule with a reason allows judgment calls. A rule without one becomes dogma.
- Memory file structure: rule, Why (often a past incident), How to apply (when it kicks in).
Terms
Pull request (PR) — A proposed batch of code changes. On GitHub, opening one creates a review surface — a list of what changed, comments, and a Merge button.
Diff — The set of additions and deletions a pull request introduces. Read this before merging.
Refactor — Restructuring code without changing what it does. Often done to improve readability or testability, but a refactor can also introduce risk if it ships alongside actual feature changes.
Dependency injection — A pattern where a class or function receives its dependencies (other classes, services, configurations) as arguments rather than creating them itself. Makes testing easier because you can pass in fakes.
Memory file — Plain text file Claude Code automatically reads on every new session. Records preferences and lessons so they persist across conversations.
FastAPI — The Python web framework the backend is built with. "FastAPI startup" refers to the framework's lifespan hooks: code that runs when the server boots.
Dockerfile — A text file describing how to build a container image: what to install, what files to copy, what command to run on startup.
CLI — Command-line interface. The terminal. "Merging from the command line" means running git merge in the terminal rather than clicking the Merge button on a GitHub pull request page.