· ARJUN · WRITING
Why an Agent Uses the Wrong Package Manager in a New Project
New projects default to pnpm, while one older repository still needs npm ci. Both rules are correct, yet the agent picks the wrong tool because Scope disappeared during a Memory write.
Last week, you told a coding agent: “Use pnpm by default when creating a new frontend project.”
Today, you picked up an older repository and added: “Keep the existing package-lock.json in this repository, and continue to install dependencies with npm ci.”
A few days later, you asked the agent to create a new project. Its first command was npm install.
If this were only a misunderstanding in the current conversation, one correction would be enough. The harder problem is that the repository rule has entered Long-Term Memory and replaced the user’s default. Every new session can now produce the same wrong answer.
It remembered the last tool mentioned and forgot that the rule belonged to one old repository.
The previous article separated Context, Thread State, and Long-Term Memory. This article focuses on a narrower question that maps directly to code: when new information enters Memory, which record should the system update?
How the bug gets written
In conceptual pseudocode, an overly simple write path often looks like this:
# Anti-pattern: overwrite when records look similar
def remember(text):
fact = extract_fact(text)
old = memory.search(fact, limit=1)
if old and old.score > 0.85:
memory.update(old.id, fact)
else:
memory.insert(fact)
The 0.85 value is only illustrative. The real problem is not a poorly tuned threshold. It is the assumption that semantic similarity means two pieces of text represent the same memory.
After extraction, the second instruction may have been reduced to this:
{
"predicate": "package_manager",
"value": "npm"
}

The repository has disappeared. Search then finds the existing package_manager = pnpm record. Since the two records share a Predicate, the update logic changes pnpm to npm.
By this point, information has been lost twice. Extraction dropped the scope; the update removed the previous value and its source. Switching to PostgreSQL, a vector database, or a stronger model cannot reconstruct those missing fields. The storage layer merely executed the semantics the application gave it.
Identify the memory before updating it
For this kind of preference, predicate alone cannot identify a record. At minimum, the system also needs a Subject and a Scope:
[
{
"subject": "user:42",
"predicate": "package_manager",
"value": "pnpm",
"scope": {
"type": "user_default"
},
"source": "thread:17/message:86",
"status": "current"
},
{
"subject": "user:42",
"predicate": "package_manager",
"value": "npm",
"scope": {
"type": "repository",
"id": "legacy-dashboard"
},
"source": "thread:23/message:14",
"status": "current"
}
]
The Predicate is the same, but the composite identities are different:
(user:42, package_manager, user_default)
(user:42, package_manager, repository:legacy-dashboard)

These records should coexist. Only when Subject, Predicate, and Scope all point to the same thing should the system consider replacing a value.
The problem is not that a conventional database cannot understand natural language. Relational and document databases can both store scopes and versions. The application must first turn an instruction into a stable identity, then decide whether it needs an INSERT or an UPDATE.
Start with three write decisions
There is no need to begin with a large taxonomy of Memory actions. Handle the three cases most likely to cause a destructive update:
| Result | Write action |
|---|---|
| Same identity, same value | Do not add a duplicate; update the source or last-confirmed time |
| Same identity, new value clearly replaces the old one | Write a new version and mark the previous version superseded |
| Different Scope, or the relationship remains ambiguous | Store separately; do not overwrite while uncertain |
The write path should follow the same logic:
- Keep the user’s original words and message ID.
- Get the current user, repository, and task from the Runtime.
- Extract the Predicate, Value, and any validity time.
- Look up an exact Subject + Predicate + Scope identity.
- Decide whether the new value replaces that same identity, then write it.
Do not ask an LLM to guess fields already known by the Runtime. User ID, repository ID, and Thread ID should come from the execution environment. The model can help interpret whether “use npm from now on” changes the global default or still refers to the repository in front of it. If the sentence does not settle that question, ask the user.
Vector search still has a role, but it comes later. It can find candidates in unstructured records; it cannot establish identity. “Prefers concise code” and “prefers concise replies” may sit close together in embedding space. One describes coding style, the other communication style. They are not the same memory.
LongMemEval treats Knowledge Update as a distinct long-term-memory capability. The Supersede preprint goes further by asking whether an agent continues to use the current value after an older fact becomes invalid. A Memory update is therefore more than an ordinary semantic-search operation.
An applicable rule should beat a merely recent one
Correct writes solve only half the problem. If reads simply sort by update time, the repository rule may still outrank the user default in an unrelated project.
For this package-manager resolver, the application can define a simple precedence rule:
one-off task > current repository > user default
This hierarchy belongs to this application; it is not a universal Agent Memory standard. The resolver can remain straightforward conceptual code:
def resolve_package_manager(user_id, repository_id, task):
if task.install_dependencies is False:
return None
repository_rule = memory.get(
subject=user_id,
predicate="package_manager",
scope=("repository", repository_id),
)
if repository_rule:
return repository_rule
return memory.get(
subject=user_id,
predicate="package_manager",
scope=("user_default", None),
)

