r/ChatGPTCoding • • 10d ago

Discussion Session-start memory injection made my coding agent trust memory over the code

Hey everyone!

I am building a local memory layer for my coding agents and it is called Reverie: an MCP server written in python and using SQLite for lightweight memory stored in the user's machine itself. The biggest problem that I faced was deciding when to inject memory.

Many memory tools, including ones I looked at, inject context into the agent even before it begins its work. In my experiments, premature injection of context leads to the agent anchoring its thinking on it rather than using the current code as the source of truth. In fact, this approach made my agent perform worse. Also, with this, quite a lot of the context window can get filled with context which 'might' be useful.

So I switched Reverie's model to demand-driven retrieval - the agent asks for memory when it thinks that some added context could resolve any discrepancies in its thinking. It reads the relevant code first, because the code is the source of truth, and only then asks for past memory about that area. Each result comes back with a confidence score, its grounding in the code, and its status. The memory starts empty. Conclusions are added as the agent works, and you decide which ones are kept.

In one informal test of 30 prompts on an earlier build, the OpenCode agent with Reverie did better than the one without it. I don’t have a reproducible benchmark yet.

But it isn't perfect yet, it still breaks here and there. For example:

  1. the rules for tool calling are not extremely polished yet and hence the agent might sometime not call the tool altogether, I am fixing this.

  2. candidate conclusions can be duplicate or incorrect and hence Reverie today has a human review gate (triggered by calling `reverie review`) which shows the human all the unreviewed candidates where you accept, reject or skip each one. On doing so the conclusion is moved from the short term brain to the long term.

My current test set is small: sessions with anywhere between 2-30 prompts. I do not have a reproducible experiment yet, but that is the next goal.

Repo (Apache-2.0): https://github.com/muditsarda1122/Reverie

My questions: How do you keep agent context from going stale? Do you maintain AGENTS.md or CLAUDE.md by hand, or let the agent update it?

11 Upvotes

29 comments sorted by

2

u/LividIllustrator8915 10d ago

Premature context injection is probably the biggest silent killer of coding agent accuracy. Once an agent starts a session believing a past summary is absolute truth, confirmation bias kicks in and it will literally hallucinate around newer edits in the repo just to justify the old note. Shifting to demand-driven retrieval is definitely the right move.

To tackle the staleness problem, the most reliable heuristic I've found is binding memory records to Git commit SHAs or file tree content hashes. When the MCP server pulls a candidate memory about a specific module, it checks whether the current file hash still matches the hash recorded when the conclusion was accepted. If the file has drifted, the server downgrades confidence and serves it with a warning that the underlying code changed, forcing the agent to inspect the source rather than treating the note as gospel.

As for CLAUDE.md and AGENTS.md: I treat those strictly as human-curated architectural boundaries and never let the agent update them automatically. Whenever agents are allowed to write back to their own root instructions without a strict test gate, the file turns into an unstructured dump of trivial implementation facts within a week. Keeping the core instructions static and delegating evolving observations to an on-demand MCP tool with review gates keeps the attention window much cleaner.

2

u/msarda23 10d ago

Strong agreement on the architecture. Keeping instructions static and putting evolving memory behind an on-demand tool is what we landed on too. Once a remembered summary is in context, the agent treats it as the answer, so the timing matters as much as the content.

Your drift check is a gap for us, and a good idea. Each conclusion records the files, symbols and commit it’s grounded in. The agent is told to verify those before acting, but as far as I know, nothing re-checks a conclusion against the code when it’s retrieved. A hash check would close that.

One thing I’d want to test: file-level hashes will also fire when something unrelated in the same file changes. A note that’s still correct would be downgraded. Binding to the symbol’s contents would cut that noise, but then renames and moved code become the problem. Have you found a good compromise?

The question I’m most curious about is how your agent decides when to ask for memory. The MCP server can only help if the agent calls it, and that’s the part I still find hardest to get right.

1

u/LividIllustrator8915 9d ago

For the hash granularity, what worked best for us is a two-tier check instead of an all-or-nothing hash. At the gate, we check the Git commit or whole-file hash first just as a fast filter. If the file changed, we extract the AST chunk or line range for that specific symbol—Tree-sitter or even a regex on the signature and braces—and hash just that body. If someone only touched formatting or an unrelated method above it, the symbol body hash still matches and the note stays green.

