Skip to main content

AI Agent with Persistent Memory

The Problem

AI agents lose all context when a conversation ends. Every new session starts from scratch — the user has to re-explain their preferences, past decisions, and ongoing projects. For agents that users interact with daily, this is a major friction point.

The Solution

Store important context in Memsolus at the end of every conversation. At the start of the next one, retrieve the compiled knowledge profile for that user. Your agent gets full context without relying on a growing context window.

How It Works

User message

Search memories for relevant context

Build system prompt with retrieved context

LLM generates response

Extract and store new facts from the conversation

Implementation

Install the SDK

npm install @memsolus/sdk

Initialize the client

import { Memsolus } from '@memsolus/sdk';

const memsolus = new Memsolus({
apiKey: process.env.MEMSOLUS_API_KEY,
});

Load context at conversation start

Before sending the first message to the LLM, retrieve the user's knowledge profile:

async function loadUserContext(userId: string): Promise<string> {
const knowledge = await memsolus.knowledge.get({
userId,
merged: true,
});

if (!knowledge || !knowledge.content) {
return '';
}

return knowledge.content;
}

The merged: true flag returns a single compiled document that combines all knowledge entries for that user — no need to stitch multiple results together.

Search for relevant memories

For every user message, search for memories specifically relevant to it:

async function searchRelevantMemories(
userId: string,
userMessage: string,
): Promise<string[]> {
const results = await memsolus.memories.search({
query: userMessage,
userId,
mode: 'hybrid',
limit: 5,
});

return results.data.map((m) => m.content);
}

Build the system prompt

Combine the knowledge profile and relevant memories into a system prompt:

async function buildSystemPrompt(userId: string, userMessage: string): Promise<string> {
const [knowledgeProfile, relevantMemories] = await Promise.all([
loadUserContext(userId),
searchRelevantMemories(userId, userMessage),
]);

const memorySection =
relevantMemories.length > 0
? `\n\nRelevant context:\n${relevantMemories.map((m) => `- ${m}`).join('\n')}`
: '';

const profileSection = knowledgeProfile
? `\n\nUser profile:\n${knowledgeProfile}`
: '';

return `You are a helpful AI assistant.${profileSection}${memorySection}`;
}

Store new facts after the conversation

At the end of each turn, store facts that should be remembered:

async function storeConversationFacts(
userId: string,
facts: string[],
): Promise<void> {
await Promise.all(
facts.map((fact) =>
memsolus.memories.add({
content: fact,
userId,
}),
),
);
}

Put it all together

Here is a complete agent turn:

import OpenAI from 'openai';
import { Memsolus } from '@memsolus/sdk';

const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
const memsolus = new Memsolus({ apiKey: process.env.MEMSOLUS_API_KEY });

async function agentTurn(userId: string, userMessage: string): Promise<string> {
const systemPrompt = await buildSystemPrompt(userId, userMessage);

const completion = await openai.chat.completions.create({
model: 'gpt-4o',
messages: [
{ role: 'system', content: systemPrompt },
{ role: 'user', content: userMessage },
],
});

const assistantResponse = completion.choices[0].message.content ?? '';

// Store facts mentioned by the user in this turn
// In production, you would use an LLM to extract these automatically
const factsToStore = extractFacts(userMessage);
if (factsToStore.length > 0) {
await storeConversationFacts(userId, factsToStore);
}

return assistantResponse;
}

function extractFacts(message: string): string[] {
// Placeholder — replace with an LLM-based extraction step
// e.g., call GPT-4o to extract declarative facts from the message
return [];
}

Full Example

import OpenAI from 'openai';
import { Memsolus } from '@memsolus/sdk';

const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
const memsolus = new Memsolus({ apiKey: process.env.MEMSOLUS_API_KEY });

const USER_ID = 'user_alice';

// First session
await memsolus.memories.add({
content: 'Alice is a senior backend engineer who prefers Go for new services.',
userId: USER_ID,
priority: 'HIGH',
});

await memsolus.memories.add({
content: 'Alice is migrating a legacy PHP monolith to a Go microservices architecture.',
userId: USER_ID,
});

// Second session — agent has full context
const knowledge = await memsolus.knowledge.get({
userId: USER_ID,
merged: true,
});

console.log(knowledge.content);
// => "Alice is a senior backend engineer who prefers Go...
// She is currently migrating a legacy PHP monolith..."

const response = await agentTurn(
USER_ID,
'What stack should I use for the new payment service?',
);

// Agent recommends Go — because it remembers Alice's preference
console.log(response);

What's Next