The same user now gets the right result in three different situations:
- A new project has no repository rule, so it uses pnpm.
legacy-dashboardmatches its repository rule, so it usesnpm ci.- A task that only checks the lockfile does not run an installation command.
Application code should preferably resolve this before building the Prompt. The Context Builder only needs to pass the currently applicable rule to the model, together with its source for explanation and debugging.
Keeping history does not mean prompting with every version
A genuine global change can still happen. If the user explicitly says, “Use npm for new projects from now on as well,” the old pnpm default should stop being current. It does not have to disappear without a trace.
A practical implementation can maintain two views:
- Current View stores the values used for ordinary reads.
- History Log records old values, new values, sources, and update times for auditing.

Normal requests read Current View. History is only needed when the user asks what used to be true, the system must explain a change, or someone is debugging an error. This preserves useful evidence without placing every old version in every model Prompt.
A History Log is not an excuse to ignore deletion requests. Data the user asks to remove, or sensitive content beyond its retention period, still has to be deleted from persistent storage or irreversibly anonymized. At most, the log may retain a deletion event that contains none of the original content.
Summary loss is a different bug
The source article also groups conversation-summary loss under “memory overwrite.” Both failures can make an agent forget, but they occur in different parts of the system.
In the current LangChain documentation, SummarizationMiddleware replaces older messages with a summary to control Context length. That summary can omit a version number, a negation, or a constraint. Important facts should therefore not exist only inside it. Store them in structured State or Long-Term Memory and retain a pointer to their source.
This is lossy Context management, not an update conflict between two long-term records. Keeping the diagnoses separate tells you whether to repair the summary policy or the Memory identity and scope.
Frameworks provide storage; the application owns the semantics
LangGraph’s long-term store organizes JSON documents with namespace + key and supports filtering and semantic search. Those primitives are useful, but they do not decide how a profile changes or when a repository rule outranks a user default.
Mem0 also shows why version details matter. In the current open-source Python implementation, add(..., infer=True) can let an LLM choose to add, update, or delete related memories. The managed algorithm Mem0 announced in April 2026 instead uses ADD-only extraction and explicitly performs no UPDATE/DELETE. Both are called Mem0, but their write behavior is not the same.
A framework can remove storage and retrieval boilerplate. It cannot answer the application’s central question: whose information is changing, and in which scope?
Five cases to check before release
| Action | Expected result |
|---|---|
| Say “Use pnpm by default for new projects” again | Do not create a duplicate; update the confirmation time or source |
Say “Use npm ci here” inside legacy-dashboard |
Add a repository rule without changing the user default |
| Explicitly say “Use npm for new projects from now on” | Update the user default; keep the old value auditable but exclude it from current reads |
| Say only “Use npm from now on” when the scope is unclear | Do not overwrite silently; confirm whether it applies globally or only to the current repository |
| Ask to delete the package-manager preference | Apply the deletion policy to Current View and any history containing the original content |
Long-Term Memory tests should not stop at “Did search return a result?” They should verify that the result belongs to the right user and repository and remains valid for the current task.
After the fix, pnpm and npm remain as two records with different scopes. A new project uses pnpm; the older repository uses npm ci. The Prompt did not become more elaborate, and the model did not need to become smarter.
The system simply stopped treating “mentioned most recently” as “applies everywhere.”
References
- LangChain, Memory overview: current definitions of thread-scoped State, Long-Term Memory, profiles, collections, and write timing.
- LangChain, Short-term memory: current message trimming, deletion, and
SummarizationMiddlewarebehavior. - Wu et al., LongMemEval: treats Knowledge Update and Temporal Reasoning as distinct capabilities in long-term interactive memory.
- Patel, Supersede: studies the supersession gap after old facts become invalid; it remains a preprint as of publication.
- Mem0 open-source repository and the current
Memory.addimplementation: used to verify version-specific write behavior. - Original reference article: the starting point for the topic.