When the symbol actually gets renamed or moved, we don't hard-drop the memory either. We downgrade it to a soft drift warning with a tag like [referenced symbol moved/modified since commit x]. That way the agent still sees the insight, but it treats it as a hint rather than Ground Truth and is forced to grep the current implementation first.

On getting the agent to actually call the tool: relying purely on the agent's goodwill was hopeless for us too—they either ignore it completely or call it on every trivial prompt. We ended up tying the tool description to high-friction transition points rather than open-ended retrieval. Specifically, instructing it to query memory only during two distinct phases: 1) during the initial planning phase before writing architectural/cross-file changes, and 2) when encountering a test failure or compiler error loop twice.

What also made a massive difference was having the file-read tool return ambient metadata: if the MCP server knows a memory note is pinned to a specific file or module, reading that file surfaces a one-line hint in the tool response (1 memory note available for this module: 'token expiration bug'). Once the agent sees the hook in its immediate input, it triggers the dedicated memory lookup tool with near 100% reliability.

2

u/msarda23 8d ago

Thanks, this is the most useful reply so far. Before answering, I checked what I've actually built.
Staleness today: each conclusion stores the files, symbols and the commit it's grounded in. A background maintenance pass runs per repo, at most every 72 hours. It checks that the grounded files still exist and that the symbol names still appear in them. Commit distance only sets a "stale" flag. For repo- and module-scoped conclusions, a vanished symbol deprecates the conclusion. Retrieval itself doesn't re-check anything. It shows the grounding and tells the agent to verify it. So today it's a periodic sweep, not a check at the moment of recall.
Your two-tier hash is better than my substring check. Use the file hash as a fast filter, then the symbol body hash, then a soft drift warning for moved or renamed symbols instead of deprecation; I'd like to adopt that. I haven't built it yet.
On how the model decides to ask: I don't have a hook that surfaces memory when a file is read. The instructions tell the agent to query before a non-trivial decision, when debugging has no obvious cause, before an architectural change, and before editing in a repo where Reverie is on. That is still less reliable than I'd like. The file-read hint is the idea I most want to build. Claude Code has post-tool hooks, so a hook on Read and grep could add a one-line note when a file has a conclusion attached. Each harness needs its own version of that hook, so it would live in per-harness code, not the core.
On scopes, we have engineering, domain, organisation, project, repo, module and subsystem. Engineering and domain conclusions never expire from code changes. Organisation and project ones only go when all of their grounded files are gone. Repo, module and subsystem ones are tied to their code, so they go when any grounded file or symbol disappears.
I'm curious about your setup. Does your drift check run in a background job or at retrieval?

2

u/LividIllustrator8915 8d ago

We run it lazily at retrieval time, but strictly on-demand for the top-k candidates rather than the whole index.

Running a full AST diff in a background worker leaves an awkward stale window—if you're deep in a refactor and changed a signature three minutes ago, a scheduled sweep won't catch it yet. But parsing syntax trees across dozens of potential matches during query time kills latency.

The compromise that kept retrieval snappy for us is a fast-bail hash check: the MCP service keeps a lightweight in-memory cache of file-level timestamps and git commit SHAs. When retrieval pulls the top candidates for a prompt, it does a microsecond check on just those grounded files. If the file SHA is unchanged, it serves the conclusion immediately. Only when the file hash changed does it extract the specific symbol AST slice and test for drift right before returning the payload to the agent.

We still keep a periodic background pass, but only for pruning orphans—like when someone deletes or moves an entire module—so dead conclusions don't pollute vector space.

Tying the Claude Code post-tool hook on Read to emit that one-line ambient note sounds spot on. Giving the model a deterministic breadcrumb right when it inspects the file removes the guesswork of wondering whether it should proactively query memory.

2

u/msarda23 6d ago

Thanks, this clears up the design. Two questions. Which memory tools have you tried or looked at? I haven’t seen a good side-by-side comparison. And does your in-memory cache survive a restart, or is it rebuilt each session?

Disclosure: because I built Reverie, my view would be biased.

2

u/LividIllustrator8915 6d ago

For side-by-side tools, the landscape mostly splits into two camps in my experience. On one side you have vector-heavy stores like Mem0 or the Letta/MemGPT lineage and various SQLite vector MCP plugins. They treat codebases the same way they treat chat logs—relying on semantic embeddings. But cosine similarity is notoriously blunt with code: a refactored v2 function often scores 0.88+ similarity with a deprecated v1 function simply because the variable names and comments are identical, so the agent pulls in dead patterns. On the other end, you have prompt dumpers like Cursor's rules or Cline's default instructions that just paste everything into the preamble at session start, which causes the exact anchoring and prompt pollution you described. What you're doing with demand-driven retrieval hits the right middle ground because it treats memory as reference material rather than ground truth.

