For the last several months I have run my development workflow through AI coding agents. We are talking about 100s of thousands of lines in Java/C++/Python/Shell etc. This involves a platform modernization at a scale where not just code, but the terminology, the architecture, design decisions etc. changed over time.
I can unequivocally say (now that we are in the final sprint of changes), that a key success factor in the effort was persistent memory.
During this journey, I have built my own memory layer. pi-graphiti, a knowledge-graph extension for the Pi coding agent backed by a Graphiti MCP server. A flat-file memory extension that manages scoped notes and harvested procedures. A self-hosted Graphiti and FalkorDB stack. This stores factual information and handles the ‘time element’ for those facts.
In addition to this graph backed memory, I also have a ‘hermes’ style memory with a background harvester that analyzes and cleans up memory periodically. I think these two - together have done wonders. The naming is a nod to Hermes Agent’s memory providers, which is a good survey of this design space - flat built-in notes that are always on, plus exactly one pluggable external store behind them, spanning graph, vector, and local SQLite backends.
A parallel experiment on mem0 with Qdrant for vectors and Memgraph for the graph, mostly to check that the design is transferable to different backends did not work out. What I take away from all of it: prompt engineering without context accumulation (smart) is of no-use. Model capability is fixed on any given day, but how much of my own hard-won operational knowledge the model can reach is something I control, and that part compounds.
Four different problems wearing one name
The first thing you learn building this is that “memory” covers four fairly different problems. Working memory is the context window: fast, expensive, gone at session end. That is why bulk material like command output and fetched documentation goes into a searchable index and only a small derived answer enters the conversation.
Episodic memory is the raw record of what happened, and in the graph setup it is literally called an episode: a snapshot pushed every few turns, again before context compaction, and again on shutdown, with a disk spool so a dead server delays writes instead of losing them.
Semantic memory is what gets distilled out of those episodes into entities and facts, the durable “X depends on Y, Z is now the default” layer. Procedural memory is the one most people skip and the one that has paid best for me. Any task that took real trial and error ends by being harvested into a skill file with trigger conditions, ordered steps, pitfalls, and verification steps. I have around seventy of those, they load on demand when a task matches, and the second occurrence of a hard problem now costs a fraction of the first.
Here is how the tiers fit together in the loop:

Scope and category are what make retrieval work
Undifferentiated memory turns into noise quickly. So every write picks a scope: a per-project bucket, a global bucket for cross-project environment facts, and a user bucket for identity and standing preferences. Every durable lesson also picks a category: preference, correction, convention, insight, failure, tool-quirk. That vocabulary is what makes retrieval selective.
When a build breaks I search failures and tool-quirks instead of dredging the whole store. In practice the negative entries are useful. “This filesystem is enormous, never recurse from the root.” “That upstream-bug theory was wrong, the cause was our own parameter names.” “Do not push a config change whose image is not built yet, sync is automatic and self-healing.” Each one is a real outage or a wasted afternoon that cannot repeat. Corrections are valuable enough that I wired a detector for them, so the moment the user pushes back on the agent, a curation pass fires immediately. That is the highest-signal moment in the loop and it is easy to lose.
The part naive implementations get wrong is time
Facts are true at a point in time. Branch names of record change, default ports move, a service starts publishing to a different registry etc. In fact our new platform was referred to briefly as the ‘new platform’ (!) and subsequently called ‘v3’ and now we have v3 (dev, stg, prod etc.). So, our skills/memory need to account for this. A theory about a bug gets replaced by the actual root cause. An append-only store will hand the agent last quarter’s truth with full confidence, and that sends work down a dead path rather than prompting a fresh look. This is why I ended up on a temporal knowledge graph rather than a plain vector store. Relationships carry value for some time intervals. So a new assertion can invalidate the old edge instead of sitting next to it as a contradiction, and “when did this change” stays answerable.
And one more thing - forgetting has to be built in :) Google for what happens when someone is ‘unable to forget’. Real systems need pruning, supersession, and expiry: file-backed sources that flag themselves stale when the underlying file’s hash changes, consolidation passes that collapse a sprawling project note down to “the detail lives in the repo docs, query the graph for raw facts,” age-based expiry that routes to a dead-letter file, and scoped purge so I can wipe one session or one project without touching the rest.
Two rules I settled on the hard way. Never delete quietly; leave a trace of what was dropped and why. And prefer supersession over deletion, because “this held until date X” is often the fact you actually need.
On balance, I think my setup has resulted in faster time-to-resolution of tasks, but sometimes at the cost of more tokens. While earlier, I used to start the session by trying to be ‘very clear with my requirements’, as the system is evolving, I now start most of my conversations with stuff like ‘Do you remember the X edge-case we discussed ideas about? Can we revisit that?’ and Boom! - I get a clear timeline of what happened, why we paused something etc.
Summary
Memory and Context is worth treating as an engineering surface. Record failures as carefully as successes, timestamp what you assert, prune deliberately, and make “harvest what I just learned” an actual step in the loop.
References
The graph-memory piece described here is published, if you want to try it or read the implementation:
- pi-graphiti on npm - persistent knowledge graph for the Pi coding agent via Graphiti MCP. Ambient recall, automatic episode writes on session events, project/global scoping, and a
/graphcommand. Install withpi install npm:pi-graphiti. - Graphiti documentation - the temporal knowledge graph engine it builds on. You need a running Graphiti MCP server (FalkorDB or Neo4j backend);
/graph setupwill provision a local Docker stack for you. - Hermes Agent - Memory Providers - worth reading for the comparison table alone: nine providers across cloud, self-hosted, and local storage, with the trade-offs of each retrieval strategy laid out side by side. It also documents the pattern I ended up agreeing with - built-in flat memory stays always-on and the external store is additive, not a replacement.
Earlier articles in this series, on the trade-offs that led here:
- Code-Execution MCP - Validating Savings - why bulk output belongs in an index instead of the context window.
- The Illusion of Competence - what goes wrong when an agent sounds confident without grounding.
- AI can generate code 10x faster, But will that help? - the throughput-versus-correctness problem that makes memory worth building.
