DEV Community

Cover image for Run -> Log -> Distill: a self-improving memory for AI-assisted development
vavilov2212
vavilov2212

Posted on

Run -> Log -> Distill: a self-improving memory for AI-assisted development

Every AI coding session starts with amnesia.

Yesterday the agent spent two hours hunting down a state-sharing bug. Today it will happily reintroduce that same bug, in the same codebase, with the same confidence. The context window closed, and everything the agent learned went with it.

I'm building a full-stack GraphQL store (Next.js + Keystone 6 + PostgreSQL, dissected in the rest of this series) almost entirely through AI-assisted sessions in Claude Code. A few sessions in, I got tired of the amnesia and changed the workflow itself. I now run every task with Run → Log → Distill, built from these ideas:

  • while the agent works, it appends to a WAL (a write-ahead log: the work journal);
  • when the work is done, a compaction step melts that log into a post-mortem: a small file of rules that every future session must read before writing code.

This pattern is being reinvented all over the industry right now. The tools are shipping it as a native feature: Claude Code auto memory, Cursor Memories, Devin's Cascade Memories. Agents that write reflections on their own failures and reread them on the next attempt are described here: Reflexion or here compounding engineering.

The pattern described in this article moves with you between tools, works today in any of them, and nothing enters long-term memory without your review. More links in prior art at the end.

Below are three prompts you can copy as-is.

Table of contents

Chat history is not knowledge

Most AI coding setups already have some persistent context. Claude Code reads CLAUDE.md, Cursor has rules files. In practice these hold static facts: how to run the dev server, where the tests live, what the folders mean.

The knowledge that actually matters looks different:

  • why a refactor was done, so the next session doesn't undo it;
  • which innocent-looking pattern caused a production bug, and the exact command that diagnoses it;
  • which corners were cut on purpose, so they get revisited instead of rediscovered.

This makes the agent write transcripts about its own work, while the context is still in the window.

Two kinds of memory: a WAL and a post-mortem

Cognitive science splits memory into episodic (what happened to me: "I burned my hand on that kettle") and semantic (what I know: "kettles are hot"). A brain constantly converts the first into the second. CoALA - Cognitive Architectures for Language Agents.

The WAL. Before a database touches its real data structures, it appends the intended change to a write-ahead log. Appending to the end of a file is the fastest, safest thing a disk can do. If the process crashes mid-write, the log replays and nothing is lost. PostgreSQL calls it WAL literally (pg_wal), MySQL calls it the redo log, SQLite has a WAL mode. The price is that a WAL is write-optimized and read-terrible: it's a chronological dump, and finding the current state of anything means replaying history.

Our work journal is a WAL: an append-only file the agent writes during execution, cheap and raw, full of detail nobody will ever reread in full.

Compaction. Log-structured engines (RocksDB, Cassandra) periodically merge their raw files: overwritten values get dropped, duplicates collapse, and what remains is small and fast to read. Kafka literally calls its variant "log compaction". Our distillation step is compaction: the raw journal (or the raw session) gets melted into current rules, and the journal is archived.

The compacted store. Here it's a file named POST_MORTEM.md, in honor of the SRE practice of blameless incident post-mortems. Purists will note that a classic post-mortem covers one incident, while this file is cumulative, more like the project's case law. Same spirit though: root causes, repair guides, action items, no blame. It holds four kinds of records: architectural decisions with their why, numbered Production Guardrails, pitfalls with repair guides, and a ledger of consciously deferred tech debt.

Databases This workflow
WAL: fast append during work, raw, chronological docs/worklog/<date>-<plan>.md, written as the agent works
Compaction: merge the log into a compact, read-optimized store Distillation prompt at the end of a plan or session
The compacted store, what readers actually query POST_MORTEM.md: guardrails, pitfalls, tech debt
Old log segments deleted after compaction Journal archived; git keeps the history

One more file ties the loop together. The agent's auto-loaded entry point (CLAUDE.md for Claude Code) stays thin: project description, commands, and one hard pointer as its first line:

👉 IMPORTANT: Before writing any new code, adding features, or refactoring, you MUST first read POST_MORTEM.md and strictly follow the architectural rules and Production Guardrails recorded there.

