Most AI agent memory systems start with a simple idea: save the conversation, embed the messages, and retrieve the most relevant pieces later.
It sounds sensible. It is also where a lot of systems quietly start to fail.
Real conversations do not unfold as neat, isolated question-and-answer pairs. People change topics halfway through. They answer a question ten turns later. They revisit an earlier subject after a long detour. They say things like “yes,” “the second one,” or “back to the earlier point,” and expect the agent to understand exactly what they mean.
That expectation is fair. Human conversation is not a flat log. It is a set of overlapping threads, decisions, clarifications, preferences, and unfinished business.
And conversational memory is only one part of the story.
A serious AI agent also needs to deal with explicit long-term memory requests from the user, external data coming from tools or MCP servers, the difference between personal and organizational memory, and a deeper kind of memory that many teams overlook entirely: remembering past decisions and whether they worked.
That is where most memory designs stop being “chat history with search” and start becoming actual system architecture.
The real challenge is not simply storing what was said. It is deciding:
- what kind of memory this is
- where it came from
- who it belongs to
- how long it should live
- whether it is trustworthy enough to guide action
- when it should be brought back into context
A real memory system is not just a recall mechanism. It is a structured layer of operational intelligence.
This article lays out what that looks like in practice: why flat Q&A memory breaks, why graph-based memory makes sense, how short-term and persistent memory should work together, why classic RAG is not enough for organizational knowledge, and what a practical end-to-end workflow looks like in real work scenarios.
The short version is this:
Do not model agent memory as a transcript archive. Model it as typed, scoped, contextual knowledge with relationships, provenance, and decision value.
That is where real agent memory begins.
The problem with flat Q&A memory
The most common first version of agent memory is simple:
- save every user message
- save every assistant response
- embed the text
- later, run similarity search
- inject the top matches into the prompt
This approach works well enough for small demos. It breaks down surprisingly quickly in real conversations.
Imagine this sequence:
- The user asks about annual leave policy
- The conversation shifts to API authentication bugs
- The user asks about dinner ideas
- Ten turns later, the user says, “okay, back to the policy thing”
A flat retrieval system may pull in the wrong material because it sees only text similarity, not conversational structure. If enough overlapping words appear in the API discussion or the dinner suggestions, those may outrank the actual policy thread.
The deeper issue is that flat memory collapses everything into one dimension: sequence.
That means it loses crucial meaning:
- which messages belong to the same subject
- whether a message continues a topic or starts a new one
- what question a short answer is replying to
- whether something was a decision, a correction, a fact, or an open issue
- whether a memory belongs to the user, the organization, or a tool result
- whether the information is still valid
A flat log tells you what happened in order. It does not tell you what matters now.
That is why a memory system should not ask only:
What text looks similar?
It also needs to ask:
What thread is active?
What facts belong to it?
What is unresolved?
What source is authoritative?
What memory is safe and useful to apply right now?
That shift changes everything.
Conversations are threads, not logs
A much better model is to treat conversation as a set of threads.
A thread is a coherent subject, goal, or problem area. Examples might include:
- annual leave policy
- API authentication bug
- AI agent memory architecture
- client-specific terminology
- job hunting in EU time zones
Threads can be active, paused, resumed, branched, resolved, or abandoned. They can also overlap. A single message may continue one thread while opening a subtopic in another.
For example:
“Back to the memory topic — but specifically, how would this work with organization policies?”
That message is not just another turn in a sequence. It is doing at least two things:
- reopening an existing thread
- creating a more specific design branch inside it
That is exactly the kind of structure flat memory misses.
A useful memory system needs to understand not just what was said, but how conversation moves.
Memory is not one thing
A lot of poor designs happen because everything gets thrown into one bucket called “memory.” In practice, agent memory has multiple layers, each serving a different purpose.
Turn memory
This is the raw conversational record.
A turn stores things like:
- speaker
- text
- timestamp
- dialogue role
- references to other turns
- thread membership
This layer matters, but it is not enough by itself.
Thread memory
This groups related turns into meaningful conversational units.
A thread stores:
- title
- summary
- status
- related entities
- recent activity
- links to parent or child threads
This is what lets the system know that “the earlier point” refers to a known topic rather than a random older sentence.
Semantic fact memory
These are reusable facts extracted from conversation.
Examples:
- the user prefers concise responses
- for this project, “site” means operational facility
- the user wants persistent memory separated from working memory
- the organization uses specific terminology for compliance documents
These should not remain trapped inside raw text.
Episodic or process memory
This stores what happened during an interaction.
Examples:
- the assistant proposed three options
- the user rejected one because it was too brittle
- a design question about persistence remains open
- a decision was made to use a graph-based model
This is memory of progress, not just memory of facts.
Operational decision memory
This is where things get more serious.
Some agents do not only answer questions. They make or influence decisions. They choose actions, workflows, escalation paths, data sources, and operating strategies.
Those agents need memory of past decisions and outcomes.
Examples:
- using stale permit data caused a planning error
- asking a clarifying question reduced failure in ambiguous requests
- auto-retrying a workflow created duplicate work orders
- source A proved more reliable than source B for compliance checks
This is not ordinary conversational recall. It is behavioral memory.
That distinction matters.
Why graph-based memory fits the problem
Once you accept that memory contains turns, threads, facts, questions, answers, decisions, policies, and operational outcomes, the natural structure is no longer a list. It is a graph.
A graph allows the system to represent both objects and relationships.
That matters because conversation is full of relationships:
- this answer responds to that question
- this fact belongs to that project
- this thread reopens an earlier topic
- this rule overrides an older version
- this tool result supports a decision
- this operational failure should influence future action selection
A graph can model those links directly.
Typical node types
A graph memory engine might include:
TurnThreadQuestionAnswerFactDecisionOpenLoopEntityPolicyRuleTermDefinitionExternalObservationOperationalDecisionSummary
Typical relationships
Useful edges might include:
BELONGS_TO_THREADANSWERSRESPONDS_TODERIVED_FROMREOPENSRESOLVESSUBTHREAD_OFMENTIONS_ENTITYCONTRADICTSVALIDATED_BYOVERRIDDEN_BYSUPPORTS_DECISION
This means the system can understand that a short answer like “both” is not a standalone fact. It is the answer to a specific earlier question. It can understand that a terminology rule belongs to a project, not to the user globally. It can understand that a policy rule is authoritative in a way a casual conversational statement is not.
That is the real value of the graph: not complexity for its own sake, but preserved meaning.
A simple graph model
Here is a simplified TypeScript-style representation:
type Speaker = "user" | "agent" | "tool" | "system";
type DialogueAct =
| "user_question"
| "user_answer"
| "user_preference"
| "user_constraint"
| "user_correction"
| "agent_question"
| "agent_answer"
| "agent_summary"
| "decision"
| "open_loop"
| "external_observation";
type ThreadStatus = "active" | "paused" | "resolved" | "abandoned";
type MemoryDomain =
| "personal"
| "organizational"
| "session"
| "operational"
| "external";
interface TurnNode {
id: string;
type: "turn";
speaker: Speaker;
text: string;
timestamp: string;
dialogueAct: DialogueAct;
threadIds: string[];
respondsToTurnId?: string;
entities?: string[];
embedding?: number[];
}
interface ThreadNode {
id: string;
type: "thread";
title: string;
summary: string;
status: ThreadStatus;
domain: MemoryDomain;
createdAt: string;
updatedAt: string;
parentThreadId?: string;
entityIds: string[];
}
interface FactNode {
id: string;
type: "fact";
domain: MemoryDomain;
subject: string;
predicate: string;
object: string;
scope: "session" | "thread" | "project" | "team" | "organization" | "global";
authority: "user_declared" | "model_inferred" | "tool_reported" | "policy_approved";
confidence: number;
sourceTurnId?: string;
validFrom?: string;
validUntil?: string;
}
interface Edge {
from: string;
to: string;
type:
| "BELONGS_TO_THREAD"
| "RESPONDS_TO"
| "ANSWERS"
| "DERIVED_FROM"
| "MENTIONS_ENTITY"
| "REOPENS"
| "RESOLVES"
| "SUBTHREAD_OF"
| "CONTRADICTS"
| "VALIDATED_BY";
}
This model is intentionally modest, but even this small amount of structure is dramatically more useful than a flat transcript plus embeddings.
Explicit long-term memory requests deserve their own pipeline
One of the biggest gaps in weak memory systems is that they do not distinguish between:
- something the agent inferred might be worth remembering
- something the user explicitly asked the agent to remember
Those are fundamentally different.
If a user says:
- “Remember that I prefer concise responses.”
- “Please remember that for this client we say ‘location’ instead of ‘site.’”
- “Remember that this project treats weekend work as high-risk.”
that is not just conversational content. It is a memory instruction.
A serious system should treat it that way.
Why this matters
Explicit memory requests carry stronger intent. They often matter more than inferred memory. But they also require more care, because not everything the user asks to remember should become global, permanent, or even acceptable.
For example:
- “Remember my password” should be rejected
- “Remember I like short responses” may be accepted as personal long-term memory
- “Remember that in this project ‘site’ means operational facility” should be project-scoped, not global
- “Remember this confidential board discussion” may require sensitivity controls
That means “remember this” needs its own handling path.
A practical representation
interface DeclaredMemory {
id: string;
memoryType: "declared_user_memory";
text: string;
normalizedFact?: {
subject: string;
predicate: string;
object: string;
};
requestedBy: string;
domain: "personal" | "organizational";
scope: "thread" | "project" | "organization" | "global";
retention: "session" | "long_term" | "until_changed";
sensitivity: "low" | "internal" | "confidential" | "restricted";
accepted: boolean;
reasonIfRejected?: string;
sourceTurnId: string;
createdAt: string;
}
A better rule
Do not treat “remember this” as a direct write to long-term memory.
Treat it as a memory intent that goes through:
- intent detection
- domain classification
- scope determination
- safety and policy checks
- normalization
- accepted storage in the right memory layer
That design prevents accidental misuse.
Real scenario: project terminology
A project lead says:
“Remember that for this customer, we always use ‘operational facility’ instead of ‘site.’”
This should not become a global personal preference. It should become:
- organizational memory
- scoped to the relevant customer or project
- reusable for future content generation in that context
- attached to the authority of the person or system who set it
That is the difference between memory and pollution.
External data from MCP or tools is not ordinary memory
Another major failure mode happens when systems treat external data the same way they treat conversation-derived facts.
They should not.
If information comes from MCP, APIs, databases, or tools, the memory system must preserve provenance.
Because the real question is not just “what is the fact?” It is also:
- where did it come from?
- when was it retrieved?
- how long is it valid?
- how trustworthy is it?
- should it influence action now, later, or not at all?
A tool result is often evidence, not timeless truth.
Different kinds of external memory
Ephemeral external context
Used only in the current reasoning cycle.
Examples:
- live asset status
- current permit state
- real-time weather
- current resource availability
Cached context
Stored briefly with expiry.
Examples:
- recent API results
- current plan snapshots
- last fetched working data
Durable organizational fact
Persisted only if the data is stable and policy allows it.
Examples:
- site hierarchy
- asset ownership relationships
- approved vocabulary
- system-of-record identifiers
Evidence-attached memory
Stored only together with source and validity metadata.
Examples:
- “According to the planning system at 09:10, crane availability was false.”
- “The policy service returned version 4.2 of the escalation rule.”
External source envelope
interface MemorySource {
sourceType:
| "user_declared"
| "user_inferred"
| "agent_inferred"
| "mcp_returned"
| "tool_returned"
| "document_extracted"
| "operational_observation";
sourceSystem?: string;
sourceReference?: string;
retrievedAt?: string;
validAt?: string;
validUntil?: string;
trustLevel: number;
}
Real scenario: maintenance scheduling
An AI planning agent receives live MCP data:
- Crane 17 unavailable
- Crew B on leave
- Permit P-203 still pending
This data absolutely matters to the current decision. But it should not automatically become stable long-term memory. It should be used as current evidence, cached briefly if helpful, and attached to freshness metadata.
What may deserve long-term persistence is not the raw availability snapshot, but the operational consequence:
- a scheduling decision was deferred because permit approval was pending
- live resource validation was required before planning could proceed
That is how you avoid a system confidently repeating stale facts later.
Personal memory and organizational memory must stay separate
This is not a minor implementation detail. It is one of the core architecture boundaries.
A lot of systems create confusion, privacy risk, and plain bad behavior because they mix personal memory and organizational memory in the same retrieval pool.
They should be separate domains.
Personal memory
This includes things like:
- user preferences
- preferred explanation style
- recurring personal constraints
- user-specific terminology
- standing instructions from the individual
Organizational memory
This includes:
- approved policies
- governance rules
- shared terminology
- domain definitions
- client-specific rules
- site or asset hierarchies
- standard procedures
Session memory
This is short-lived working context for the current interaction.
External context memory
Tool and system results, usually temporary or freshness-bound.
Operational decision memory
Action history and lessons learned from behavior and outcome.
Why the distinction matters
Suppose the user says:
“I prefer concise explanations.”
That is personal memory.
Suppose the organization policy says:
“All safety incident reports must include regulatory references and formal wording.”
That is organizational memory.
Suppose the agent has learned:
“Using a casual summary format for incident reports caused review rejection.”
That is operational decision memory.
If all of that sits in one undifferentiated memory layer, the assistant may produce a short casual incident report that violates policy while thinking it is being helpful.
That is not a model problem. It is a memory model problem.
Useful ownership and access fields
type MemoryOwner = "user" | "team" | "organization" | "system";
interface AccessPolicy {
visibility: "private" | "team" | "org" | "system_internal";
readableBy: string[];
writableBy: string[];
}
Every durable memory item should know:
- who owns it
- who can read it
- who can change it
- what scope it applies to
Real scenario: same user, different output context
A consultant tells the assistant:
“When you chat with me, keep it brief.”
That is personal memory.
The organization also has a policy:
“Client-facing reports must be formal and include compliance references.”
When generating a report, the system should apply the organizational rule, not the personal chat preference. When replying conversationally, the reverse may be true.
That only works if memory domains are separated and ranked properly.
Operational decision memory is what makes agents better over time
This is one of the most valuable and least discussed memory types.
Some agents do not just answer. They plan, route, escalate, schedule, retry, or execute. Those agents need memory of decisions and their outcomes.
Without that, they make the same mistakes again and again.
Operational decision memory stores things like:
- what action was chosen
- in what context
- why it was chosen
- what happened afterward
- whether that pattern should be reused or avoided
Example representation
interface OperationalDecisionMemory {
id: string;
type: "operational_decision";
contextSignature: string;
domain: string;
decision: string;
rationale: string;
confidence: number;
outcome: "success" | "failure" | "partial" | "unknown";
outcomeEvidence?: string[];
shouldReuse: boolean;
shouldAvoid: boolean;
applicabilityConditions: string[];
createdAt: string;
reviewedAt?: string;
}
Why it matters
Without operational memory, the system may:
- keep retrying a workflow that creates duplicates
- keep acting without clarification in ambiguous cases
- keep trusting a stale cache in high-risk decisions
- keep using a weak data source when a better one exists
With operational memory, it can learn:
- ask when ambiguity is high
- re-validate live data before critical scheduling
- prefer policy-approved sources over retrieved snippets
- avoid previously harmful automation shortcuts
Real scenario: support workflow
A support agent previously handled ambiguous refund requests by auto-selecting the most likely customer account. That worked several times, until it caused a serious misapplied refund.
A good system stores:
- context: ambiguous account match
- decision: auto-select likely account
- outcome: failure
- lesson: do not auto-process in ambiguous account cases
- future guidance: ask for confirmation instead
That is behavior-shaping memory.
Important caution
Operational memory can become dangerous if one-off outcomes get promoted into permanent rules too quickly.
A single success does not equal a valid policy.
So operational decision memory should separate:
- observed outcome
- candidate pattern
- reviewed rule
That prevents the system from turning anecdotes into doctrine.
Why classic RAG is not enough for organizational knowledge
Classic RAG usually works like this:
- chunk documents
- embed them
- retrieve the top matches
- let the model answer from them
Useful? Yes. Sufficient? Not for serious memory architecture.
The problem is that RAG retrieves text fragments, not governed knowledge.
That creates familiar failure modes:
- chunk boundaries split meaning
- older policy text competes with current policy text
- semantically similar text outranks authoritative text
- the model improvises across incomplete fragments
- relationships between rules, entities, and exceptions disappear
That is why a lot of RAG systems produce answers that sound grounded but still go wrong.
The issue is not that retrieval is useless. It is that unstructured retrieval is too weak to be the core memory architecture for organizational knowledge.
Organizational memory should be structured before runtime
If you want reliable organizational memory, the ingestion process needs to do more than chunk and embed.
It should turn raw sources into structured memory objects.
That means extracting:
- entities
- relationships
- policy rules
- definitions
- constraints
- validity windows
- provenance
- ownership
- authority
- review state
In other words, the system should build a knowledge layer, not just a vector store.
Different source types need different ingestion
Policies and SOPs need:
- effective dates
- authority level
- exceptions
- obligations
- supersession handling
Master data needs:
- stable IDs
- entity relationships
- canonical naming
- version control
Transactional records need:
- time awareness
- event ordering
- freshness strategy
MCP or tool results need:
- provenance
- expiry
- confidence
- mapping to authoritative entities
Human-authored notes need:
- author
- scope
- review state
- trust level
Operational history needs:
- context
- action
- result
- applicability
Trying to push all of this through one identical “chunk and embed” pipe is a design shortcut that eventually becomes a reliability problem.
One runtime memory system, multiple ingestion pipelines
The best design is often not one pipeline for everything, but one runtime interface fed by specialized ingestion pipelines.
At runtime, the agent should be able to ask for memory in one consistent way:
- give me relevant personal preferences
- give me applicable organizational rules
- give me fresh external evidence
- give me prior operational decisions in similar contexts
- give me the active conversation thread and open loops
But under the hood, those memory types should have been produced differently.
Example ingestion paths
Personal memory ingestion
- detect explicit memory request
- extract preference or instruction
- determine scope
- run privacy and safety checks
Organizational memory ingestion
- parse documents, tables, ontologies, or system exports
- normalize entities
- extract rules, terms, and relationships
- assign authority and validity
MCP/tool ingestion
- decide whether the data is ephemeral, cached, or durable
- attach provenance
- assign expiry
- optionally map into known entities
Operational memory ingestion
- capture action, rationale, and outcome
- mark pattern confidence
- separate observed behavior from approved rule
That gives you one runtime experience without flattening every source into the same weak structure.
Retrieval should rank more than similarity
One of the biggest reasons memory systems hallucinate or misbehave is that ranking is too simplistic.
A serious memory system should rank by more than semantic similarity. It should consider:
- semantic relevance
- freshness
- authority
- trust
- scope match
- thread relevance
- privacy and access constraints
- operational importance
Example ranking object
interface MemoryRankScore {
semantic: number;
freshness: number;
authority: number;
trust: number;
scopeMatch: number;
threadMatch: number;
decisionImpact: number;
final: number;
}
This allows the runtime to prefer:
- an approved policy over a similar old note
- an explicit user preference over an inferred one
- a live system result over a stale cache
- a reviewed operational rule over a one-off anecdotal success
That is how retrieval becomes safe enough to influence behavior.
In-memory and persistent memory should work as a pair
A strong system needs both.
They are not alternatives. They are complementary layers.
In-memory memory
This is fast working memory for the current session.
Typical contents:
- recent turns
- active thread
- unresolved clarifications
- current tool outputs
- rolling session summaries
Benefits:
- very fast
- cheap to update constantly
- ideal for immediate reasoning
Weaknesses:
- lost on restart
- not suitable for cross-session continuity
- not enough for durable knowledge
Persistent memory
This is durable memory in a database, graph store, or multi-model store.
Typical contents:
- accepted user preferences
- organizational rules and definitions
- thread summaries worth restoring later
- long-lived open loops
- operational decision history
- stable facts and structured relationships
Benefits:
- survives sessions
- supports continuity
- supports governance and provenance
- enables structured retrieval
Weaknesses:
- slower
- more expensive
- easier to pollute if overused
- requires deliberate modeling
Best practice
Use persistent memory to selectively hydrate working memory. Do not fetch the entire long-term memory store on every turn. Do not keep everything only in RAM either.
A good system uses:
- working memory for immediate context
- persistent memory for durable continuity
- a retrieval layer to pull in only what matters now
That is the balance.
Where performance problems actually show up
A lot of memory systems become slow because they put too much work on the hot path.
Common mistakes:
- embedding every turn synchronously
- persisting every turn before replying
- recalculating large summaries constantly
- traversing too much of the graph on every message
- stuffing too much retrieved memory into the prompt
The answer is to separate hot path from cold path.
Hot path
This must happen before the response:
- store raw turn in session memory
- classify the turn
- resolve the active thread
- retrieve minimal relevant context
- assemble working memory
- generate the response
Cold path
This can happen afterward or asynchronously:
- deeper fact extraction
- importance scoring
- embedding high-value records
- updating long-term summaries
- graph cleanup and deduplication
- operational outcome analysis
This split is one of the simplest ways to improve both speed and quality.
How the runtime should process incoming information
Let’s turn the architecture into a concrete workflow.
Step 1: input arrives
The input could be:
- a user message
- a tool result
- an MCP response
- a document ingestion event
- a workflow outcome
Step 2: classify the origin
The system first decides what kind of input this is:
- conversational
- external observation
- memory instruction
- organizational ingestion
- operational event
That classification shapes everything that follows.
Step 3: classify intent and role
The system asks:
- is this a question?
- is it an answer to an earlier question?
- is it a correction?
- is it a memory request?
- is it reopening an older thread?
- is it introducing a rule or a preference?
- is it a decision outcome?
This works best as a hybrid of rules and model inference.
Rules help with explicit signals:
- “remember that…” strongly suggests a memory instruction
- “back to…” strongly suggests thread reactivation
- very short replies after a question often suggest answer binding
Models help with ambiguity:
- “that won’t work” may be a rejection, a constraint, or a temporary concern
- “the browser one is too limiting” likely refers to an earlier option
- “this should apply everywhere” suggests broader scope
Step 4: resolve the thread
Now the system decides whether the input:
- continues the current thread
- reopens a paused thread
- starts a new thread
- creates a subthread
This can use:
- explicit phrase cues
- entity overlap
- semantic similarity
- recency
- current thread momentum
Step 5: determine memory type and domain
The system asks:
- should this stay in session only?
- is it personal?
- is it organizational?
- is it external evidence?
- is it operational learning?
- is it an open loop?
Step 6: choose a storage strategy
Possible results:
- do not store
- keep in working memory only
- cache with expiry
- persist as durable fact
- persist as operational decision memory
- persist only after review
- reject due to policy or sensitivity
Step 7: build the working context
The runtime assembles the smallest useful set of memory:
- active thread summary
- recent relevant turns
- linked question-answer context
- applicable personal preferences
- authoritative organizational rules
- fresh external evidence
- relevant operational warnings or lessons
- unresolved open loops
Step 8: generate the response
The model answers from this curated context, not from a giant transcript dump.
Step 9: enrich memory afterward
After responding, the system can do heavier work:
- update summaries
- embed high-value items
- persist accepted facts
- update graph relationships
- score operational outcomes
- compact or archive stale memory
That is how memory becomes manageable at scale.
Example: turn processing flow
interface TurnAnalysis {
dialogueAct: DialogueAct;
candidateThreadIds: Array<{ threadId: string; score: number }>;
extractedEntities: string[];
likelyAnswersQuestionId?: string;
memoryIntent?: "none" | "remember_fact" | "remember_preference";
domainHint?: MemoryDomain;
shouldCreateOpenLoop: boolean;
}
async function processIncomingTurn(turn: TurnNode) {
const analysis = await analyzeTurn(turn);
const activeThread = await resolveThread(analysis, turn);
await linkTurnToThread(turn.id, activeThread.id);
if (analysis.likelyAnswersQuestionId) {
await createEdge(turn.id, analysis.likelyAnswersQuestionId, "ANSWERS");
}
if (analysis.memoryIntent && analysis.memoryIntent !== "none") {
await handleMemoryIntent(turn, analysis);
}
const facts = await extractFacts(turn, analysis);
for (const fact of facts) {
if (fact.confidence > 0.8) {
await persistFact(fact);
await createEdge(fact.id, turn.id, "DERIVED_FROM");
}
}
if (analysis.shouldCreateOpenLoop) {
await createOpenLoop({
description: summarizeOpenLoop(turn.text),
threadId: activeThread.id,
openedByTurnId: turn.id
});
}
return buildContextForGeneration(activeThread.id, turn);
}
The important point is not the exact implementation. It is that turn handling includes classification, linking, domain decisions, and storage decisions — not just “save text and embed.”
Example: binding short answers to earlier questions
This is one of the highest-value features in a real conversation memory engine.
async function linkAnswerToQuestion(turn: TurnNode): Promise<string | undefined> {
if (turn.speaker !== "user") return undefined;
const recentAgentQuestions = await getRecentUnansweredAgentQuestions(5);
for (const question of recentAgentQuestions) {
const likely = await isLikelyAnswerToQuestion(turn.text, question.text);
if (likely.score > 0.8) {
await createEdge(turn.id, question.id, "ANSWERS");
return question.id;
}
}
return undefined;
}
Without this, short replies like “both,” “yes,” or “the second one” are almost useless. With it, they become meaningful memory.
Example: deciding what deserves persistence
interface PersistenceDecision {
persistTurn: boolean;
persistFacts: boolean;
cacheOnly: boolean;
reason: string;
}
function decidePersistence(turn: TurnNode, analysis: TurnAnalysis): PersistenceDecision {
if (analysis.memoryIntent === "remember_fact") {
return {
persistTurn: true,
persistFacts: true,
cacheOnly: false,
reason: "Explicit user memory request"
};
}
if (analysis.dialogueAct === "user_preference") {
return {
persistTurn: true,
persistFacts: true,
cacheOnly: false,
reason: "Stable preference"
};
}
if (analysis.dialogueAct === "user_answer" && turn.text.length < 15) {
return {
persistTurn: false,
persistFacts: false,
cacheOnly: true,
reason: "Short answer is only useful if linked"
};
}
return {
persistTurn: false,
persistFacts: false,
cacheOnly: true,
reason: "Keep mainly in session or summary"
};
}
The key principle is selectivity. Not everything belongs in long-term memory.
Real work scenario: enterprise maintenance planning
Consider an AI planning assistant used in an asset-intensive environment.
A planner asks:
“Can we schedule the pump replacement for Saturday?”
A strong memory system should pull from multiple layers.
Personal memory
The planner prefers concise summaries.
Organizational memory
Weekend high-risk maintenance requires supervisor approval and permit clearance.
External context
Live MCP data shows:
- required permit still pending
- one critical technician unavailable
- the asset has a shutdown conflict
Operational decision memory
The system remembers that a previous plan failed because stale permit data was used during scheduling.
The agent should not simply say “yes” because similar work has been scheduled before. It should say, in effect:
- current permit state blocks approval
- required technician is unavailable
- similar decisions previously failed when live permit status was not revalidated
- the safe next action is to draft a pending schedule, not auto-confirm the work
That is memory doing real work.
Real work scenario: client-specific language and document output
A consulting team uses an AI assistant to generate project documentation.
A user says:
“Remember that for Client A, we use ‘operational facility’ instead of ‘site.’”
That should not be stored as a global personal preference. It should be:
- organizational memory
- scoped to the client or project
- activated during content generation for that client
- ignored elsewhere
Later, someone else uses the assistant for Client B. A weak memory system might apply the wrong terminology globally. A scoped system will not.
That is why memory needs domains and recall conditions, not just raw text storage.
Real work scenario: support agent improving its own behavior
A support agent previously auto-matched ambiguous customer accounts during refunds. It worked several times, then caused a major mistake.
A useful operational memory record would store:
- context: ambiguous customer identity
- decision: auto-selected best match
- outcome: failure
- lesson: ambiguous identity requires confirmation
- reuse guidance: do not auto-process this class of request without explicit confirmation
Later, when a similar case appears, the system uses that operational memory to ask a clarifying question instead of repeating the old behavior.
That is memory improving judgment, not just recall.
When the system should ask clarifying questions
Memory systems should not guess recklessly when the cost of being wrong is high.
Good moments to clarify include:
- scope is unclear
- personal vs organizational is unclear
- the statement may be temporary rather than long-term
- a short answer could refer to multiple prior questions
- an operational pattern looks relevant but applicability is uncertain
Examples:
- “Should I remember that as a long-term preference?”
- “Is that for this project only, or should it apply across the organization?”
- “When you say ‘the second one,’ do you mean persistent memory over in-session memory?”
- “Are we continuing the earlier design thread, or starting a new branch?”
The goal is not to ask all the time. It is to ask when ambiguity would make memory or action unsafe.
Hallucination is often a memory design problem
Hallucinations do not come only from the model. They often come from weak memory structure.
Systems hallucinate more when:
- everything is flattened into text
- source authority is missing
- stale and current facts are mixed
- contradictions are not represented
- retrieval surfaces fragments without relationships
- the model must improvise where the memory layer should have decided
Better controls include:
Provenance-first memory
Every important memory should be traceable to:
- user declaration
- approved policy
- external tool or MCP response
- document source
- operational observation
Validity windows
Facts should know when they became valid and when they go stale.
Contradiction-aware memory
The system should represent:
- superseded facts
- overridden rules
- competing sources
- review state
Typed memory objects
Instead of passing only raw snippets into the prompt, pass structured objects like:
- personal preference
- policy rule
- live external observation
- operational warning
- project-specific definition
This reduces the room for improvisation.
A general memory envelope
A mature system often needs a shared wrapper for different memory types:
type MemoryKind =
| "conversation_turn"
| "thread_summary"
| "declared_user_memory"
| "inferred_personal_fact"
| "organizational_fact"
| "policy_rule"
| "external_observation"
| "operational_decision"
| "open_loop"
| "term_definition";
interface MemoryRecord {
id: string;
kind: MemoryKind;
domain: "personal" | "organizational" | "session" | "operational" | "external";
scope: "session" | "thread" | "project" | "team" | "organization" | "global";
authority: "user_declared" | "policy_approved" | "system_observed" | "model_inferred" | "tool_reported";
trustScore: number;
importanceScore: number;
privacyClass: "public" | "internal" | "confidential" | "restricted";
sourceRefs: string[];
validFrom?: string;
validUntil?: string;
staleAfter?: string;
createdAt: string;
updatedAt: string;
payload: unknown;
}
This lets one runtime pipeline work across many kinds of memory without forcing everything into the same raw text form.
The architecture I would recommend
If I had to reduce all of this into a practical system design, it would look like this:
1. Working memory layer
Fast, in-memory session context:
- active thread
- recent turns
- open clarifications
- live tool outputs
2. Personal memory layer
Durable user-specific memory:
- preferences
- stable constraints
- explicit standing instructions
3. Organizational memory layer
Structured enterprise knowledge:
- entities
- relationships
- policies
- terminology
- approved rules
- master data
4. External contextual memory layer
Tool, MCP, and API evidence:
- provenance-first
- freshness-aware
- often cached, sometimes persisted
5. Operational decision memory layer
Behavior and outcome history:
- actions taken
- context
- result
- reuse or avoid guidance
- review state
6. Unified retrieval orchestrator
One runtime component that:
- filters by access and scope
- ranks by authority, freshness, and relevance
- resolves contradictions
- assembles structured working context
That design gives the agent one coherent memory experience without collapsing everything into one noisy store.
Closing thought
The future of AI agent memory is not about saving more transcript text.
It is about building memory that understands:
- context
- scope
- ownership
- authority
- freshness
- relationships
- outcomes
- when a fact should matter again
A flat list of questions and answers is too small a model for real agent behavior.
A serious memory system must be able to remember:
- what the conversation was about
- what question a short answer belonged to
- what the user explicitly asked to remember
- what came from a tool and how fresh it is
- what belongs to the user and what belongs to the organization
- what decision was made before and whether it worked
- what policy is authoritative
- what is still unresolved
That is the difference between chat history and agent memory.
If you are building AI agents, the question is not:
How do I store the conversation?
The real question is:
How do I structure memory so the agent can safely reactivate the right fact, decision, policy, or preference at the right time, from the right source, under the right scope?
That is where agent memory becomes real.