Give an Autonomous Coding Agent Memory: KNOWLEDGE + RAG

Because the three kinds have different lifecycles. Curated knowledge is written by a human and reviewed, accumulated lessons are appended by the agent one line per run, and a session handoff is rewritten every run. Separating them means an accidental overwrite cannot erase weeks of accumulated detail.
No. A script that chunks markdown by heading, embeds each chunk and ranks by cosine similarity is about a hundred and fifty lines and needs no infrastructure. Add a lexical token-overlap fallback so a run never breaks because the embedding API was unavailable.
The reflection step, which most setups omit. At the end of each run the agent appends one dated lesson and then re-indexes, so today's discovery is retrievable tomorrow. Without the re-index the lessons file grows and nothing ever reads it, which looks like memory and is not.
Domain invariants stated as rules, a named anti-patterns section, modern language idioms referred to by name, and repository quirks that cannot be discovered in a single run. Naming an anti-pattern proved more effective than describing the ideal, and concrete feature names are retrievable where write good code is not.
It prevents repeated mistakes; it does not add capability. In my project the throughput gains came from procedure — task sizing, turn budgets, landing deadlines — and memory is what stopped those gains being eroded by the same few errors every week. Also budget for it: a richer retrieval pipeline costs turns.

Key Takeaway
An agent that starts every run from zero repeats the same mistakes forever. Three files fix it: curated knowledge, accumulated lessons, and a session handoff — all indexed for semantic search, with a reflection step at the end of each run that appends one new lesson and re-indexes it.
For the first few weeks my daily coding agent had no memory. Each run began with the repository and its instructions, worked for half an hour, opened a pull request, and forgot everything. It was competent and it was Groundhog Day: the same misunderstanding about a fixture, the same wrong assumption about a helper, three times in one week.
The fix turned out to be small — three markdown files, about a hundred and fifty lines of Python, and one instruction at the end of the run. This is how it fits together, and which parts actually earned their place.
The temptation is one big notes file. That fails because the three kinds of memory have different lifecycles: one is curated by a human, one accumulates automatically, and one is overwritten constantly.
| File | Who writes it | What belongs in it |
|---|---|---|
| Knowledge | Me, deliberately, occasionally | Domain patterns and standards: how state transitions should be modelled, what an idempotent operation means here, modern language features to prefer |
| Lessons | The agent, one line per run | Dated, specific discoveries: this helper returns undefined for a missing path, the build runs out of memory on this box |
| Sessions | The agent, at the end of every run | A short handoff: what I did today, what to do next — read at the start of the following run for continuity |
Keeping them separate matters operationally too. Knowledge is committed and reviewed; lessons and sessions are seeded once and then only appended to, so an accidental overwrite does not erase weeks of accumulated detail.

Once there are three growing files, dumping them all into the prompt stops being viable, and grep is too literal to find the note that matters. The middle ground is a tiny retrieval script: chunk the markdown by heading, embed each chunk, and rank by cosine similarity.
# kb.py — stdlib-only vector search over the agent's own notes.
# No vector database, no framework, about 150 lines.
#
# kb index KNOWLEDGE.md LEARNINGS.md SESSIONS.md
# kb search "how do we handle idempotency" 5
def chunk(markdown: str) -> list[str]:
"""Split on level-2 headings, then sub-split anything over ~1200 chars."""
def embed(texts: list[str]) -> list[list[float]]:
"""One HTTP call per chunk to a free embedding endpoint.
On any failure, fall back to lexical token overlap so a run never
breaks because an embedding API was down."""
def search(query: str, k: int) -> list[tuple[float, str]]:
"""Cosine similarity against the on-disk index, top k returned."""
# Index resolution walks UP from the working directory, the way git
# finds .git, so a search works from any subdirectory of the repo.Two decisions made this practical. First, a lexical fallback: if the embedding call fails for any reason, the script falls back to token overlap so a run never breaks because an API was down. Second, index resolution that walks up from the working directory the way git finds its own root, so a search works from anywhere inside the repository.
Check which interpreter actually exists inside your agent container before writing the script. Mine had no system Python at all — only the runtime's own virtual environment — so the tool is a two-line wrapper that execs that interpreter. Discovering this after writing the script cost an hour.
Retrieval alone is a lookup table. What turns it into learning is a per-item pipeline plus a reflection step, and the reflection step is the part most setups leave out.
# The per-item loop, written into the agent's persistent instructions.
# Each step costs turns, which is exactly why the budget must be stated.
1. RETRIEVE kb search "<the item you are about to do>"
2. EDGE CASES consider empty, null, zero, negative, boundary, duplicate,
already-terminal — pick the ones that apply
3. EDIT the smallest change that covers them
4. SELF-REVIEW re-read your own diff as a reviewer would. If it is
trivial or wrong, rewrite it before committing
5. COMMIT one logical change, conventional commit message
# At the END of the run:
6. REFLECT append one dated lesson to LEARNINGS.md
7. REINDEX kb index — so the next run can retrieve today's lessonThe pipeline is deliberately ordered. Retrieve first, so the edge cases the agent considers are informed by what previous runs found. Self-review before committing, because a model reading its own diff as a reviewer catches the trivially wrong test it just wrote. Then reflect and re-index, so today's lesson is retrievable tomorrow. That last step is what closes the loop; without it the lessons file grows and nothing ever reads it.
A richer retrieval pipeline costs turns, and the landing budget must grow to match. The first run under this loop produced eleven good commits and never opened its pull request, because retrieval plus edge-case analysis plus self-review took about seven turns per item and consumed the entire budget. Lowering the commit target fixed it.

The content that changed behaviour was more specific than I expected, and more about standards than about facts.
The last category is the one the agent contributes best. It cannot tell you what good design is, but it is very good at recording that a particular helper returned undefined when given a path with no dot in it, on the day that cost it twenty minutes.
The honest evidence is indirect but consistent. Semantic queries score highest against exactly the chunks a human would pick, the lessons file grew from one line to over twenty within days of the reflection step being added, and the specific misunderstandings that used to recur stopped recurring.
What it did not do is make a weak model strong. Retrieval prevents repeated mistakes; it does not add capability. The throughput gains in my project came from procedure — task sizing, turn budgets, landing deadlines — and memory is what stopped those gains from being eroded by the same three errors every week.
Five steps, in this order, and stop after step three if that is all you need.
Step five is the verification most people skip, and it is the only one that proves the loop is closed rather than merely configured.
Memory turns an agent from a competent stranger into a colleague who was here yesterday. Three files with different lifecycles, a hundred and fifty lines of retrieval, and one reflection instruction were enough — and the discipline that makes it work is not the vector search, it is remembering to re-index so the lesson learned today is findable tomorrow.