💡 That single line turns POST_MORTEM.md from documentation the agent might stumble upon into a precondition for writing code.

The lifecycle at a glance

Two modes, three prompts total:

Planned work Spontaneous work
Phase 0 Plan with a task dependency graph (no plan; work just happens)
Phase 1: Run → Log Prompt 1: one item at a time, WAL entry after each the session context itself plays the role of the log
Phase 2: Distill Prompt 2: compact the WAL into POST_MORTEM.md Prompt 3: distill straight from session context

Both modes are shown below on real tasks from this store, with the prompts in full.

Case 1: a planned refactor

The best showcase of the planned mode grew out of the ugliest bug in this project's history. Every page kept its server-fetched data in a module-level store object. A module in Next.js initializes once per process, so that object was silently shared between all requests and all users. When one visitor hit an error, the error state stuck, and every visitor after them saw the error page until restart. (The full detective story is a later part of this series.)

The fix couldn't be a one-liner: introduce a per-request store provider, migrate every page and component to it, split client-only UI state into its own store, delete two dead abstractions along the way. A dozen-plus interdependent items, which is work that needs a plan.

Phase 0: a plan with a dependency graph

For planned work I don't start from a prompt at all. I use the Superpowers plugin for Claude Code, a community skill library that forces a brainstorm → design → implementation-plan pipeline before any code gets written. Any planning approach works. The output that matters for this workflow is a plan whose appendix contains a task dependency graph: which items block which, and which are independent quick wins.

That graph is what makes strictly iterative execution possible: at any moment the agent knows what one atomic, unblocked item looks like. For the refactor above, the store factory had to exist before any page could migrate to it, but the pages could migrate independently of each other.

Phase 1: run and log

Prompt 1: execute the plan

You are an autonomous AI coding agent. Your task is to execute the
attached implementation plan step by step.

So that the codebase and your memory evolve together, we will use the
practice "Run -> Log -> Distill -> Repeat". Your goal is not just to
write code, but to log the process and accumulate experience in the
work journal file `docs/worklog/<date>-<plan-name>.md`.

### RULES AND ALGORITHM:
1. Study the Appendix (the task dependency graph) at the end of the
   plan. You may not start a child task until its parents are done.
2. Work strictly iteratively: ONE specific item per ONE step. Do not
   attempt several tasks at once.
3. The algorithm for each item:
   a) [Maker] Write or change the code for the current isolated task.
   b) [Verifier] Run <verify command> and confirm nothing is broken.
   c) [Distill] Pause and append an entry to the work journal.
   d) The step is complete. Send me a short report and wait for my
      approval before moving to the next item.

### JOURNAL FORMAT:
After each completed item, append an entry to the journal, strictly
following these 5 criteria:
1. [Verified Facts] Which item is done, which files changed, which
   checks passed.
2. [What Worked] Successful patterns and approaches worth repeating.
3. [Distilled Rules] Provisional rules the next steps must respect
   (imports, dependency injection, state management).
4. [Pitfalls & What to Avoid] Compilation errors, dependency conflicts
   or bugs you hit on this item, and how you worked around them.
