AI Agents with Memory
⏱️ Time to Complete: 15 minutes 🎯 Level: Intermediate 💻 Language: TypeScript
What You'll Build
An AI agent that:
✅ Maintains conversation history across sessions
✅ Searches your knowledge base semantically
✅ Uses tools to accomplish tasks
✅ Reasons across multiple data sources
✅ Streams responses in real-time
📁 Tool calling examples | 🚀 Production example: Zine
Why Agents Need Memory
Traditional chatbots forget everything between sessions. AI agents with semantic memory:
✅ Remember past conversations and decisions
✅ Search across all your company's knowledge
✅ Use tools to take actions (not just answer questions)
✅ Reason across multiple data sources
✅ Share context with other agents
Real-world example: Zine uses this pattern to let agents search Slack threads, meeting transcripts, and GitHub discussions simultaneously.
Prerequisites
Complete the Quickstart tutorial
Graphlit account with content ingested
Pattern 1: Multi-Turn Conversations with Memory
First, create an agent that keeps track of everything you've asked it:
import { Graphlit } from 'graphlit-client';
import {
SpecificationTypes,
ModelServiceTypes,
OpenAiModels,
} from 'graphlit-client/dist/generated/graphql-types';
const graphlit = new Graphlit();
async function main() {
console.log('⚙️ Creating agent specification...');
const spec = await graphlit.createSpecification({
name: 'Sales Agent Spec',
type: SpecificationTypes.Completion,
serviceType: ModelServiceTypes.OpenAi,
openAI: {
model: OpenAiModels.Gpt5_400K,
temperature: 0.7,
completionTokenLimit: 2000,
},
systemPrompt: `You are a helpful sales agent. You have access to all customer conversations, product docs, and meeting notes. When asked about customers, search your knowledge base and provide specific details with context.`,
});
console.log('🧠 Creating agent memory...');
const conversation = await graphlit.createConversation({
name: 'Sales Agent - Acme Corp',
specification: { id: spec.createSpecification.id },
});
const conversationId = conversation.createConversation.id;
console.log(`✅ Agent created: ${conversationId}`);
console.log('\n💬 Turn 1: Asking about customer...');
const turn1 = await graphlit.promptConversation(
"What do we know about Acme Corp's requirements?",
conversationId,
);
console.log(`Agent: ${turn1.promptConversation.message?.message}`);
console.log('\n💬 Turn 2: Follow-up question...');
const turn2 = await graphlit.promptConversation(
'What pricing concerns did they raise?',
conversationId,
);
console.log(`Agent: ${turn2.promptConversation.message?.message}`);
console.log('\n💬 Turn 3: Synthesis...');
const turn3 = await graphlit.promptConversation(
"Based on everything we discussed, what's the best next step?",
conversationId,
);
console.log(`Agent: ${turn3.promptConversation.message?.message}`);
console.log('\n📜 Conversation history:');
const history = await graphlit.getConversation(conversationId);
history.conversation?.messages?.forEach((msg, index) => {
if (!msg?.message) {
return;
}
const role = msg.role === 'USER' ? 'User' : 'Agent';
console.log(` ${index + 1}. ${role}: ${msg.message}`);
});
}
main().catch((error) => {
console.error(error);
process.exit(1);
});What's happening:
Agent automatically retrieves relevant context from its memory
Conversation state persists across turns (follow-up questions work)
You can inspect the full conversation history after each exchange
Pattern 2: Agentic Tool Calling
Let your agent call custom tools instead of just responding with text:
import { Graphlit } from 'graphlit-client';
import {
SpecificationTypes,
ModelServiceTypes,
OpenAiModels,
ToolDefinitionInput,
} from 'graphlit-client/dist/generated/graphql-types';
const graphlit = new Graphlit();
async function agentWithTools() {
const tools: ToolDefinitionInput[] = [
{
name: 'search_slack',
description: 'Search Slack messages for information',
schema: JSON.stringify({
type: 'object',
properties: {
query: { type: 'string', description: 'Search query' },
channel: { type: 'string', description: 'Slack channel' },
},
required: ['query'],
}),
},
{
name: 'get_github_pr',
description: 'Get details about a GitHub pull request',
schema: JSON.stringify({
type: 'object',
properties: {
pr_number: { type: 'integer', description: 'PR number' },
},
required: ['pr_number'],
}),
},
];
const spec = await graphlit.createSpecification({
name: 'Developer Assistant Spec',
type: SpecificationTypes.Completion,
serviceType: ModelServiceTypes.OpenAi,
openAI: {
model: OpenAiModels.Gpt5_400K,
temperature: 0.1,
},
systemPrompt: `You are a developer assistant with access to your team's knowledge base. Use the available tools to search for relevant information, then synthesize findings to provide specific, actionable answers.`,
});
const conversation = await graphlit.createConversation({
name: 'Multi-Tool Agent',
specification: { id: spec.createSpecification.id },
tools,
});
const response = await graphlit.promptConversation(
'Why did PR #247 fail CI? Check Slack discussions about it.',
conversation.createConversation.id,
);
const message = response.promptConversation.message;
if (message?.toolCalls?.length) {
console.log('🛠️ Agent suggested tool usage:');
message.toolCalls.forEach((toolCall) => {
if (!toolCall) {
return;
}
console.log(` ${toolCall.name}(${toolCall.arguments})`);
});
}
console.log(`\n💬 Agent: ${message?.message}`);
}
agentWithTools().catch((error) => {
console.error(error);
process.exit(1);
});What's happening:
Agent sees the available tool definitions and selects the right one
Tool calls are recorded on the conversation so you can execute them
Responses combine tool outputs with natural language answers
Real-world example: Zine uses this pattern for questions like “Why are checkout timeouts spiking?” — the agent queries Sentry, Slack, GitHub, and meeting notes via tools.
Pattern 3: Multi-Agent Systems with Shared Knowledge
Point multiple agents at the same semantic memory but give each its own persona:
import { Graphlit } from 'graphlit-client';
import {
CollectionTypes,
SpecificationTypes,
ModelServiceTypes,
OpenAiModels,
RetrievalStrategyTypes,
} from 'graphlit-client/dist/generated/graphql-types';
const graphlit = new Graphlit();
async function multiAgentSystem() {
const knowledgeBase = await graphlit.createCollection({
name: 'Company Knowledge Base',
type: CollectionTypes.Collection,
});
const collectionId = knowledgeBase.createCollection.id;
const salesSpec = await graphlit.createSpecification({
name: 'Sales Agent Spec',
type: SpecificationTypes.Completion,
serviceType: ModelServiceTypes.OpenAi,
openAI: {
model: OpenAiModels.Gpt5_400K,
temperature: 0.7,
},
systemPrompt:
'You are a sales agent. Focus on customer needs, pricing, and deal status.',
retrievalStrategy: {
type: RetrievalStrategyTypes.Content,
contentLimit: 10,
},
});
const engSpec = await graphlit.createSpecification({
name: 'Engineering Agent Spec',
type: SpecificationTypes.Completion,
serviceType: ModelServiceTypes.OpenAi,
openAI: {
model: OpenAiModels.Gpt5_400K,
temperature: 0.3,
},
systemPrompt:
'You are an engineering agent. Focus on technical requirements, integrations, and implementation details.',
retrievalStrategy: {
type: RetrievalStrategyTypes.Content,
contentLimit: 10,
},
});
const salesAgent = await graphlit.createConversation({
name: 'Sales Agent',
specification: { id: salesSpec.createSpecification.id },
filter: { collections: [{ id: collectionId }] },
});
const engAgent = await graphlit.createConversation({
name: 'Engineering Agent',
specification: { id: engSpec.createSpecification.id },
filter: { collections: [{ id: collectionId }] },
});
const salesAnswer = await graphlit.promptConversation(
"What are Acme Corp's budget constraints?",
salesAgent.createConversation.id,
);
const engineeringAnswer = await graphlit.promptConversation(
'What technical integrations does Acme Corp need?',
engAgent.createConversation.id,
);
console.log('💼 Sales Agent:', salesAnswer.promptConversation.message?.message);
console.log('⚙️ Engineering Agent:', engineeringAnswer.promptConversation.message?.message);
}
multiAgentSystem().catch((error) => {
console.error(error);
process.exit(1);
});What's happening:
Two agents share the same knowledge base but have different prompts and temperatures
Retrieval strategy keeps both agents grounded in the same set of documents
You can orchestrate the agents together (sales → engineering handoff) without duplicating memory
Pattern 4: Streaming Responses
For real-time user experiences, stream agent output as it happens:
import { Graphlit } from 'graphlit-client';
import {
SpecificationTypes,
ModelServiceTypes,
OpenAiModels,
} from 'graphlit-client/dist/generated/graphql-types';
import { OpenAI } from 'openai';
const graphlit = new Graphlit();
graphlit.setOpenAIClient(new OpenAI());
async function streamingAgent() {
const spec = await graphlit.createSpecification({
name: 'Streaming Assistant',
type: SpecificationTypes.Completion,
serviceType: ModelServiceTypes.OpenAi,
openAI: {
model: OpenAiModels.Gpt5_400K,
temperature: 0.5,
},
});
await graphlit.streamAgent(
'Explain our pricing model to enterprise customers.',
(event) => {
switch (event.type) {
case 'conversation_started':
console.log(`🌊 Streaming conversation ${event.conversationId}`);
break;
case 'message_update':
process.stdout.write(event.message.message);
break;
case 'conversation_completed':
console.log(`\n✅ Complete! Tokens: ${event.usage?.tokens ?? 0}`);
break;
}
},
undefined,
{ id: spec.createSpecification.id },
);
}
streamingAgent().catch((error) => {
console.error(error);
process.exit(1);
});Use cases:
Real-time chat experiences (Graphlit + Next.js chat app)
Live meeting transcription with incremental summaries
Progressive document generation or approvals
Production Patterns
Error Handling
try {
const reply = await graphlit.promptConversation(userInput, conversationId);
console.log(reply.promptConversation.message?.message);
} catch (error) {
console.error('❌ Agent error:', error);
console.log("I'm having trouble right now. Please try again.");
}Rate Limiting
// Simple delay between prompts to respect rate limits
await new Promise((resolve) => setTimeout(resolve, 500));Context Management
// Update retrieval scope as the conversation evolves
const followUpCollectionId = 'collection-id-for-the-follow-up';
await graphlit.updateConversation({
id: conversationId,
filter: { collections: [{ id: followUpCollectionId }] },
});
// Or branch the thread to explore a new idea without losing history
const branch = await graphlit.branchConversation(conversationId);
console.log('✨ Branched conversation:', branch.branchConversation?.id);Real-World Examples
1. Customer Support Agent
Searches past tickets, product docs, and conversations
Maintains conversation history per customer
Uses tools to create tickets, update CRM
2. Engineering Agent (like Zine)
Searches Slack, GitHub, Jira simultaneously
Reasons across code, discussions, and meeting notes
Uses tools to fetch error logs, run queries
3. Sales Agent
Searches customer conversations, contracts, meeting notes
Tracks deal status and objections
Uses tools to update CRM, send follow-ups
Next Steps
MCP Integration - Connect your agents to your IDE
Knowledge Graph - Extract entities for richer context
Context Engineering - Optimize memory formation
Full Example: Production Agent
See the complete Next.js agent in graphlit-samples:
Tool calling with streaming UI
Shared collections and semantic memory queries
Production-ready configuration (environment variables, error handling)
Build agents that actually remember. Build with Graphlit.
Last updated
Was this helpful?