The Memory Layer

Read22 min · Remember → Retrieve → Act → Report · Updated 2026-09-13

You end this lesson with: Working memory files plus the read/write protocol the agent follows every session.

Chapters

  1. Why one growing file fails
    01
  2. The three-file split
    02
  3. The read protocol
    03
  4. The write protocol
    04
  5. The failure mode: remembering what's no longer true
    05
  6. The instructions block
    06
view

Lesson 02. The Memory Layer

Three weeks into running a standing agent, someone adds one line to its memory file: "prefers async standup updates over live meetings." Two months later the same file has another line: "always attend the 10am standup live." Nobody deleted the first line. The agent now holds two contradictory facts about the same thing, and whichever one it read most recently, or whichever one happens to sit later in the file, closer to the end of context, wins by accident, not by correctness. This is not a hypothetical. It is the default outcome of the obvious first design: one file, appended to forever.

The obvious design fails for three separate reasons, and it's worth naming all three before fixing them, because a fix that solves one and not the others just moves the failure around.

Why one growing file fails

Retrieval cost. A single file that accumulates every fact, preference, and event you've ever told the agent grows without bound. Loading all of it into context on every run gets expensive and, past a certain size, physically doesn't fit. You end up truncating, which means the agent silently loses whichever part got cut, with no signal to you that it happened.