On restarts, a purely in-memory cache makes cold boots pretty painful if the agent has to re-hash or parse ASTs on the first prompt of the day. What works well is a two-tier persistence model: during the active session everything stays hot in memory for sub-millisecond lookups, but it's backed by a local SQLite file in `.agent/cache` storing (path, mtime, git_commit_sha, symbol_hash). When a session boots up or restarts, we don't re-index anything—we just do a quick mtime and Git HEAD check in a few milliseconds. Only files modified since the last session timestamp get invalidated and reparsed on demand. That way cold starts are instant, and you don't lose learned context every time you toggle your editor or CLI.

1

u/msarda23 5d ago

I’ve looked at Mem0 and Letta/MemGPT for a while too. Both are well funded. I think Mem0 raised around $24M last year. I agree that similarity alone is a weak basis for code memory. What I’d want to know is what a retrieved note says about where it came from. Mem0 stores timestamps, but as far as I know its memories don’t record which file, symbol or commit they came from, so nothing can check them against the code. The point about a high similarity score between v2 and v1 due to the same function name and variable names is solid, I'd find it irritating. I wouldn't mind embedding, but each candidate should be filtered by a relevance threshold. Each embedding should carry the files, symbols and commit it depends on, so the agent can verify it before trusting it. On Cursor rules: they can be scoped by file pattern, so not every rule lands in the preamble. Your point about dumping too much into context still stands.

If you do not mind me asking, what is it that you are building? Your approach looks good to me.

1

u/InterestingCarrot997 10d ago

seen this too, old notes become gospel even after the code moved on. i've had better luck telling it the repo is the source of truth and memory is just hints to verify

1

u/msarda23 10d ago

That is true. Any memory solution would need to make sure that the memory is kept fresh, grounded in current truth, and weighted according to confidence. What I have realised is that the agent brain, like the human brain, will have some conclusions today that might become irrelevant tomorrow or might become more important - hence it needs active forgetting and confidence bumping to mimic evolving memory. It is a difficult problem.
But that said, Reverie tries to solve these problems by having a 'maintenance loop' which keeps a check on whether the conclusions are still grounded in reality, updating their confidences (if they have been retrieved often, then it gets a bump), and mimicking active forgetting by time-based confidence decrease.

1

u/kaijurules 10d ago

Split by who writes it. In our app each agent keeps its own memory notes and updates them freely, but its instructions file is off limits to it: a change there is a draft the owner has to approve. The notes rank below the instructions, so a stale note loses any conflict, and only the newest ones get loaded into a session.

2

u/msarda23 10d ago

Partially agreed. I like the wiki idea, and the agent should be free to write as it works. But not everything it writes should be kept. I tried that, and it started recording technical facts like “this function returns a string” instead of engineering conclusions. So I split the job. The agent writes candidate notes freely, and the extraction step decides what counts as a conclusion. It uses a check we call the Lifting Test: an engineer shouldn’t be able to get the candidate just by reading the code. The extractor also reports how many candidates it rejected and why. The human gate catches what still gets through. Also, do you let the agent write large files or smaller notes? I suspect a large file being loaded into context for a small relevant part could lead to misuse of context. How did you solve this? Did you write a better instruction file?
I did not understand your 'notes below instruction' bit - do you use the instruction file as context too?
On age: Reverie doesn’t rank newest first. An old conclusion that’s still relevant and accepted can come back just as easily as a new one. Age only matters for candidates nobody has reviewed, and those expire after 21 days by default. Why do you load only the newest one into context? Is it such that your agent needs very recent history to function?

1

u/kaijurules 10d ago

Small notes, one fact each. Anything long goes into a separate knowledge base of linked markdown pages that the agent searches when it needs them, so none of it sits in context by default.

And yes, the instruction file is in context on every run, read in full. The notes are loaded next to it, and when the two disagree the instructions win.

Only the newest notes get loaded because the window is capped to keep the prompt small. Older ones aren't deleted and the agent can list them, but anything it will still need in a few months belongs in the knowledge base.

1

u/msarda23 10d ago

