DEV Community

Cover image for Claude Code, Beyond the Prompt — Part 1: Stop Explaining Your Project Twice (The Two-File Memory System)
Giulio D'Erme
Giulio D'Erme

Posted on • Edited on

Claude Code, Beyond the Prompt — Part 1: Stop Explaining Your Project Twice (The Two-File Memory System)

Part 1 of Claude Code, Beyond the Prompt — patterns from running a live automated trading system on Claude Code. Start with the intro.


You know the loop.

You open Claude Code. You explain your project — the stack, the conventions, the module that's half-refactored, the database that's named something weird for historical reasons. Claude gets it. You do good work for an hour. You close the terminal.

Next session: blank slate. All of it, gone. So you explain it again. And again the session after that.

Most people accept this as the cost of working with an AI. It isn't. Claude Code has a memory system built in. The problem is that a lot of people don't use it — and most of the people who do use it wrong, by dumping everything into one file that slowly rots into something nobody trusts.

The fix is almost embarrassingly simple. It's the highest-return change in this whole series, and the first version took me about ten minutes. Here it is.

Claude reads a file before every session

Drop a file called CLAUDE.md in your project root. Claude Code loads it into context automatically at the start of every session. Whatever's in it, Claude knows — without you saying a word.

That's the entire mechanism. (There's also a user-level file at ~/.claude/CLAUDE.md for things that apply across all your projects, but project-level is where the action is.)

So the real question was never "how do I make Claude remember." The mechanism is already there. The question is what you put in it — and that's where almost everyone goes wrong.

The mistake: one file for everything

The natural instinct is to put everything in that one file:

We use Postgres, never SQLite. Tests run with make test. Currently mid-refactor on the payments module. Staging DB is down. TODO: migrate the auth service.

Looks reasonable. It's a trap.

You've just mixed two things with completely different lifespans. "We use Postgres, never SQLite" is true for a year. "Currently mid-refactor on payments" is true for three days. "Staging DB is down" might be false by this afternoon.

When they share a file, two things break:

  1. The stable rules get buried in a stream of transient status updates.
  2. The whole file loses trust. When half of it is stale — wait, is the payments refactor still happening? — you stop believing any of it. And a memory you don't trust is worse than no memory, because now you have to re-verify everything anyway.

The fix: split by how often it changes

Two files:

  • CLAUDE.md — the stable stuff. Rules, architecture, conventions, hard do-nots. Changes maybe once a month. This is your project's constitution.
  • MEMORY.md — the dynamic stuff. What's in flight right now, what changed recently, what's next, deadlines. Changes every session.

The split is not by topic. It's by change frequency. That's the whole trick.

Now each file has a clear update cadence and a clear trust level. CLAUDE.md is stable ground truth. MEMORY.md is "here's the current situation, and it's expected to move." You never again have to wonder whether a line is still valid — the file it lives in tells you.

The 10-minute starter

Copy these. Fill in the blanks. You're done.

CLAUDE.md

# CLAUDE.md — <Project Name>

## Stack
- <language / framework / database / key libraries>

## Conventions
- <how you name things, how files are organized>
- Run tests with `<command>`

## Rules (do / don't)
- Use <X>, never <Y> — because <reason>
- Never touch <thing> directly
- <the mistake you keep having to correct>

## Architecture (the 30-second version)
- <entry point><core module><data layer>
- Key files: <file  what it does>

## Memory
- Read MEMORY.md at the start of a session for current state.
- Update MEMORY.md at the end of any session that changed something.
Enter fullscreen mode Exit fullscreen mode

MEMORY.md

# MEMORY.md — current state

## Now
- Working on: <the thing in flight>
- Watch out for: <this week's gotcha>

## Recently changed
- <date>: <what changed>

## Next
- <what's queued>
Enter fullscreen mode Exit fullscreen mode

That last block in CLAUDE.md — the one telling Claude to read and update MEMORY.md — is what turns two static files into a system. (The habit that keeps it honest is the entire subject of Part 2.)

