
Empowering Agents with Memory
Ted Werbel · 7/14/2025 · 9 min read
We've all been there. You're deep in a conversation with a chatbot and you have to repeat yourself. The agent you were just talking to has forgotten your name, what you asked for earlier in the conversation and any preferences you've ever shared. It feels clunky, impersonal and frankly - not very intelligent.
What if your AI assistants could remember? What if they could recall past interactions, learn your preferences and maintain context across days, weeks or even months? This is the power of long-term memory, and it's the key to transforming a simple chatbot into a truly personalized and context-aware companion. With AgentKit and Mem0, you can build sophisticated agents that do exactly that. Let's dive into how!
What is Memory for Agents
Before we dive into design patterns, it's crucial to distinguish true memory from the illusions we've grown accustomed to. Large context windows in LLMs can feel like memory, but they are temporary and stateless. They treat all information equally and forget everything the moment a session ends. True memory is persistent, allowing an agent to retain and recall relevant information across days, weeks or even months. It's not about the quantity of information held in a temporary window, but the quality and persistence of that knowledge over time.
Similarly, Retrieval-Augmented Generation (RAG) is often confused with memory. RAG is a powerful technique for fetching external, static information to ground an agent's responses in facts—like pulling from a knowledge base or documentation. However, RAG is stateless; it doesn't remember past interactions or learn about the user. Memory, on the other hand, is about continuity and adaptation. It captures the evolving context of a relationship, such as user preferences, past decisions and important points of a conversation.
We can think of memory in two forms, much like human memory: short-term memory for immediate context and long-term memory for enduring knowledge. Long-term memory can be further broken down into factual memory (like user preferences), episodic memory (recalling specific past events) and semantic memory (generalized knowledge learned over time). By equipping agents with these different types of memory, we can evolve agents into genuine collaborators.
Source: Memory in Agents: What, Why and How
In this article, we will explore two simple examples of how one would integrate Mem0 into an agent or network of agents using both a deterministic and non-deterministic approach.
Introduction to Mem0
Before we dive into building agents, let's look at the basic building blocks that Mem0's Node.js SDK provides. The SDK is intuitive and gives you a handful of simple functions to manage memories.
First, you'll want to initialize the Mem0 client:
import { Mem0 } from "mem0ai";
const mem0 = new Mem0({
apiKey: process.env.MEM0_API_KEY,
});
With the client ready, you can perform the core memory operations:
-
Adding Memories: You can add one or more memories. Each memory is a simple string. You can also provide a
userId
to associate memories with a specific user.javascriptconst result = await mem0.add( ["User's name is John Doe.", "User likes rock music."], { userId: "user-123", } );
-
Searching Memories: You can search for memories using a natural language query. Mem0 will return the most relevant memories.
javascriptconst results = await mem0.search("what is the user's name?", { userId: "user-123", }); // results: [{ id: "...", text: "User's name is John Doe." }]
-
Listing Memories: You can retrieve all memories associated with a user.
javascriptconst memories = await mem0.getAll({ userId: "user-123" });
-
Updating Memories: If information changes, you can update an existing memory using its ID.
javascriptawait mem0.update("memory-id-abc", "User's name is Jane Doe.");
-
Deleting Memories: You can delete a specific memory by its ID or clear all memories for a user.
javascriptawait mem0.delete("memory-id-abc"); await mem0.deleteAll({ userId: "user-123" });
These simple functions are the foundation we'll build upon to create intelligent agents that remember. You can always add additional config to your Mem0 client for more sophisticated solutions. You can read more about all the ways you can customize mem0 in their docs!
Asynchronous Memory
A key challenge with memory is latency. You don't want your user waiting around while your agent meticulously records notes in its database. This is where the combination of AgentKit and Inngest shines.
When an agent needs to create, update or delete a memory - it simply sends an event to Inngest for durable background processing. This has two huge advantages:
- Faster Responses: Your agent can respond to the user immediately, without waiting for slow database writes to complete.
- Durable Background Processing: The memory operation runs reliably as a separate, durable Inngest function. If it fails for any reason, Inngest automatically retries it - ensuring no memory is ever lost.
The Building Blocks: Memory as Tools
To empower an agent with memory, we provide it with tools. The core idea is to abstract memory operations (create, read, update, delete) into functions that an agent can call, just like any other tool.
Source: Memory in Agents: What, Why and How
These tools can then use Inngest to perform the actual database writes asynchronously.
// A tool to create new memories
const createMemoriesTool = createTool({
name: "create_memories",
description: "Save one or more new pieces of information to memory.",
parameters: z.object({
statements: z
.array(z.string())
.describe("The pieces of information to memorize."),
}),
handler: async ({ statements }, { step }) => {
// 1. Send an event for background processing
await step?.sendEvent("send-create-memories-event", {
name: "app/memories.create",
data: { statements },
});
// 2. Return immediately to the user
return `I have scheduled the creation of ${statements.length} new memories.`;
},
});
With these building blocks, we can explore two powerful patterns for designing agents that remember.
Pattern 1: The Autonomous Agent (Non-Deterministic)
In this pattern, we create a single, powerful agent and give it a set of granular memory tools: create_memories
, recall_memories
, update_memories
, and delete_memories
.
We then instruct the agent, via its system prompt, to follow a recall-reflect-respond process. The agent uses its own reasoning to decide which memory tool to use at each step. This approach offers maximum flexibility and autonomy.
// An agent designed to manage its own memory
const mem0Agent = createAgent({
name: "reflective-mem0-agent",
system: `
You are an assistant with a dynamic, reflective memory. You must actively manage your memories to keep them accurate.
On every user interaction, you MUST follow this process:
1. **RECALL**: Use the 'recall_memories' tool to get context.
2. **ANALYZE & REFLECT**: Compare the user's statement with recalled memories. If there are contradictions, use the 'update_memories' or 'delete_memories' tool. If it's new information, use 'create_memories'.
3. **RESPOND**: Never make mention of any memory operations you have executed.
`,
tools: [
createMemoriesTool,
recallMemoriesTool,
updateMemoriesTool,
deleteMemoriesTool,
],
model: openai({ model: "gpt-4o" }),
});
How It Works
The agent's internal monologue drives the process, deciding which tools to call in sequence.
- Pros: Highly flexible and autonomous. The agent has control over when, how and if it should recall or update memories.
- Cons: Can be unpredictable. The agent might get stuck in loops or fail to use memory tools correctly. The system prompt must be carefully engineered, which can be brittle.
Pattern 2: The Multi-Agent Network (Deterministic)
To address the unpredictability of a single agent, we can build a more deterministic, multi-agent network with a structured sequence of specialized agents, orchestrated by a code-based router.
With this multi-agent system, each agent has a clearly defined purpose and single job to do. Our “router” is basically a function used to orchestrate/route between these different agents. Many multi-agent networks may have complex routing, but here, we use our router to force a linear path between the following agents:
- Memory Retrieval Agent: Its sole job is to use the
recall_memories
tool. - Personal Assistant Agent: Has no memory tools. Its only job is to synthesize the final answer based on the retrieved memories.
- Memory Updater Agent: Reviews the conversation and uses a consolidated
manage_memories
tool to create/update/delete one or memories after generating a response.
The router guarantees that these agents run in a predictable, step-by-step sequence.
// The Router enforces the sequence
const multiAgentMemoryNetwork = createNetwork({
name: "multi-agent-memory-network",
agents: [memoryRetrievalAgent, personalAssistantAgent, memoryUpdaterAgent],
router: async ({ callCount }) => {
if (callCount === 0) return memoryRetrievalAgent;
if (callCount === 1) return personalAssistantAgent;
if (callCount === 2) return memoryUpdaterAgent;
return undefined; // Stop the network
},
});
How It Works
The router guarantees a predictable, step-by-step execution path, where each agent performs its specialized task in order.
Starting with a memory recall agent to retrieve memories, then invoking an assistant agent (which could have many tools assigned to it) and then once a final answer tool is invoked by the assistant to produce a final, summarized response - a memory updater agent would be invoked and trigger a background job to create/update/delete any memories.
- Pros: Deterministic, reliable and easier to maintain. Each agent has a single, well-defined responsibility.
- Cons: Requires more boilerplate code and may unneccesarily create, read, update or delete memories.
The Future is Memorable
These patterns are just the beginning. With AgentKit, you have fine-grained control over how and when you want to interact with memory. You can create deterministic memory solutions using state-based routing or by integrating memory directly into an agent's lifecycle hooks. Alternatively, you can take a more non-deterministic path and empower agents to interact with memory as a tool. Taking this a step further, you can even create multiple memory stores dedicated to specific kinds of memory (episodic, user preferences, etc).
Giving your agents memory is a fundamental step toward creating more intelligent, personal and useful AI. By combining the power of AgentKit, Inngest durable execution and tools like Mem0 - you have everything you need to build agents that can learn over time. There’s a lot more we have not yet covered including graph-based memory and the notion of having multiple memory stores for different purposes.
We’ll be posting more advanced guides and tutorials for implementing more in-depth agents with memory very soon! There's a lot we have not covered like graph-based memory and other ways to configure Mem0 which you can learn all about in their docs.
Ready to get started? Check out our docs for a full deep-dive into everything memory and take a look at a complete Mem0 Memory Example to see these patterns in action.