Memory
The memory system enables agents to maintain context across multiple interactions by storing and retrieving conversation history using semantic vector search.
Overview
AgentMesh provides two memory implementations:
- VectorMemory - Semantic search using embeddings for intelligent recall
- SimpleMemory - Basic FIFO storage without semantic search
Key features:
- β Session-based storage - Organize conversations by session/user ID
- β Semantic search - Find relevant messages by meaning, not keywords
- β Relevance ranking - Sort results by similarity score
- β Flexible filtering - Time-based and metadata queries
Vector Memory
VectorMemory uses embeddings to enable semantic search over conversation history.
Setup
import (
"github.com/hupe1980/agentmesh/pkg/embedding/openai"
"github.com/hupe1980/agentmesh/pkg/memory"
)
// Create embedder
embedder := openai.NewEmbedder(client,
openai.WithModel("text-embedding-3-small"),
)
// Create vector memory
mem := memory.NewVectorMemory(embedder)
Custom VectorStore Backend
By default, VectorMemory uses an in-memory store. For persistence or scalability, provide a custom VectorStore:
import (
"github.com/hupe1980/agentmesh/pkg/memory"
"github.com/hupe1980/agentmesh/pkg/vectorstore"
memstore "github.com/hupe1980/agentmesh/pkg/vectorstore/memory"
)
// Use in-memory store (default behavior)
store := memstore.New()
mem := memory.NewVectorMemory(embedder, memory.WithStore(store))
// Or use Qdrant for production
// import "github.com/hupe1980/agentmesh/pkg/vectorstore/qdrant"
// store, _ := qdrant.New("localhost:6334",
// qdrant.WithCollectionName("conversations"),
// qdrant.WithDimensions(1536),
// )
// mem := memory.NewVectorMemory(embedder, memory.WithStore(store))
// Or use PostgreSQL with pgvector
// import "github.com/hupe1980/agentmesh/pkg/vectorstore/pgvector"
// store, _ := pgvector.New(ctx,
// pgvector.WithConnectionString("postgres://..."),
// pgvector.WithTableName("conversations"),
// pgvector.WithDimensions(1536),
// )
// mem := memory.NewVectorMemory(embedder, memory.WithStore(store))
// Don't forget to close when done
defer mem.Close()
The VectorStore backend handles:
- Persistence - Messages survive application restarts
- Scalability - Support millions of messages
- Namespace isolation - Session IDs map to namespaces
Storing Messages
import "github.com/hupe1980/agentmesh/pkg/message"
sessionID := "user-123-session-456"
messages := []message.Message{
message.NewHumanMessageFromText("What's the pricing for the enterprise plan?"),
message.NewAIMessageFromText("Our enterprise plan starts at $500/month with unlimited users."),
message.NewHumanMessageFromText("What features are included?"),
message.NewAIMessageFromText("Enterprise includes SSO, advanced analytics, priority support, and custom integrations."),
}
err := mem.Store(ctx, sessionID, messages)
Recalling Messages
Retrieve relevant messages using semantic search:
recalled, err := mem.Recall(ctx, sessionID, memory.RecallFilter{
Query: "Tell me about the enterprise pricing again",
K: 5, // Top 5 most relevant messages
})
for _, msg := range recalled {
fmt.Printf("%s\n", msg.String())
}
Output:
[0.94] Our enterprise plan starts at $500/month with unlimited users.
[0.87] What's the pricing for the enterprise plan?
[0.72] Enterprise includes SSO, advanced analytics, priority support...
Advanced Filtering
last24h := time.Now().Add(-24 * time.Hour)
recalled, err := mem.Recall(ctx, sessionID, memory.RecallFilter{
Query: "pricing information",
K: 10,
MinScore: 0.7, // Only messages with similarity > 0.7
After: &last24h, // Only messages after this time
Types: []message.Type{message.TypeAI}, // Only AI responses
})
Session Management
// List all sessions
sessions, err := mem.ListSessions(ctx)
// Get session metadata
metadata, err := mem.GetSessionMetadata(ctx, sessionID)
fmt.Printf("Messages: %d, Created: %v\n", metadata.MessageCount, metadata.CreatedAt)
// Delete session
err = mem.DeleteSession(ctx, sessionID)
Simple Memory
SimpleMemory provides basic FIFO storage without semantic search. Useful for development or when semantic search isnβt needed.
Setup
mem := memory.NewSimpleMemory(
memory.WithMaxMessages(100), // Keep last 100 messages
)
Usage
// Store messages
err := mem.Store(ctx, sessionID, messages)
// Recall all messages (no semantic search)
recalled, err := mem.Recall(ctx, sessionID, memory.RecallFilter{
K: 10, // Last 10 messages
})
// Recall with time filter
lastHour := time.Now().Add(-1 * time.Hour)
recalled, err := mem.Recall(ctx, sessionID, memory.RecallFilter{
After: &lastHour,
})
Use Cases
1. Conversational Agent Wrapper
The simplest way to add memory to an agent is using the Conversational wrapper:
import (
"github.com/hupe1980/agentmesh/pkg/agent"
"github.com/hupe1980/agentmesh/pkg/graph"
"github.com/hupe1980/agentmesh/pkg/memory"
)
// Create any agent (ReAct, RAG, Supervisor, etc.)
reactAgent, _ := agent.NewReAct(model, agent.WithTools(tools...))
// Create memory
mem := memory.NewVectorMemory(embedder)
// Wrap with conversational memory
chatAgent, _ := agent.NewConversational(
reactAgent,
mem,
agent.WithShortTermMessages(5), // Recent messages for context
agent.WithLongTermMessages(5), // Semantic search for relevance
agent.WithMinSimilarityScore(0.5),
)
// Use with a session ID
for msg, err := range chatAgent.Run(ctx, messages,
graph.WithInitialValue(agent.SessionIDKey, "user-123"),
) {
// Memory is handled automatically
}
See the Conversational Agent documentation for details.
2. Multi-Session Chatbot
Maintain separate conversation history per user:
type ChatBot struct {
memory memory.Memory
agent *graph.Compiled
}
func (c *ChatBot) Chat(ctx context.Context, userID, message string) (string, error) {
sessionID := fmt.Sprintf("user-%s", userID)
// Recall relevant context
history, err := c.memory.Recall(ctx, sessionID, memory.RecallFilter{
Query: message,
K: 5,
})
if err != nil {
return "", err
}
// Build messages with context
messages := append(history, message.NewHumanMessageFromText(message))
// Execute agent
var lastResponse string
for msg, err := range c.agent.Run(ctx, messages) {
if err != nil {
return "", err
}
lastResponse = msg.Content()
}
return lastResponse, nil
// Store conversation
err = c.memory.Store(ctx, sessionID, results)
return extractResponse(results), nil
}
3. RAG with Conversation History
Combine memory with retrieval for context-aware RAG:
func answerWithContext(ctx context.Context, query string, sessionID string) (string, error) {
// 1. Recall relevant conversation history
history, err := memory.Recall(ctx, sessionID, memory.RecallFilter{
Query: query,
K: 3,
})
// 2. Retrieve relevant documents
docs, err := retriever.Retrieve(ctx, query, 5)
// 3. Build context from both sources
context := buildContext(history, docs)
// 4. Generate answer
prompt := fmt.Sprintf(`Context from previous conversations:
%s
Relevant documents:
%s
Question: %s
Answer:`, formatHistory(history), formatDocs(docs), query)
return agent.Generate(ctx, prompt)
}
4. Personalized Recommendations
Use memory to track user preferences:
func getRecommendations(ctx context.Context, userID string) ([]string, error) {
sessionID := fmt.Sprintf("user-%s", userID)
// Recall mentions of preferences
last30Days := time.Now().Add(-30 * 24 * time.Hour)
prefs, err := memory.Recall(ctx, sessionID, memory.RecallFilter{
Query: "I like, I prefer, my favorite",
K: 10,
After: &last30Days,
})
// Extract preferences
preferences := extractPreferences(prefs)
// Generate recommendations
return generateRecommendations(preferences), nil
}
5. Context Window Management
Automatically select most relevant messages when context is limited:
func buildContextualPrompt(ctx context.Context, sessionID, query string, maxTokens int) ([]message.Message, error) {
// Recall semantically relevant messages
relevant, err := memory.Recall(ctx, sessionID, memory.RecallFilter{
Query: query,
K: 20, // Get more candidates
})
// Select messages that fit in context window
selected := []message.Message{}
tokens := 0
for _, msg := range relevant {
msgTokens := countTokens(msg)
if tokens+msgTokens > maxTokens {
break
}
selected = append(selected, msg)
tokens += msgTokens
}
return selected, nil
}
6. Topic-Based Retrieval
Organize memory by conversation topics:
func recallByTopic(ctx context.Context, sessionID, topic string) ([]message.Message, error) {
topicQueries := map[string]string{
"billing": "payment, invoice, pricing, subscription, billing",
"support": "help, issue, problem, error, bug",
"features": "feature, capability, functionality, how to",
}
query := topicQueries[topic]
return memory.Recall(ctx, sessionID, memory.RecallFilter{
Query: query,
K: 10,
MinScore: 0.6,
})
}
7. Conversation Summarization
Periodically summarize old messages to save storage:
func summarizeOldMessages(ctx context.Context, sessionID string) error {
// Get messages older than 7 days
sevenDaysAgo := time.Now().Add(-7 * 24 * time.Hour)
old, err := memory.Recall(ctx, sessionID, memory.RecallFilter{
Before: &sevenDaysAgo,
K: 1000,
})
if len(old) < 10 {
return nil // Not enough to summarize
}
// Generate summary
summary := generateSummary(old)
// Store summary as system message
summaryMsg := message.NewSystemMessageFromText(fmt.Sprintf("Previous conversation summary: %s", summary))
err = memory.Store(ctx, sessionID, []message.Message{summaryMsg})
// Delete old messages
for _, msg := range old {
err = memory.DeleteMessage(ctx, sessionID, msg.ID)
}
return nil
}
Best Practices
1. Choose the Right Memory Type
// Use VectorMemory when:
// - Conversations are long (>100 messages)
// - Need semantic search ("find when we discussed pricing")
// - Building RAG systems
// - Personalization is important
vectorMem := memory.NewVectorMemory(embedder)
// Use SimpleMemory when:
// - Short conversations
// - Sequential recall is sufficient
// - Development/testing
// - Embedding costs are a concern
simpleMem := memory.NewSimpleMemory()
2. Optimize Embedding Calls
Batch embed when storing multiple messages:
// β Bad - embeds each message individually
for _, msg := range messages {
mem.Store(ctx, sessionID, []message.Message{msg})
}
// β
Good - batch embeds all messages
mem.Store(ctx, sessionID, messages)
3. Set Appropriate K Values
// Short-term context (last few exchanges)
mem.Recall(ctx, sessionID, memory.RecallFilter{
K: 5,
})
// Comprehensive search (find all relevant)
mem.Recall(ctx, sessionID, memory.RecallFilter{
K: 20,
MinScore: 0.7, // But filter by relevance
})
4. Handle Privacy & Retention
// Automatic cleanup after 90 days
go func() {
ticker := time.NewTicker(24 * time.Hour)
for range ticker.C {
sessions, _ := mem.ListSessions(ctx)
for _, session := range sessions {
metadata, _ := mem.GetSessionMetadata(ctx, session)
if time.Since(metadata.CreatedAt) > 90*24*time.Hour {
mem.DeleteSession(ctx, session)
}
}
}
}()
5. Combine with Checkpointing
Persist memory alongside graph state:
// Store conversation in memory
mem.Store(ctx, sessionID, messages)
// Also checkpoint for resumability
compiled, _ := g.Build()
seq := compiled.Run(ctx, messages,
graph.WithRunID(sessionID),
graph.WithCheckpointer(checkpointer),
)
6. Monitor Performance
func (m *InstrumentedMemory) Recall(ctx context.Context, sessionID string, filter memory.RecallFilter) ([]message.Message, error) {
start := time.Now()
results, err := m.memory.Recall(ctx, sessionID, filter)
metrics.RecordMemoryRecall(time.Since(start), len(results))
return results, err
}
Implementation Details
Vector Memory Architecture
1. Store() called with messages
β
2. Batch embed all message texts
β
3. Store embeddings + metadata in vector store
β
4. Index by sessionID
Recall() with query:
β
1. Embed query
β
2. Similarity search in vector store
β
3. Filter by sessionID, time, type
β
4. Rank by cosine similarity
β
5. Return top K results
Performance Characteristics
| Operation | VectorMemory | SimpleMemory |
|---|---|---|
| Store (100 msgs) | ~500ms | ~5ms |
| Recall (semantic) | ~100ms | N/A |
| Recall (recent) | ~100ms | ~1ms |
| Storage/msg | ~800 bytes | ~200 bytes |
Examples
See the conversational_agent example for memory integration.
See Also
- Embeddings Guide - Understanding vector embeddings
- Checkpointing - Persisting graph state
- API Reference