5. [What's Next] The next atomic task from the plan and its
   prerequisites.

Start with the first unblocked item (the quick wins, if the plan marks
them). What is your first concrete task? Describe what exactly you are
about to change in the code.
Enter fullscreen mode Exit fullscreen mode

📝 Fill in: <verify command> (your test or build command: npm test, flutter test, cargo check), the journal path, and attach or reference the plan itself.

The prompt forces a rhythm: code, verify, log, report, wait. The waiting is there so that an agent drifting off course gets caught after one item instead of after ten. The logging happens right after verification, while the dead ends are still in the context window, because a diff read tomorrow won't tell you why any of it happened. And nothing enters the journal until the checks pass; that rule alone separates a work log from a chat transcript.

Here's an entry from that refactor's run (lightly reconstructed):

### Item 7: migrate category page to the per-request store
1. [Verified Facts] Item 7 done. Changed: category/[id]/page.tsx.
   `npm run typecheck` clean, smoke test green.
2. [What Worked] Same pattern as items 5-6: the page fetches data and
   passes it as initState to ProductsStoreProvider; components read
   via useProductsStore(selector).
3. [Distilled Rules] Selectors must return scalars or stable
   references. An object literal inside a selector caused an infinite
   re-render loop on item 6; don't repeat it here or on item 8.
4. [Pitfalls & What to Avoid] Keystone query results are class
   instances; passing them directly into a client component triggers
   "Only plain objects can be passed to Client Components". Wrap them
   in toPlainObject() before they cross the RSC boundary.
5. [What's Next] Item 8 (product page) was blocked by this item; now
   unblocked. Prerequisite: store factory (item 2, done).
Enter fullscreen mode Exit fullscreen mode

Entries 3 and 4 are the valuable ones: a rule and a trap caught mid-run, written while the context was hot, phrased as advice for the next item. This is the raw material compaction feeds on.

Phase 2: compaction

When the last item is done, I run the second prompt.

Prompt 2: compact the journal

We have fully completed the plan! All tests pass. Our final task is a
total distillation of the experience, to capitalize it for the long
term.

Melt the work journal (docs/worklog/<date>-<plan-name>.md) down into
our architectural memory, and make sure `CLAUDE.md` remains a clean
entry point. Do not limit yourself to 5-7 bullet points: lay out every
important technical insight and rule, with no length limit.

Perform two actions, strictly in this order:

### ACTION 1: Create or extend `POST_MORTEM.md`
Record the codebase's Book of Law in the project root. Add a section
for this run, labeled with the work type ([feature] / [refactor] /
[redesign] / [deploy] / [debug] / [mixed]), and integrate its lessons
into the global sections:
1. ARCHITECTURAL CHANGES: what changed in the structure of the
   codebase and why. Justify each decision so a future session doesn't
   undo it.
2. PRODUCTION GUARDRAILS: the rules for writing code that
   crystallized out of this run (imports, state management, where
   mutable state is categorically forbidden, etc.). Phrase them as
   hard, numbered standards; justify every rule.
3. PITFALLS & TROUBLESHOOTING: every trap we hit, with a repair guide
   for each in case it recurs: symptom, root cause, the exact
   diagnostic commands.
4. FUTURE BACKLOG: a numbered ledger of deferred tech debt and areas
   needing further work.

### ACTION 2: Verify the entry point `CLAUDE.md`
Remove any temporary statuses, logs, or plan steps that leaked into
`CLAUDE.md`. It must contain ONLY:
1. A short project description.
2. Basic commands (run, test, build).
3. This hard pointer on the first line:
"👉 IMPORTANT: Before writing any new code, adding features, or
refactoring, you MUST first read POST_MORTEM.md and strictly follow
the architectural rules and Production Guardrails recorded there."

Apply the changes, archive the work journal, and show me the structure
of the resulting POST_MORTEM.md.
Enter fullscreen mode Exit fullscreen mode

Compaction of that refactor's journal produced the first entries of the project's case law. Two of them:

1. It is FORBIDDEN to declare a mutable module-level object in server components / server actions. A module in Next.js initializes once per process: such an object is shared between ALL requests and ALL users. Any per-request state must be created inside the component function.

3. Server data reaches the client ONLY two ways: (a) a per-request store created by the page with its data as initState; (b) plain props. A global module-level store for server data is FORBIDDEN, as are "hydrator" components calling setState during render.

Both came out numbered and imperative, with the reasoning attached, and that formatting does real work. Agents obey "FORBIDDEN / MUST" far more reliably than "prefer / consider", and numbers make rules citable: the agent cites them in code comments, and I cite them in reviews ("this violates guardrail 3").

Case 2: a spontaneous session

Everything above assumes a plan existed. Plenty of sessions don't have one, and the best example here is the day of this store's first production deploy.

The plan looked trivial: push the admin-panel image to Google Cloud Run, run migrations against the managed Postgres (Neon), done. Instead, the container died on startup with the most generic error a platform can produce: "Container failed to start and listen on PORT=8080". No stack trace, and everything ran green locally. Three hours of digging later, the story emerged: the Alpine-based image shipped Prisma engines linked against OpenSSL 1.1, Node 20 links OpenSSL 3, and the query engine segfaulted during the TLS handshake with the production database. Local Postgres has no TLS, so no local run could have caught it. The eventual fix was one line in the Dockerfile; the three hours were spent finding that line.

No plan predicted any of this, so no journal was being kept. Sessions like this are common (the quick fix that grows into a refactor, the deploy that turns into a debugging saga), and they produce the most valuable lessons, because nothing about them was predicted.

For these sessions there's a third prompt, run once at the end.

⚠️ Prompt 3 distills from the live context window, so it has to run before the session closes. Next morning the context is gone.

Prompt 3: distill a spontaneous session

We've finished a working session in which we completed a number of
varied tasks and ad-hoc code fixes without rigid upfront planning. Now
we need to distill and capitalize this spontaneous experience following
the "Run -> Log -> Distill" principle (a self-improving system).

Your task: analyze the entire current session, the history of changed
files, and the context of the bugs we solved, then record that
experience in our architectural memory. Don't limit yourself on length;
dump every important engineering insight.

Perform two actions:

### ACTION 1: Extend/update the file `POST_MORTEM.md`
Find or create `POST_MORTEM.md` in the project root. Carefully
integrate this session's experience into it. Add a section for this
session, labeled with the work type ([feature] / [refactor] /
[redesign] / [deploy] / [debug] / [mixed]), organized into these
blocks:
1. [Architectural changes]: What exactly did we change in the
   structure of the codebase and why (which files we split, which
   dependencies we rewrote, which patterns we applied).
2. [Production Guardrails]: Which new rules for writing code, imports,
   state management, or the design system crystallized out of today's
   changes. Phrase them as hard, numbered standards that must not be
   violated.
3. [Lessons & Pitfalls]: Which non-obvious bugs, compilation errors,
   or library conflicts did we hit today? Why did they happen and how
   did we solve them? Write a detailed repair guide in case they
   recur.
4. [Residual tech debt]: What loose ends did we leave? What from
   today's work needs to be finished, refactored, or covered with
   tests in future sessions.

### ACTION 2: Refresh the entry point `CLAUDE.md`
Make sure `CLAUDE.md` in the project root is clean of process logs,
contains only current build/test commands, and has this hard pointer
on its first line:
"👉 IMPORTANT: Before writing any new code, adding features, or
refactoring, you MUST first read POST_MORTEM.md and strictly follow
the architectural rules and Production Guardrails recorded there."

Do the analysis, update the files, and give me a short summary: which
key rules and pitfalls from this session did you just commit to
memory.
Enter fullscreen mode Exit fullscreen mode

It differs from Prompt 2 in three ways:

  • The source is the session context, not a journal. There's no WAL to compact, so the agent works from what's still in the window: the conversation, the diffs, the bugs solved.
  • It integrates instead of creating. Prompt 2 assumes a completed plan and writes a definitive section. Prompt 3 says "find or create" and merges carefully into whatever case law already exists.
  • It ends with a ranked summary. "Tell me which rules you just committed to memory" forces the agent to prioritize, and gives me a 30-second review of what just entered long-term memory. The human stays editor-in-chief. I occasionally veto a rule that over-generalizes a one-off; a self-improving system with no editor drifts into superstition.

Run at the end of the deploy saga, this prompt produced the project's most valuable pitfall entry:

3.12 "Container failed to start on PORT=8080", which was actually a segfault. Prisma's musl engines are linked against OpenSSL 1.1; node:20-alpine ships OpenSSL 3; the query engine SIGSEGVs exactly at the TLS handshake with the production database. Migrations still work (separate process!), local runs are green (local Postgres has no TLS!). Diagnosis: run the same image locally with the production DATABASE_URL and read the full stderr. Fix: node:20-slim. Lesson: "works on my machine" must include production-mode TLS.

The repair guide is deliberately a command: the next session can rerun the diagnosis in ten seconds instead of rediscovering it. A session later the lesson got generalized into guardrail 18: any image that talks TLS to a database in production gets tested against a TLS database before deploy.

The loop closes

The payoff came from a third task. Early on, the storefront called the supplier's B2B API during page rendering. One day the supplier API got flaky and took the whole storefront down with it. The distilled lesson became guardrail 4: external APIs are best-effort only, sync never sits on the render path, try { sync } catch { log }, then serve data from our own DB.

Sessions later, the agent was writing a new data-access function, and this comment appeared in the diff, unprompted:

// Pure DB read: the supplier sync is NOT called here - pages are statically
// cached (ISR), so external APIs must stay off the render path (Guardrail 4,
// tech-debt #9). Sync runs on demand via POST /api/sync-supplier.
export async function getProducts() {
Enter fullscreen mode Exit fullscreen mode

The rule number, the tech-debt entry that rule closed, and the alternative chosen because of them: the agent is citing the project's own case law while writing new code. This comment did more to convince me the setup works than anything else in five sessions.

Don't keep the journal in CLAUDE.md

An early version of Prompt 1 told the agent to keep the work journal inside CLAUDE.md, the same file the agent auto-loads at the start of every session. That's also why Prompt 2 has a "clean up CLAUDE.md" action: the cleanup was a symptom of the journal living in the wrong place. The prompts above already contain the fix; the reasoning behind it:

  • The WAL stays out of the auto-loaded context file. Otherwise every session pays tokens for old execution logs, and the entry point gets diluted right when it should stay sharp.
  • One journal file per plan or session, like docs/worklog/2026-07-08-per-request-store.md. Not auto-loaded. Prompt 1 names it explicitly.
  • CLAUDE.md holds only commands and pointers. Mine is 48 lines: project description, how to run and verify, the hard pointer to POST_MORTEM.md.
  • Archive the journal after compaction. Its value has been extracted. Git keeps the raw material if you ever need it.
  • Tag sessions by work type: [feature], [refactor], [redesign], [deploy], [debug], [mixed] in the section header. Rules are type-agnostic (a rule born in a redesign binds a refactor just the same), but tags make future compaction of the post-mortem itself much easier.

Limitations

  • The post-mortem grows. Mine is ~300 lines after five sessions. At some point old session narratives need compaction of their own: keep the guardrails and pitfalls, collapse the story. Compaction is never finished, in databases or here.
  • Rules can over-fit. A workaround for one library version can calcify into false doctrine. The numbered format helps: rules are easy to find and repeal.
  • It's discipline-dependent. A good distillation pass takes a few minutes of review, and if you skip it, the knowledge from that session is simply gone.

Prior art

For digging deeper, the landscape this workflow sits in:

  • CoALA (Sumers et al., 2023): the paper that mapped agent memory onto the psychology terms used above (working, episodic, semantic, procedural).
  • ExpeL (Zhao et al., 2023): agents that collect their own trajectories and extract natural-language insights from them. The academic version of the compaction step.
  • The Memory Bank pattern: a popular markdown-memory methodology from the AI-IDE world. It solves a neighboring problem: Memory Bank reconstructs project state (what we're building, where we stopped), while this workflow accumulates lessons (what must never happen again). The two combine well.
  • Compounding engineering (Every): the same philosophy ("each unit of work should make the next one easier") grown into a full Claude Code plugin with a Plan, Work, Review, Compound cycle. A packaged alternative to the three prompts here.
  • Self-improving coding agents (Addy Osmani, 2026): a survey of the looped variants, learnings.md files and continuous loops included.

Steal it

The whole system is three prompts and one pointer line. Nothing is specific to Claude Code except the CLAUDE.md filename; Cursor rules, Windsurf rules, or a plain AGENTS.md work the same way.

  1. Put the hard pointer on the first line of your agent's entry-point file.
  2. Planned work: Prompt 1 to start the run, Prompt 2 when the plan is done.
  3. Everything else: Prompt 3 at the end of the session, before you close it.

🚀 Start on an existing project today: run Prompt 3 at the end of your next session and you'll have your first guardrails by tonight. Five sessions from now, your agent will be citing them back to you.


This is part 1 of a build-in-public series. The project this memory system grew on, a full-stack GraphQL store (Next.js 13 + Keystone 6 + PostgreSQL) running in production for $0/month, is what the next parts dissect: the architecture, the deployment, the ISR + live supplier API mechanics.

Top comments (0)