How I scaled it

My CLAUDE.md is a genuine constitution: rules, architecture, and the critical do-nots — the things that would cause real damage if forgotten. Which files are the live ones versus one-off scripts. What must never be run against production. The deploy discipline. It changes maybe monthly, and every line earns its place.

My MEMORY.md is a living dashboard. A Now section lists every active workstream in one line each. A small table tracks upcoming deadlines. An archive holds the resolved stuff. It changes every single session — because in a system with a dozen things running and experiments that resolve on specific dates, "current state" is the product.

Under that sits a second tier: MEMORY.md is an index — one line per topic — and the deep context for each topic lives in its own file, loaded only when it's relevant. That keeps the always-loaded file skimmable while the depth stays one link away. (Why "skimmable" matters for your token bill is Part 7.)

The five rules that make it work

The templates are easy. These are the lessons that took me longer, and they're what separate a memory system from a text file that rots:

  1. Split by change frequency, not by topic. This is the core. If you remember one thing, remember this one.

  2. Give each file a cadence — and close the loop. A memory file only works if it's current. I update MEMORY.md at the end of every session that changed something. Skip the habit and the file rots within a week, and you're back to re-explaining everything. (Part 2 is entirely about making this automatic.)

  3. One fact, one place. Link, don't duplicate. The moment the same fact lives in two files, they drift — and now you don't know which is true. Write it once; reference it everywhere else.

  4. Have a precedence rule. When the stable file and the dynamic file disagree, the dynamic one wins — it's newer by definition. Put that rule in writing so Claude knows which to trust in a conflict.

  5. Treat memory as a snapshot, not gospel. This is the one people miss. A memory file records what was true when you wrote it. Code moves. Before Claude acts on a remembered fact — "the config flag is called X" — have it verify against the actual code. I keep a literal line in my CLAUDE.md: before citing a memory fact in an action, verify it with a read. It has caught a dozen would-be mistakes where a remembered detail had quietly gone stale.

What it's worth in practice

Not "10x productivity." I'm not going to insult you with that.

Concretely: you stop re-explaining your project. Claude's first suggestion in a session is already context-aware instead of generic. Reviews land closer on the first try because it knows your conventions. And — a bonus I didn't expect — the file becomes genuinely useful to you. MEMORY.md is the fastest way for me to remember what I was doing three days ago.

It isn't magic. It's a text file Claude reads. But it's the difference between an assistant with amnesia and one that picks up exactly where you left off.

Next

A memory file is only as good as your habit of keeping it current — and "remember to update it" is not a habit, it's a wish. Part 2 is the ritual that makes it automatic: a session start-and-close routine that grounds Claude in current state before it touches anything, so precision comes from state rather than from hoping you wrote a good enough prompt.


Part 1 of a series on running real systems on Claude Code. The deeper agent-memory research is open source — see RE-call. Part 2 is next.

Top comments (4)

Collapse
 
sarracin0 profile image
Raffaele Zarrelli

The change-frequency split is the right fix for the first failure mode, rules and status sharing one file until you stop trusting either. There is a second failure mode a few months further out, inside MEMORY.md itself: not every line in the Now section keeps the same trust level either, some entries are still open, some got resolved and never made it to the archive, some got quietly superseded by a later call and nobody deleted the old one. The two-file split fixes topic drift, it does not fix stale-within-the-dynamic-file, that needs a status per entry (active, superseded, resolved) so the agent reading MEMORY.md at session start knows which lines are still binding instead of you re-verifying by memory. That is the piece cowork-os adds on top of exactly this pattern, decisions carry a state and a review point instead of just living in a file that is dynamic by default.

How do you currently catch an entry in Now that quietly stopped being true, at the next update, or does it sit there until something breaks?

Collapse
 
gde03 profile image
Giulio D'Erme

