
Paul-SMy MEAN Agent Remembered Everything. That Was the Bug I reproduced the failure during a...
I reproduced the failure during a red-team test.
10:14 Tenant: northwind
User: Where should we send the replacement?
10:14 Agent memory retrieved:
“Deliver replacement orders to the Chicago warehouse.”
10:14 Source tenant: copper
The agent remembered a real instruction. It was relevant to the question and ranked highly in semantic search.
It also belonged to another customer.
This was not a hallucination. The model did not invent the warehouse. It accurately used information our application should never have placed in its context.
Nothing in the MEAN stack had crashed. Angular rendered the conversation, Express accepted the request, Node completed the workflow, and MongoDB returned a matching document.
The system was working exactly as we had designed it.
That was the bug.
The first version of the agent stored almost everything:
Each record received an embedding. Before generating an answer, the Node service searched for semantically similar memories and added the strongest matches to the prompt.
The retrieval query considered relevance, but it did not enforce ownership strongly enough. We had treated memory as a smarter version of chat history.
It is not.
An audit record, a temporary workflow value, and a durable customer preference may all contain text, but they should not have the same retention period or retrieval rules.
We replaced the generic memory document with an explicit contract:
type AgentMemory = {
tenantId: string;
principalId: string;
purpose: "current_task" | "approved_preference";
source: "user" | "verified_tool";
sensitivity: "standard" | "restricted";
content: string;
embedding: number[];
createdAt: Date;
expiresAt: Date;
status: "active" | "revoked";
};
A memory could no longer exist simply because the model considered it useful.
The application had to know:
Anything that could not answer those questions stayed outside the agent’s retrievable memory.
We also separated four concepts that had previously shared one collection: short-lived conversation context, workflow state, approved preferences, and audit evidence.
Audit records remained available to authorized services, but they were no longer automatically fed back into the model.
The repair was not another system prompt. It was three application-level gates.
Only approved memory types could be persisted.
Raw tool output was not automatically trusted. A webpage, uploaded document, or CRM note could contain incorrect information or malicious instructions.
OWASP’s AI Agent Security Cheat Sheet identifies memory poisoning as a distinct risk and recommends validating content, isolating memory between users and sessions, and enforcing expiration and size limits.
Every retrieval included identity and lifecycle filters before semantic ranking:
const memories = await Memory.aggregate([
{
$vectorSearch: {
index: "agent_memory",
path: "embedding",
queryVector,
numCandidates: 100,
limit: 8,
filter: {
tenantId: { $eq: tenantId },
principalId: { $eq: principalId },
status: { $eq: "active" },
expiresAt: { $gt: new Date() }
}
}
}
]);
The corresponding vector index must define those fields as filters.
MongoDB’s multi-tenant Vector Search guidance recommends storing a tenant_id and applying it as a pre-filter so queries only return documents belonging to that tenant.
The important rule is simple: semantic relevance must never override authorization.
Temporary memories received an expiry date, backed by a MongoDB TTL index:
MemorySchema.index(
{ expiresAt: 1 },
{ expireAfterSeconds: 0 }
);
However, TTL deletion is asynchronous. MongoDB explains that expired documents can remain until its background process removes them.
That means TTL is useful for cleanup, but it is not an authorization boundary. The application must still exclude expired or revoked memory during every read.
A user pressing “Forget this” should affect retrieval immediately.
The backend changes exposed a product problem: users had no way to see what the agent remembered.
We added a small Angular memory panel showing:
This made incorrect memories observable before they silently influenced several future conversations.
It also changed our testing. We stopped asking only, “Did the agent remember the preference?” We added a second question:
Can it still retrieve that preference after the user changes tenants, revokes it, or reaches its expiry date?
That second question caught more important defects than the first.
Agent benchmarks often reward successful recall. Production systems must also measure forbidden recall.
Can the agent avoid retrieving another tenant’s data? Can it forget expired workflow state? Can it distinguish verified preferences from untrusted text? Can it explain why a memory entered the prompt?
These boundaries deserve attention when teams hire MEAN stack developers for agentic applications. At Spaculus Software, we treat memory as governed application data, not an unlimited extension of the model’s context window.
The safest agent is not the one that remembers everything.
It is the one that can prove why each memory exists, who owns it, and when it must stop influencing decisions.
What would your agent need to forget before you trusted it with customer data?