Contradiction. Preferences change. "I prefer async standups" today, "actually let's do live standups" in three months. If both statements live in the same undifferentiated pile with no timestamp and no marker for which one superseded the other, the agent has no principled way to pick. It either uses both (visibly wrong) or picks one by recency-in-context (wrong for reasons you can't audit).

No invalidation. A single growing file has no mechanism for saying "this is no longer true." Deleting a line loses the historical record: you can no longer answer "when did this change and why." Not deleting it means false and true statements sit side by side, indistinguishable to a reader who wasn't there when it changed.

The fix is not "write better prompts telling the agent to be careful." It's a file structure where these three problems can't arise, because different kinds of information live in different files with different rules.

The three-file split

Split memory by how often it changes and how it should be read, not by topic:

FileWhat it holdsChangesLoaded
identity.mdWho you are, stable preferences, constraints that rarely changeRarely, you edit it by hand most of the timeEvery session, in full
working-context.mdWhat's true right now: current tasks, open threads, the last snapshot of stateEvery run, the agent overwrites the relevant sectionEvery session, in full
episodic.mdA dated, append-only log of what happenedGrows every run, never edited after writingOnly on demand, via search (lesson 3)

identity.md is small on purpose: a page, not a book. It holds facts stable enough that changing them is a deliberate act, which is why the agent should rarely write to it unattended (more on that below). working-context.md is the agent's scratch pad for "what does the world look like as of the last time I checked." It gets overwritten, not appended to, so it never grows unbounded and never contradicts itself; there is exactly one current value for each field. episodic.md is the only file allowed to grow forever, and it solves the contradiction problem by never claiming to be current: every line is a dated observation, not a standing fact, so two entries that disagree are just history, not a live contradiction.

The table above sorts files by how they change; the picture below sorts them by how a session actually loads them, which is the distinction the read protocol in the next section depends on.

diagram
drawing
Two files load in full every session; the third loads only through a targeted search.

Two arrows are unconditional and one is narrow: that asymmetry is what keeps a session's context bill flat even as episodic.md grows without bound.

The read protocol

Every session starts the same way, and the order matters: identity.md first (who am I working with, what are the constraints), then working-context.md (what's the state of the world right now), and only then, if the current task calls for it, a targeted search into episodic.md for relevant history. The first two load in full, every time, because they're small and because the agent cannot reason about "what should I do" without them. The third loads narrowly, because it's the one file with no size ceiling; loading it in full is the retrieval-cost failure mode from the top of this lesson, reintroduced through the back door.

"If the current task calls for it" is doing real work in that sentence: not every run needs episodic history. A run that's just updating working-context.md with today's snapshot doesn't need to search episodic.md at all. A run that's answering "have I dealt with this vendor before" does. Lesson 3 builds the search mechanism; this lesson's job is establishing that the two loads (identity + working-context) are unconditional and the third is conditional and targeted.

The write protocol

Not every file is writable by the unattended agent, and not every write is unattended.

  • identity.md: the agent may propose a change (append a suggestion to working-context.md's "pending" section) but should not edit this file without your confirmation. It's small, stable, and wrong changes here corrupt every future session, so the cost of a bad unattended write is high relative to the cost of asking.
  • working-context.md: the agent may overwrite freely. This is exactly its job: reflect current state after every run. A bad write here is cheap to fix, the next run overwrites it again.
  • episodic.md: the agent may append, never edit or delete existing lines. Append-only is the invalidation mechanism: if something changes, you write a new dated line saying so, and the old line stays as an accurate record of what was true when it was written. Nothing is ever silently wrong in place.

This is also where self-dating and self-sourcing earn their keep. Every entry that goes into episodic.md carries a date and a source — where the fact came from (a tool call, a conversation, an inference). A fact with no date can't be checked for staleness; a fact with no source can't be re-verified when something looks off. 2026-08-19 (from calendar-cli sync): 3 meetings rescheduled to next week is checkable — you know when it was true and how the agent knew it. Meetings rescheduled on its own is a claim you have to take on faith, forever, with no way to tell if it's still accurate.

The failure mode: remembering what's no longer true

This is the concrete failure the whole structure defends against. It happens in one specific shape: the agent reads a fact from memory, treats it as still current, and acts on it, when the actual state of the world moved on without an update reaching memory. Three-file split reduces the blast radius but doesn't eliminate the risk entirely, because working-context.md is only as fresh as the last run that updated it, and identity.md is only as fresh as your last manual edit.

The mitigation is dating everything and treating memory as a hypothesis to be confirmed, not a fact to act on unquestioned, whenever the action is consequential. A low-stakes action (mentioning a preference in a summary) can proceed on memory alone. A high-stakes or externally-visible action (booking something based on a remembered preference) should re-check the live source when one exists, and memory should be treated as a prior, not a certainty. This is the same trust-boundary discipline from lesson 1, applied specifically to stale reads instead of over-broad writes.

The instructions block

This is the exact block that goes into the agent's system prompt or a loaded instructions file, teaching it the protocol above. It is deliberately mechanical (rules, not vibes) so behavior is predictable across sessions.

markdown
## Memory protocol

On every run, in this order:
1. Read agent/memory/identity.md in full. This is who you're working for
   and what constraints always apply. Treat it as ground truth.
2. Read agent/memory/working-context.md in full. This is the last known
   state of the world. Treat it as current but not certain — it is only
   as fresh as the last run that wrote it.
3. Only if the current task needs history, search agent/memory/episodic.md
   for relevant entries. Do not load it in full.

Writing:
- identity.md: never edit directly. If something suggests a change
  (a stated preference, a corrected fact), append a note to the
  "Pending identity changes" section of working-context.md instead,
  and mention it in your report. A human confirms before it moves.
- working-context.md: overwrite the relevant section at the end of
  every run to reflect current state. Do not append — this file holds
  exactly one current value per field.
- episodic.md: append only, never edit or delete existing lines. Every
  entry MUST have a date and a source, in this exact format:
  `YYYY-MM-DD (source): fact`. If you cannot state a source, do not
  write the entry.

Never treat an episodic.md entry as still true without checking its
date. For any action with real-world consequences (sending something,
spending money, anything hard to undo), prefer the live source over a
remembered fact when a live source is available.

Sizing the two files that load in full

identity.md and working-context.md load into every session's context unconditionally, which means their size is a direct tax on every run, unlike episodic.md, which only costs what a given search pulls in. Keep both short by construction, not by discipline you have to remember to apply:

  • identity.md should read comfortably in under a minute. If it's grown past that, something has crept in that belongs in working-context (a current project detail) or episodic (a one-time event) instead of a standing fact about you. A useful test: would this line still be true in a year? If not, it's not identity.
  • working-context.md should reflect a handful of active threads, not an archive of every thread that ever existed. When a thread resolves (a PR merges, a task closes), its line moves out of working-context. It doesn't need to move anywhere; the fact that it happened is already durable in episodic.md's history if it was logged there when it occurred. Working-context only holds what's still open.

If either file keeps growing despite this, that's a signal the split itself needs a second look for your specific use case — maybe "current focus" is doing the job of three separate things and deserves its own subsection, or maybe some of what you're calling "current" is actually stable enough to graduate to identity. The three-file split is a starting structure, not a rigid law; the invariant worth protecting is that the two full-load files stay small and the append-only log stays the only place unbounded growth is allowed to happen.

What this replaces, concretely

If you've already been running a coding agent with some form of memory, it's probably one file (NOTES.md, MEMORY.md, CONTEXT.md) that the agent appends to whenever something seems worth remembering. That file has no distinction between "this is who I am" and "this is what happened on Tuesday," so every read loads all of it, and every write risks silently contradicting an earlier line. The table below maps that failure onto which of the three new files fixes it.

Symptom in a single-file setupWhich failure from the top of this lessonFixed by
Context fills up before the actual task startsRetrieval costepisodic.md loads on demand only (lesson 3), not in full
Agent says something you corrected weeks agoContradictionDated entries in episodic.md; superseding entries instead of edits
You can't tell if a line is still trueNo invalidationSelf-dating + self-sourcing format; working-context.md overwritten, not appended
A stable fact (your name, your constraints) got buried under event noiseNo separation of stable vs. transientidentity.md holds only what rarely changes

Migrating an existing single-file memory is mechanical: stable facts (name, standing preferences, hard constraints) move to identity.md. Anything describing "the state of things as of now" moves to working-context.md, with only the most recent value kept. Everything else (every dated observation, every event) moves to episodic.md with a best-effort date and source reconstructed from context, even if it's approximate. Approximate dating beats no dating; it's still enough to spot "this is probably stale" at a glance.

Key Takeaways

  • One growing memory file fails three separate ways: unbounded retrieval cost, unresolved contradiction, and no invalidation mechanism. Fix the structure, not the prompt.
  • Split memory by change frequency and read pattern: stable facts (identity.md), current state (working-context.md), and append-only history (episodic.md).
  • Read protocol: identity and working-context load in full every session; episodic loads only on demand, narrowly.
  • Write protocol: identity needs human confirmation, working-context is freely overwritten, episodic is append-only.
  • Every episodic entry is self-dating and self-sourcing (YYYY-MM-DD (source): fact). Undated facts can't be checked for staleness.
  • Memory is a hypothesis, not a certainty. Match verification effort to the stakes of the action you're about to take on it.

What You Have Now

Working memory files plus the read/write protocol the agent follows every session.

Example Files

agent/memory/identity.md

Small, stable, hand-edited most of the time. Loaded in full every session.

markdown
# Identity and Preferences

Last reviewed: 2026-08-19

## Who this agent works for
- Name: <your name>
- Timezone: <your timezone>
- Primary role: <e.g. backend engineer, solo founder>

## Stable preferences
- Communication: async updates preferred over live meetings, unless
  explicitly time-sensitive.
- Notification threshold: only interrupt for things that need a same-day
  decision. Everything else waits for the daily report.
- Tone: direct, no filler, flag uncertainty rather than guessing.

## Constraints that always apply
- Never send external communications (email, messages) without
  confirmation.
- Never take an action that spends money.
- Working hours: do not schedule notifications before 8am or after 8pm
  in the timezone above.

agent/memory/working-context.md

Overwritten every run. Holds exactly one current value per field, never append a second "Open tasks" section.

markdown
# Working Context

Last updated: 2026-08-19 07:02 (by scheduled run)

## Current focus
<What the agent is actively tracking right now, in one or two lines.>

## Open threads
- <thing 1, with enough detail to pick back up>
- <thing 2>

## Last known state (per tool)
- github-cli: 3 open PRs, none waiting on review as of last check
- calendar-cli: no conflicts through Friday

## Pending identity changes
<Suggestions the agent noticed but has not applied — waiting on human
confirmation. Empty most of the time.>
- none currently

agent/memory/episodic.md

Append-only. Every entry dated and sourced. This file has no size ceiling: lesson 3 builds the search index that makes that safe.

markdown
# Episodic Log

Append-only. Format: `YYYY-MM-DD (source): fact`. Never edit or delete
existing lines below this point.

2026-08-01 (direct statement in chat): user said they prefer async
standup updates over live meetings.
2026-08-15 (github-cli sync): PR #42 opened, waiting on review from
@alice.
2026-08-17 (github-cli sync): PR #42 merged.
2026-08-19 (direct statement in chat): user said live standups are back
on for this quarter — supersedes 2026-08-01 entry above.

Further Reading

Exercises

  1. Create the three files above in your own agent/memory/ directory, filled in with your actual preferences and current state instead of the placeholders.
  2. Write down one concrete scenario where your agent would read a stale working-context.md value and take a wrong action. Then decide, for that scenario, whether the fix is "re-verify against a live source" or "shorten how long working-context.md is trusted before a refresh." They are different fixes for different situations.
  3. Add three real entries to your episodic.md by hand, each with a genuine date and source, then write one entry that intentionally supersedes an earlier one (like the standup example above) and confirm a fresh read of the file makes the current state unambiguous to a human skimming it.
  4. If you have an existing single-file memory setup, run the migration described above on it: sort every line into identity, working-context, or episodic, and note how many lines landed in each. A file that's almost entirely episodic-shaped events with no stable facts is a sign your agent has been recording history without ever forming a stable model of who it's working for.

Check your understanding


Reading is free — your progress isn't being saved

Nothing you did on this page is recorded. Paid students get saved progress and verified completions, recorded quiz answers, the spaced-repetition review queue, labs, and a certificate for each course they finish.

30-day money-back guarantee. One-time payment — lifetime access and updates.