Good catch, and you're right that Part 1 doesn't solve it. The two-file split is deliberately the on-ramp it fixes rules-vs-status and stops there.

To answer directly: not at the next update. "I'll notice when I update it" is a wish, not a mechanism.

What actually catches it is that the state lives outside the file. Every open entry in my Now section is backed by an issue with a decision date and a scheduled job that fires on that date, posts the verdict (pass / fail / needs-more-data) as a comment, and closes it. The schedule is a deadman switch, an entry can't quietly outlive its review point, because the review point is an external timer, not a line I have to remember to re-read. Session start surfaces whatever is due today; session close archives whatever resolved (that's Part 2). Superseded decisions get moved to a separate closed-hypotheses index that the agent checks before re-proposing anything.

So: active / superseded / resolved yes, but as issue state with a timer, not a status field in the markdown. "Decisions carry a state and a review point" is the right shape; I got there from a different direction.

Where it still fails, and this is the interesting part. I had an experiment sit at "needs more data" for weeks. The entry's status was honestly active. The gate had a date. The scheduled check ran on time. What had died was the collector feeding it silently, weeks earlier. The status was correct and the entry was worthless.

A per-entry state wouldn't have caught that, because the gap wasn't between the entry and its status. It was between the entry and reality. Which is why the last rule in the post is the load-bearing one: treat memory as a snapshot, verify against the source before acting. Status tells you what you believed. Only a read tells you what's true.

The retrieval-side version of your point, surfacing closed decisions before the agent re-proposes them, is the piece I ended up building separately, in RE-call. That one I agree is essential, and the file alone won't do it. Further infos later in the serie

Collapse
 
sarracin0 profile image
Raffaele Zarrelli

The deadman switch framing is the sharper version of what I was gesturing at, a scheduled check beats a status field every time because it does not depend on anyone remembering to look. The collector case is the real teeth of it though. That entry was internally consistent, correct status, review point respected on schedule, but externally stale, and no amount of per-entry state catches that because the failure sits one layer below the entry, in whether the thing feeding it is still alive. That is close to why we treat memory files as something you diff against source before acting on, not something you trust because the last write looked right. Do you run any liveness check on the collectors themselves, a heartbeat or a last-write timestamp you alert on, or is a collector going silent still a class of failure you only catch when the numbers look wrong to a human?

Thread Thread
 
gde03 profile image
Giulio D'Erme

Partly, and the line it splits on is embarrassingly revealing: money.

Anything on the money path has liveness. Unit status is batched and checked at every session start, as part of a mandatory spine that runs even for a one-line question, and a daily monitor pings me on Telegram if a unit is dead or the P&L bleeds past a threshold. Those I hear about in hours, not weeks.

The memory index has it too, and it's the closest thing to what you're describing: session start queries the last-indexed timestamp on the memory corpus and warns if it's older than two days. A last-write staleness check, exactly.

The research collectors, the ones feeding the gate entries, had neither. I can't dress that up: the probe's unit had been disabled, and because it wasn't money-path it wasn't in the liveness batch. So to your question directly: yes, that class of failure was caught by a human noticing the sample count hadn't moved.

But here's the part I think generalizes, and it's your original point one layer down:

The deadman switch has the deadman's failure mode. My scheduled check read a value sample count below threshold → "needs more data." That verdict is bit-for-bit identical for a healthy-but-quiet collector and a corpse. The check was honest, on time, and blind, because it asserted on the number and never on whether the number was still being written.

So the fix isn't "also monitor the collectors" as a separate concern. It's that every scheduled check must assert on the liveness of its own input, not just its value max(created_at) against expected cadence, alongside count(*). "Insufficient data" and "no data arriving" have to be different verdicts. Collapsing them is the bug: a gate that can't tell them apart will renew a dead experiment forever, politely, on schedule.

Same reason freshness is a first-class returned signal in RE-call rather than something you check separately, a retrieved memo comes back with its age attached, because "I found something" and "I found something current" are different claims.