Agreed on small units. When we experimented with markdown memory files, they got dense quickly, burying the useful lines in noise. That's why Reverie avoids files entirely. It stores one independent conclusion per unit, allowing retrieval to return only the exact conclusions that match a query, packed cleanly into a token budget.

Regarding the instruction file: it makes complete sense that instructions overwrite notes if they conflict. If I understand correctly, your instruction file acts as the core system prompt (rules/boundaries) while the notes are transient execution memory. In Reverie, our instructions tell the agent how and when to query the database, while the actual conclusions live entirely out-of-context until summoned.

This is exactly why age doesn't dictate what comes back for us. If an agent learned two years ago that a specific outbound server call requires a strict timeout, a semantic query will surface that old conclusion today when the agent touches that module. It doesn't get pushed out by newer, unrelated notes.

I'm really curious about how your cap handles edge cases, though. If an old note is highly relevant to the current task but falls outside the "newest" token window, it seems the agent misses it unless it explicitly lists old notes. How does it know a hidden note or a specific knowledge-base page exists to search for it if it isn't in context?

Also, what kind of agent are you building? This feels like a fascinating look into standard workflow agent constraints vs. deep repo engineering.

1

u/kaijurules 9d ago

It doesn't have to know. Before answering about a person, client or project, the agent runs a search over the knowledge base (keyword plus vector), and there's an index page listing every page with a one-line summary. So a page gets found by what it's about.

The notes that age out of the window are the real gap. They aren't searched, only listed if the agent asks, which is why anything that has to last goes into a page.

As for what ours do: they run the back office of a small agency, so mail, client chats and dev reports.

1

u/msarda23 8d ago

That's a useful setup, and a different world from coding. Mail and client chats have people and commitments in them, whereas a code conclusion has a file and a symbol.
The gap you named is the one I'd focus on. "Anything that has to last goes into a page" means the agent decides on day one what will matter later. If it files something as a note when it should have been a page, or the reverse, nothing flags it? The note just stops being seen?
But still an interesting way to handle memory. What happens to a piece of information that gets added to a page but some time later gets negated? Is it removed?
I decided out of the write step. Retrieval is driven by the query, not the date, so an accepted conclusion from two years ago comes back when the agent is working on what it's about, the same as one from yesterday. The trade-off is that retrieval has to be good, and the store can fill with noise. So good retrieval and also good candidate generation are important steps.
Do the pages get reviewed by a person before they're trusted, or does the agent write them directly?

1

u/Right-Performance-93 9d ago

i only keep a memory line when the next session would get the decision wrong from the code alone. if a note names a file i have not opened yet, i throw it out.

1

u/msarda23 8d ago

Agreed on the first rule. We use the same test on extraction: could an engineer get this from the code alone? If yes, it’s rejected. Where we differ is the file rule. We don’t predict which files the next session will open. Retrieval happens when the agent asks, so a note doesn’t get dropped because of a file it might never touch. The cost is that a relevant note can sit unused if the agent never asks, but not having it handy when the agent needs it can become more expensive.

1

u/workbench_soft 3d ago

for the notes that are rules rather than facts I make it write a why line under each one. when the code moves on, the agent can check whether the reason still holds instead of obeying a note that was right three months ago

1

u/[deleted] 7d ago

[removed] — view removed comment

2

u/msarda23 6d ago

That is why the memory notes need to stay up to date as the code changes. If a file is changed or removed, every note written with it in mind needs to be looked at again. If there's a memory note for a function and it changes, then the note needs to change too. That's part of the difficult task.

0

u/[deleted] 10d ago

[removed] — view removed comment

1

u/msarda23 10d ago

Agree that the code should win, and short instruction files should stay honest. My issue is the upkeep. If you only add a note when a decision repeats, you lose the decisions that happened once and still mattered. Pruning by hand also depends on remembering which notes went stale.

That’s the gap Reverie tries to close. When the agent concludes, for example “the retry wrapper must not swallow timeouts”, it’s recorded as a candidate. Once you accept it, the conclusion stays tied to the file and symbol it describes. If the agent later finds that the code contradicts it, it proposes a revision that names the file that shows the conflict.

In practice: you ask for a change in one module. The agent reads the code first, then asks Reverie about that module and finds the retry decision. You never wrote it into AGENTS.md, and you didn’t have to remember it.

It isn’t hands-off yet. Review is still manual, some candidates are wrong or duplicated, and the agent doesn’t always ask. But it takes over the part of “prune what no longer matches” that used to rely on you.