← All writing
Singy Songbook · 4 of 12
May 3, 2026 · 5 min read

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 300 was 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.