Building a Memory-Powered AI Agent: A Journey with Amazon Bedrock, Strands, and Mem0
Using Amazon Bedrock's Nova models, the Strands agent framework, and Mem0's memory layer, I created an AI assistant that maintains context across conversations, learns user preferences, and provides genuinely personalized interactions. Think of it as upgrading from a goldfish's 3-second memory to an elephant's legendary recall.
Table of Contents
- Introduction: The Memory Problem in AI Conversations
- Why Strands? The Framework That Just Makes Sense
- Why Mem0? Giving AI a Real Memory
- The Dual Memory Architecture: Session vs. Long-Term Memory
- AWS Diagram MCP Server: Architecture Diagrams from Natural Language
- Architecture: How It All Comes Together
- Building the Application: Key Components
- The "Aha!" Moments: Lessons from Development
- Real-World Use Cases and Benefits
- Conclusion: The Future of Memory-Enabled AI
Introduction: The Memory Problem in AI Conversations
Imagine walking into your favorite coffee shop. The barista greets you by name, remembers you prefer oat milk, and asks if you want "the usual." Now imagine the opposite: every single visit, you have to reintroduce yourself and explain your preferences from scratch. Frustrating, right?
This is exactly the problem most AI chatbots face today. Each conversation starts with a blank slate. You could tell an AI assistant your name is Sarah on Monday, discuss your love for Python programming on Tuesday, and by Wednesday, it has completely forgotten both facts. The AI is like a barista with amnesia—helpful in the moment, but unable to build a relationship.
This blog chronicles my journey building a memory-powered AI agent that actually remembers.
Why Strands? The Framework That Just Makes Sense
The Agent Framework Landscape
When I started this project, I needed an agent framework that was production-ready, AWS-native, and didn't require weeks to master. After evaluating several options, Strands emerged as the clear winner. Here's why it's exceptional for building real-world AI agents.

The Philosophy: Model-Driven, Not Workflow-Driven
Strands takes a fundamentally different approach than traditional frameworks. Instead of forcing you to hardcode complex workflows and orchestration logic, Strands leverages the model's own reasoning capabilities to plan, orchestrate tasks, and reflect on goals.
What this means in practice: You define what tools are available and what the agent should do (via system prompt), and the model figures out how to accomplish the task. No chains to build, no state machines to design, no complex orchestration code.
1
2
3
4
5
6
7
8
9
10
from strands import Agent
# That's it - the model handles the orchestration
agent = Agent(
system_prompt="You are a helpful assistant",
tools=[calculator, search_memory, aws_account_info]
)
agent("Calculate 25 * 48 and save the result to memory")
# Model decides: 1) Use calculator, 2) Use save_memory
Key Advantages of Strands
1. Simplicity Without Sacrificing Power
Creating tools is refreshingly straightforward:
1
2
3
4
5
6
7
from strands import tool
def search_memory(query: str, user_id: str = "default") -> str:
"""Search through user's stored memories."""
result = memory.search(query, user_id=user_id, limit=5)
return format_memories(result)
The
1
@tool decorator handles everything: serialization, error handling, schema generation, and integration with the agent. No boilerplate, no complex interfaces.
2. True Model & Provider Agnostic
Strands supports 11+ model providers out of the box:
- Amazon Bedrock (Nova, Claude, Llama, Mistral, etc.)
- OpenAI
- Anthropic
- Ollama (local models)
- llama.cpp
- LiteLLM
- MistralAI
- SageMaker
- Writer
- Cohere
- Custom providers
Switch providers without changing code:
1
2
3
4
5
6
7
8
9
# Using Amazon Bedrock
agent = Agent(model="us.amazon.nova-premier-v1:0")
# Switch to OpenAI - same code
agent = Agent(model="gpt-4")
# Or use local Ollama
from strands.models.ollama import OllamaModel
agent = Agent(model=OllamaModel(model_id="llama3.1"))
3. Production-Ready from Day One
Strands was built for production by teams at AWS who run agents at scale (Amazon Q Developer, AWS Glue, VPC Reachability Analyzer). It includes:
Built-in Observability:
- Automatic metrics collection (tokens, latency, tool usage)
- OpenTelemetry integration for traces
- Detailed execution logs
- Performance analytics
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
result = agent("What is the square root of 144?")
# Metrics are automatically collected
print(result.metrics.get_summary())
# {
# "total_cycles": 2,
# "total_duration": 1.88,
# "accumulated_usage": {"inputTokens": 3921, "outputTokens": 83},
# "tool_usage": {
# "calculator": {
# "call_count": 1,
# "success_rate": 1.0,
# "average_time": 0.008
# }
# }
# }
Enterprise Security:
- Bedrock Guardrails integration
- PII redaction
- Content filtering
- Responsible AI features
Deployment Flexibility:
- AWS Lambda (serverless)
- Amazon ECS/Fargate (containers)
- Amazon EKS (Kubernetes)
- Amazon EC2 (VMs)
- Bedrock AgentCore Runtime (managed)
- Local development
4. Native MCP (Model Context Protocol) Support
Strands has first-class support for MCP, the emerging standard for AI tool integration:
1
2
3
4
5
6
from strands import Agent
from strands.tools.mcp import MCPClient
# MCP tools work alongside native tools
mcp_client = get_diagram_mcp_client()
agent = Agent(tools=[calculator, current_time, mcp_client])
This means you can leverage the growing ecosystem of MCP servers without custom integration code.
5. Advanced Multi-Agent Capabilities
Strands provides simple primitives for complex multi-agent systems:
Agent-to-Agent (A2A) Protocol:
1
2
3
4
5
6
7
from strands.multiagent.a2a import A2AServer
# Expose agent as network service
server = A2AServer(
agent=my_agent,
http_url="http://my-service.com/agent"
)
Agents as Tools:
1
2
3
4
5
6
7
8
9
10
11
def research_assistant(query: str) -> str:
"""Specialized research agent"""
research_agent = Agent(
system_prompt="You are a research specialist",
tools=[web_search, retrieve]
)
return research_agent(query)
# Use research agent as a tool
main_agent = Agent(tools=[research_assistant, calculator])
Swarms (parallel execution):
1
2
3
4
from strands.multiagent import Swarm
swarm = Swarm(agents=[research_agent, writer_agent, editor_agent])
result = swarm("Research AI trends and write a summary")
Graphs (DAG workflows):
1
2
3
4
5
6
from strands.multiagent import Graph
workflow = Graph()
workflow.add_node("research", research_agent)
workflow.add_node("write", writer_agent)
workflow.add_edge("research", "write")
6. Intelligent Conversation Management
Strands provides sophisticated conversation management out of the box:
SlidingWindowConversationManager: Keep last N messages
1
2
3
4
5
from strands.agent.conversation_manager import SlidingWindowConversationManager
agent = Agent(
conversation_manager=SlidingWindowConversationManager(window_size=10)
)
SummarizingConversationManager: Automatically summarize old messages
1
2
3
4
5
6
7
8
from strands.agent.conversation_manager import SummarizingConversationManager
agent = Agent(
conversation_manager=SummarizingConversationManager(
summary_ratio=0.3, # Summarize 30% of messages
preserve_recent_messages=10
)
)
This prevents context window overflow while maintaining conversation coherence.
7. Streaming Support for Real-Time Responses
Built-in streaming for better user experience:
1
2
3
4
5
6
7
8
9
# Synchronous streaming
for event in agent.stream("Tell me about AWS"):
if "data" in event:
print(event["data"], end="", flush=True)
# Asynchronous streaming
async for event in agent.stream_async("Tell me about AWS"):
if "data" in event:
yield event["data"]
8. Session Persistence
Multiple session managers for different deployment scenarios:
FileSessionManager (local development):
1
2
3
4
5
6
7
from strands.session.file_session_manager import FileSessionManager
session_manager = FileSessionManager(
session_id="user-123",
storage_dir="./sessions"
)
agent = Agent(session_manager=session_manager)
S3SessionManager (cloud deployment):
1
2
3
4
5
6
7
8
from strands.session.s3_session_manager import S3SessionManager
session_manager = S3SessionManager(
session_id="user-456",
bucket="my-agent-sessions",
prefix="production/"
)
agent = Agent(session_manager=session_manager)
9. Comprehensive Observability Integration
Strands integrates with leading observability platforms:
- Langfuse: OpenTelemetry-based tracing and debugging
- Arize AI: Comprehensive evaluation capabilities
- Datadog: Real-time monitoring
- AWS X-Ray: Distributed tracing
- CloudWatch: Logs and metrics
Custom trace attributes:
1
2
3
4
5
6
7
agent = Agent(
trace_attributes={
"session.id": "abc-1234",
"user.id": "user@example.com",
"tags": ["production", "high-priority"]
}
)
10. Battle-Tested at Scale
Strands is used in production by multiple AWS teams:
- Amazon Q Developer: AI coding assistant
- AWS Glue: Data integration service
- VPC Reachability Analyzer: Network diagnostics
Real testimonials:
"At Smartsheet, we chose Strands for our next generation of AI capabilities because it provided the perfect balance of enterprise-ready features and development efficiency." — JB Brown, VP Engineering, Smartsheet
"Strands Agents on Bedrock turns autonomous agents into an enterprise product: governed, observable, and safe by design." — Gal Malachi, Co-founder & CTO, Terra Security
11. Excellent Developer Experience
- Clear documentation: Comprehensive guides and examples
- Intuitive API: Minimal learning curve
- Fast iteration: Add tools in minutes, not hours
- Active community: Growing ecosystem and support
- Open source: Full transparency, community contributions
12. Cost Optimization Features
- Efficient token usage: Smart conversation management
- Model flexibility: Use cheaper models for simple tasks
- Streaming: Reduce perceived latency
- Caching: Reuse agent instances
When Strands Shines
Strands is exceptional for:
- ✅ Production-ready AI agents
- ✅ AWS-native deployments
- ✅ Multi-agent systems
- ✅ Enterprise applications requiring observability
- ✅ Teams that value simplicity and maintainability
- ✅ Projects needing model flexibility
- ✅ Applications requiring MCP integration
The Bottom Line
Strands doesn't try to be everything to everyone. It focuses on doing one thing exceptionally well: enabling developers to build production-ready AI agents quickly and reliably. The model-driven approach, combined with production-grade features and AWS-native integration, makes it the ideal choice for serious AI agent development.
Why Mem0? Giving AI a Real Memory
The Memory Challenge
Adding memory to an AI agent sounds simple in theory: just store conversation history in a database, right? Wrong. That's like saying human memory is just writing everything in a notebook.
Real memory is:-
Selective: We remember important things, not every mundane detail-
Associative: Memories connect to each other through meaning, not just timestamps-
Contextual: We recall information based on relevance to the current situation-
Evolving: Memories update and consolidate over time
Mem0 is a universal, self-improving memory layer for AI applications that makes agents truly remember—not just store text, but understand, connect, and recall information like humans do.
The Analogy: Filing Cabinet vs. Human Brain
**Traditional approach (**storing raw conversation logs): Like a filing cabinet where you stuff every document chronologically. Need to find something? Good luck searching through thousands of pages.
Vector databases alone: Like organizing by keywords. Better, but still mechanical—no understanding of context or importance.
Mem0: Like how your brain actually works—extracting meaningful facts, connecting related information, and retrieving relevant memories based on semantic similarity.
The Philosophy: Memory-Centric AI
Mem0 addresses a fundamental limitation of LLMs: fixed context windows. While models can generate contextually coherent responses, they struggle with consistency over prolonged multi-session dialogues. Mem0 solves this by dynamically extracting, consolidating, and retrieving salient information from ongoing conversations.


Key Advantages of Mem0
1. Intelligent Memory Extraction with LLM-Powered Fact Extraction
Mem0 doesn't just store what users say—it uses an LLM to extract meaningful facts. When a user says:
"Hi, I'm Alice, and I'm working on a machine learning project using Python and TensorFlow for image classification."
Mem0 intelligently extracts:
- User's name is Alice
- Working on machine learning project
- Using Python programming language
- Using TensorFlow framework
- Project focus: image classification
How it works:
1
2
3
4
5
6
7
8
9
10
11
12
from mem0 import Memory
memory = Memory()
# Add memory with automatic fact extraction
result = memory.add(
"I'm Alice and I love Python programming",
user_id="alice",
infer=True # LLM extracts facts (default)
)
# Mem0 extracts: "User's name is Alice. User loves Python programming."
1
infer=True parameter (default) enables LLM-based fact extraction. You can disable it for simple storage, but the magic is in the intelligent extraction.
2. Semantic Search, Not Keyword Matching
When you ask "What am I building?", Mem0 doesn't just look for the word "building"—it understands you're asking about projects, work, or goals, and retrieves semantically relevant memories.
1
2
3
4
# Search with semantic understanding
results = memory.search("food preferences", user_id="alice")
# Returns: "User loves pizza", "User hates broccoli"
# Even though the query didn't contain "pizza" or "broccoli"
This is powered by vector embeddings that convert memories into high-dimensional representations of their meaning.
3. Graph Memory: Understanding Relationships
Mem0's Graph Memory feature captures complex relational structures among conversational elements, going beyond simple fact storage to understand how entities relate to each other.
Enable graph memory:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from mem0 import Memory
config = {
"graph_store": {
"provider": "neo4j",
"config": {
"url": "bolt://localhost:7687",
"username": "neo4j",
"password": "password"
}
}
}
memory = Memory.from_config(config)
# Add memory with relationship extraction
result = memory.add(
"John works at OpenAI and is friends with Sarah",
user_id="user123"
)
# Result includes both memories and relationships
print(result["results"]) # Memory entries
print(result["relations"]) # Graph relationships
# Relations: John -> works_at -> OpenAI
# John -> friends_with -> Sarah
Search with graph context:
1
2
3
4
5
6
7
8
9
results = memory.search(
"Who is John?",
user_id="user123",
enable_graph=True
)
# Returns memories AND related entities
print(results["memories"]) # "John works at OpenAI"
print(results["relations"]) # Shows John's connections
Why graph memory matters: It enables the AI to understand not just isolated facts, but the web of relationships between entities—crucial for complex, multi-entity conversations.
4. Multimodal Memory: Remember Images and Documents
Mem0 supports multimodal content, allowing agents to remember visual information and documents:
Images:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Text + Image
messages = [
{"role": "user", "content": "This is my travel setup"},
{
"role": "user",
"content": {
"type": "image_url",
"image_url": {"url": f"data:image/jpeg;base64,{base64_image}"}
}
}
]
memory.add(messages, user_id="user123")
# Mem0 extracts: "User has travel setup with laptop, camera, and backpack"
PDFs and Documents:
1
2
3
4
5
6
7
8
9
# PDF processing
pdf_message = {
"role": "user",
"content": {
"type": "pdf_url",
"pdf_url": {"url": "https://example.com/document.pdf"}
}
}
memory.add([pdf_message], user_id="user123")
Real-world use case: A user shows the agent a photo of their desk setup, and weeks later asks "What monitor do I have?" The agent remembers from the image.
5. Research-Backed Performance
Mem0 published research demonstrating significant improvements over existing solutions:
Benchmarking Results (LOCOMO benchmark):
- 26% relative accuracy gains over OpenAI's memory implementation
- 91% lower p95 latency (faster response times)
- 90% fewer tokens (massive cost savings)

6. User Isolation and Privacy
Every user gets their own memory space. Alice's memories never leak into Bob's conversations.
1
2
3
4
5
6
7
# Memories are isolated by user_id
memory.add("I love pizza", user_id="alice")
memory.add("I love sushi", user_id="bob")
# Alice's search only returns her memories
alice_memories = memory.search("food", user_id="alice")
# Returns: "I love pizza" (not Bob's sushi preference)
Enterprise-ready: Mem0 is SOC 2 & HIPAA compliant with BYOK (Bring Your Own Key) support, making it suitable for healthcare, finance, and other regulated industries.
7. Multi-Agent Memory Management
Mem0 supports complex multi-agent systems with agent-specific memory:
1
2
3
4
5
6
7
8
# Add memories for different agents
memory.add("I prefer Italian cuisine", user_id="bob", agent_id="food-assistant")
memory.add("I'm allergic to peanuts", user_id="bob", agent_id="health-assistant")
memory.add("I live in Seattle", user_id="bob") # Shared across all agents
# Search agent-specific memories
memory.search("food preferences", user_id="bob", agent_id="food-assistant")
# Returns only food-related memories for this agent
Why this matters: Different agents can have specialized knowledge while sharing common user information.
8. Flexible Deployment Options
Mem0 offers three deployment modes:
1. Hosted Platform (Managed service):
1
2
3
4
5
6
7
from mem0 import MemoryClient
client = MemoryClient(
api_key="your-api-key",
org_id="your-org-id",
project_id="your-project-id"
)
2. Self-Hosted (Open Source):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from mem0 import Memory
memory = Memory.from_config({
"vector_store": {
"provider": "qdrant",
"config": {"host": "localhost", "port": 6333}
},
"embedder": {
"provider": "aws_bedrock",
"config": {"model": "amazon.titan-embed-text-v2:0"}
},
"llm": {
"provider": "aws_bedrock",
"config": {"model": "amazon.nova-micro-v1:0"}
}
})
3. Private Cloud: Deploy on Kubernetes, air-gapped servers, or private clouds
Why flexibility matters: You're not locked into Mem0's hosted service. Run it anywhere, with any models.
9. Framework Compatibility
Mem0 works with popular AI frameworks:
- OpenAI
- LangGraph
- CrewAI
- AutoGen
- Strands (via custom integration)
- Vercel AI SDK
Available in Python and JavaScript, so you can use it in any stack.
10. Advanced Memory Types
Procedural Memory: Store step-by-step workflows
1
2
3
4
5
memory.add(
"To deploy: 1. Run tests 2. Build Docker image 3. Push to registry",
user_id="dev123",
memory_type="procedural_memory"
)
Categorical Memory: Organize memories by category
1
2
3
4
5
memory.add(
"User prefers dark mode",
user_id="alice",
metadata={"category": "preferences"}
)
Immutable Memory: Facts that shouldn't change
1
2
3
4
5
memory.add(
"User's birthday is March 15, 1990",
user_id="alice",
immutable=True
)
11. Built-in Observability & Versioning
Every memory is:
- Timestamped: Know when it was created
- Versioned: Track changes over time with __PRE_PLACEHOLDER_12__
- Exportable: Full GDPR compliance with data export
- Traceable: See TTL, size, and access patterns
1
2
3
4
5
6
# Get memory history
history = memory.history(memory_id="mem_123")
# [
# {"timestamp": "2024-01-15T10:00:00Z", "change": "Initial creation"},
# {"timestamp": "2024-01-16T14:30:00Z", "change": "Content updated"}
# ]
12. Seamless Integration
The API is beautifully simple:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from mem0 import Memory
memory = Memory()
# Add memory
memory.add("User prefers dark mode", user_id="alice")
# Search memories
results = memory.search("preferences", user_id="alice")
# Get all memories
all_memories = memory.get_all(user_id="alice")
# Delete specific memory
memory.delete(memory_id="mem_123")
# Delete all memories for a user
memory.delete_all(user_id="alice")
13. Real-World Success Stories
Sunflower Sober: Scaled personalized recovery support to 80,000+ users with Mem0
"Mem0 turned our AI into true companions - tracking each user's struggles, strengths, and journey across the entire platform." — Koby Conrad, CEO
RevisionDojo: Personalized tutoring for every student
"Mem0 allowed us to unlock true personalized tutoring for every student, and it took us just a weekend to integrate." — Michael Tong, CTO
OpenNote: Scaled personalized visual learning
"OpenNote scaled personalized visual learning with Mem0 while reducing token costs by 40%" — Abhi Arya, Co-Founder
The Custom LLM Integration: A Key Innovation
One challenge I encountered was that Mem0 primarily supports OpenAI and Anthropic models out of the box. To use Amazon Nova Micro for memory fact extraction, I built a custom LLM wrapper:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class NovaMem0LLM:
"""Custom LLM wrapper for Amazon Nova models compatible with Mem0"""
def __init__(self, config: Dict[str, Any]):
self.model = config.get("model", "amazon.nova-micro-v1:0")
self.client = boto3.client("bedrock-runtime")
def generate_response(self, messages, **kwargs) -> str:
# Convert to Nova Converse API format
formatted_messages = self._format_messages(messages)
# Call Nova using Bedrock
response = self.client.converse(
modelId=self.model,
messages=formatted_messages,
inferenceConfig={
"maxTokens": self.max_tokens,
"temperature": self.temperature
}
)
return response["output"]["message"]["content"][0]["text"]
This adapter allows Mem0 to use Amazon Nova Micro for extracting facts from conversations—keeping everything within the AWS ecosystem and reducing costs.
Aha Moment: Realizing I could inject a custom LLM into Mem0's pipeline was a game-changer. It meant I could use the best model for each task:
- Nova Premier for conversations (high quality reasoning)
- Nova Micro for memory extraction (fast and cheap)
- Titan Embeddings v2 for vectors (optimized for this task)
This multi-model approach reduced costs by ~70% compared to using Claude for everything.
When Mem0 Shines
Mem0 is exceptional for:
- ✅ AI applications requiring long-term memory
- ✅ Multi-session conversations
- ✅ Personalized user experiences
- ✅ Complex entity relationships (graph memory)
- ✅ Multimodal applications (images, documents)
- ✅ Multi-agent systems
- ✅ Enterprise applications (SOC 2, HIPAA compliant)
- ✅ Cost-sensitive deployments (self-hosted option)
The Bottom Line
Mem0 doesn't just store conversation history—it builds a living, evolving knowledge graph about each user. It understands facts, relationships, and context, enabling AI agents to provide genuinely personalized experiences that improve over time.
Combined with Strands' model-driven orchestration, Mem0 creates AI agents that don't just respond—they remember, learn, and grow with their users.
Strands provides:
- Model-driven orchestration (let the model think)
- True provider agnostic (switch models without code changes)
- Production-ready observability (metrics, traces, logs built-in)
- Native AWS integration (deploy anywhere)
- Simple multi-agent primitives (swarms, graphs, workflows)
- Native MCP support (integrate with MCP ecosystem)
Mem0 provides:
- Intelligent fact extraction (LLM-powered memory)
- Semantic search (meaning, not keywords)
- Memory compression (80% token savings)
- User isolation (privacy by design)
- Framework compatibility (works with everything)
Together, they create a powerful foundation for building production-ready, memory-enabled AI agents that are cost-effective, scalable, and genuinely intelligent.
The Dual Memory Architecture: Session vs. Long-Term Memory
One of the most powerful aspects of this project is the dual memory system—combining Strands' built-in session management with Mem0's long-term memory. Understanding the difference between these two memory types is crucial for building truly intelligent agents.
The Two Types of Memory
1. Strands Session Memory (Conversation Context)
What it is: Strands agents have built-in conversation management that automatically maintains message history within a session. Every time you call the agent, it remembers the previous messages in that conversation.
How it works:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
from strands import Agent
# Create agent - session memory is built-in by default
agent = Agent()
# First message
agent("My favorite color is blue")
# Second message - agent remembers the first message
agent("What's my favorite color?")
# Agent responds: "Your favorite color is blue"
# Access conversation history
print(agent.messages) # Shows all messages exchangedUnder the hood: Strands uses a
ConversationManager to handle message history:1
2
3
4
5
6
7
8
from strands.agent.conversation_manager import SlidingWindowConversationManager
# Default: Sliding window keeps recent messages
agent = Agent(
conversation_manager=SlidingWindowConversationManager(
window_size=10 # Keep last 10 message pairs
)
)Characteristics:
- Scope: Single conversation session (in-memory)
- Duration: Lost when agent instance is destroyed (logout, server restart)
- Purpose: Maintain conversation flow and context
- Storage: In-memory
agent.messageslist - Automatic: No configuration needed, works out of the box
- Use case: "What did I just say?" "Continue from where we left off"
Conversation Managers Available:
- SlidingWindowConversationManager (default): Keeps last N messages
- SummarizingConversationManager: Summarizes old messages to save tokens
- NullConversationManager: No history (stateless)
Real-world analogy: Like having a conversation with someone at a coffee shop. They remember everything you said during that conversation, but if you leave and come back tomorrow, they won't remember you.
2. Strands Session Persistence (Optional)
Strands also offers session persistence to save conversation state across restarts:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from strands import Agent
from strands.session.file_session_manager import FileSessionManager
# Persist sessions to filesystem
session_manager = FileSessionManager(
session_id="user-123",
storage_dir="./sessions"
)
agent = Agent(session_manager=session_manager)
# Conversations are saved to disk
agent("Hello!")
# Later, create agent with same session_id
# It will load the previous conversation
agent2 = Agent(session_manager=FileSessionManager(session_id="user-123"))
agent2("What did I say before?") # Remembers "Hello!"Session managers available:
FileSessionManager: Local filesystem storageS3SessionManager: AWS S3 cloud storage- Custom: Build your own (Redis, DynamoDB, etc.)
Characteristics:
- Scope: Single user's conversation history
- Duration: Persists across restarts (saved to disk/S3)
- Purpose: Resume conversations after logout/restart
- Storage: File system or S3
- Use case: "Continue my conversation from yesterday"
Important: Session persistence saves the conversation history (messages), but doesn't extract facts or enable semantic search.
3. Mem0 Long-Term Memory (Intelligent Fact Storage)
What it is: Mem0 provides intelligent, persistent memory that extracts facts from conversations and stores them in a searchable vector database. This is fundamentally different from just saving message history.
How it works:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
from mem0 import Memory
memory = Memory()
# Session 1 (Monday)
memory.add(
"User's name is Alice and she loves Python programming",
user_id="alice"
)
# Mem0 extracts: "User's name is Alice" + "User loves Python programming"
# Session 2 (Wednesday - completely new agent instance)
results = memory.search("user information", user_id="alice")
# Returns: "User's name is Alice. User loves Python programming."Key difference: Mem0 doesn't just save messages—it extracts meaningful facts using an LLM:
1
2
3
4
5
6
7
8
# User says this in conversation:
"Hi, I'm Alice and I'm building a serverless web app using Lambda and DynamoDB"
# Mem0 extracts multiple facts:
# 1. "User's name is Alice"
# 2. "User is building a serverless web app"
# 3. "User is using AWS Lambda"
# 4. "User is using DynamoDB"Characteristics:
- Scope: Cross-session, multi-day, multi-device
- Duration: Persistent (stored in Qdrant vector database)
- Purpose: Remember user preferences, facts, and history
- Storage: Vector database with semantic search
- Intelligence: LLM-powered fact extraction
- Use case: "What's my name?" "What projects am I working on?" "What do I like?"
Real-world analogy: Like a friend who remembers your birthday, your favorite foods, and that story you told them three months ago—not just the exact words you said, but the meaningful facts.
The Key Differences: A Clear Comparison
| Feature | Strands Session Memory | Strands Session Persistence | Mem0 Long-Term Memory |
|---|---|---|---|
| What it stores | Message history | Message history | Extracted facts |
| Storage | In-memory | Filesystem/S3 | Vector database |
| Duration | Until agent destroyed | Across restarts | Permanent |
| Search | No search | No search | Semantic search |
| Intelligence | None (raw messages) | None (raw messages) | LLM fact extraction |
| Use case | Conversation flow | Resume conversations | Remember facts |
| Example | "You said X 3 messages ago" | "Yesterday you said X" | "You like Python" |
Why Both? The Power of Dual Memory
Using both memory types together creates a powerful combination:
Example conversation flow:
Monday (Session 1):
- User: "Hi, I'm Alice and I'm building a serverless web app on AWS"
- Agent (Strands session memory): Stores message in
agent.messages - Agent (calls save_memory tool): Mem0 extracts and stores facts
- Agent: "Nice to meet you, Alice! Tell me more about your serverless app."
- User: "I'm using Lambda, API Gateway, and DynamoDB"
- Agent (Strands session memory): Remembers previous message in this conversation
- Agent: "Great! Lambda, API Gateway, and DynamoDB are excellent choices for serverless."
Wednesday (Session 2 - User logs in again, NEW agent instance):
- User: "Can you help me with my project?"
- Agent (Strands session memory): Empty (new session)
- Agent (searches Mem0): Finds "User's name is Alice. User is building a serverless web app on AWS."
- Agent: "Of course, Alice! I remember you're building a serverless web app. What do you need help with?"
- User: "I need to add authentication"
- Agent (Strands session memory): Remembers "Can you help with my project?" from THIS session
- Agent: "For your serverless app, I recommend AWS Cognito..."
- Agent (saves to Mem0): "User needs authentication for serverless app"
Friday (Session 3):
- User: "What have I been working on this week?"
- Agent (searches Mem0): Retrieves all facts from this week
- Agent: "This week you've been building a serverless web app using Lambda, API Gateway, and DynamoDB, and you added authentication with Cognito."
My Implementation: The Best of Both Worlds
In my project, I use:
1. Strands Default Session Memory (automatic):
- Maintains conversation flow within each session
- No configuration needed—works out of the box
- Fast, in-memory access to recent messages
2. Mem0 Long-Term Memory (custom integration):
- Persistent facts across sessions
- Semantic search for relevant memories
- LLM-powered fact extraction
Why I didn't use Strands Session Persistence:
Strands offers
FileSessionManager and S3SessionManager to persist conversations, but I chose Mem0 instead because:Session Persistence limitations:
- ❌ Saves raw message history (not extracted facts)
- ❌ No semantic search (can't search "what do I like?")
- ❌ No intelligence (just saves/loads messages)
- ❌ Grows unbounded (entire conversation history)
Mem0 advantages:
- ✅ Extracts meaningful facts from conversations
- ✅ Semantic search ("food preferences" finds "likes pizza")
- ✅ LLM-powered intelligence
- ✅ Compact storage (facts, not full messages)
- ✅ Custom LLM integration (Nova Micro for cost savings)
- ✅ Self-hosted Qdrant (no AWS dependency)
Code comparison:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# Option 1: Strands Session Persistence (what I didn't use)
from strands.session.file_session_manager import FileSessionManager
session_manager = FileSessionManager(session_id="alice")
agent = Agent(session_manager=session_manager)
# Saves entire message history to disk
# Can resume conversation, but can't search facts
# Option 2: Mem0 Long-Term Memory (what I use)
from mem0 import Memory
memory = Memory()
def save_memory(content: str, user_id: str) -> str:
memory.add(content, user_id=user_id) # Extracts facts
return "Saved to memory"
def search_memory(query: str, user_id: str) -> str:
results = memory.search(query, user_id=user_id) # Semantic search
return format_results(results)
agent = Agent(tools=[save_memory, search_memory])
# Agent can intelligently save and search factsThe Hybrid Approach in Action
Within a single session (Strands session memory):
1
2
3
4
5
6
7
8
agent = Agent()
agent("I'm working on a Lambda function") # Message 1
agent("It needs to connect to DynamoDB") # Message 2
agent("How do I do that?") # Message 3
# Agent remembers all 3 messages in THIS session
# Can reference "the Lambda function" and "DynamoDB" from earlierAcross multiple sessions (Mem0 long-term memory):
1
2
3
4
5
6
7
# Monday
agent1 = Agent()
agent1("I'm Alice and I love Python") # Saved to Mem0
# Wednesday (new agent instance)
agent2 = Agent()
agent2("What's my name?") # Searches Mem0, finds "Alice"Why This Architecture Works
Strands session memory handles:
- ✅ Conversation flow ("as I mentioned earlier...")
- ✅ Immediate context (last few messages)
- ✅ Automatic, no configuration
Mem0 long-term memory handles:
- ✅ User facts (name, preferences, projects)
- ✅ Cross-session continuity
- ✅ Semantic search ("what do I like?")
- ✅ Intelligent fact extraction
Together, they create an agent that:
- Maintains natural conversation flow (Strands)
- Remembers you across sessions (Mem0)
- Understands context and facts (both)
AWS Diagram MCP Server: Architecture Diagrams from Natural Language
One of the most exciting additions to this project is the integration of AWS Diagram MCP Server—a tool that generates professional AWS architecture diagrams from natural language prompts. This showcases the power of the Model Context Protocol (MCP) and how Strands seamlessly integrates with the MCP ecosystem.
What is Model Context Protocol (MCP)?
MCP is an open standard that standardizes how AI applications provide context to Large Language Models. Think of MCP as "USB-C for AI"—just as USB-C provides a universal way to connect devices to peripherals, MCP provides a universal way to connect AI models to data sources and tools.
Key concepts:
- MCP Server: Exposes tools, resources, and context to AI agents
- MCP Client: Connects to MCP servers and makes tools available to agents
- Standardized Protocol: Works across different AI frameworks and platforms
Why MCP matters:
- No custom integrations: Write once, use everywhere
- Growing ecosystem: Thousands of community-built MCP servers
- Interoperability: Tools work across Claude, OpenAI, Amazon Q, and more
- Scalability: Connect to hundreds of tools without custom code
Industry adoption:
- ✅ AWS (Amazon Q CLI, Bedrock)
- ✅ Anthropic (Claude Desktop)
- ✅ OpenAI (Agents SDK)
- ✅ Microsoft (Azure AI)
- ✅ Google (Gemini)
AWS Diagram MCP Server: What It Does
The AWS Diagram MCP Server is an MCP server that creates professional AWS architecture diagrams using the Python
diagrams library. It supports:Diagram Types:
- ✅ AWS architecture diagrams (Lambda, S3, DynamoDB, etc.)
- ✅ Sequence diagrams (interaction flows)
- ✅ Flow diagrams (process flows)
- ✅ Class diagrams (system design)
Key Features:
- Natural language input: "Create a serverless web app with Lambda and DynamoDB"
- Professional output: PNG diagrams with official AWS icons
- Iterative design: "Add API Gateway" → Updates existing diagram
- Best practices: Follows AWS Well-Architected Framework patterns
Example prompt:
1
2
3
4
"Create a diagram for a serverless web application with:
- API Gateway for REST API
- Lambda functions for business logic
- DynamoDB for data storage
Output:PNG diagram with all components properly connected.

How Strands Integrates with MCP
Strands has first-class MCP support, making it trivial to integrate MCP servers:
Integration Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
from strands import Agent
from strands.tools.mcp import MCPClient
from mcp import stdio_client, StdioServerParameters
import sys
def get_diagram_mcp_client():
"""Get AWS Diagram MCP client as ToolProvider"""
# Windows-compatible command format
if sys.platform == "win32":
command = "uv"
args = [
"tool", "run",
"--from", "awslabs.aws-diagram-mcp-server@latest",
"awslabs.aws-diagram-mcp-server.exe"
]
else:
command = "uvx"
args = ["awslabs.aws-diagram-mcp-server@latest"]
mcp_client = MCPClient(
lambda: stdio_client(
StdioServerParameters(
command=command,
args=args
)
),
startup_timeout=120
)
return mcp_client
# Add MCP client to agent tools
mcp_client = get_diagram_mcp_client()
agent = Agent(
tools=[calculator, current_time, mcp_client], # MCP tools alongside native tools
model="us.amazon.nova-premier-v1:0"
)That's it! The MCP server's tools are now available to the agent.
What Happens Under the Hood
- Agent receives prompt: "Create a diagram for a serverless web app"
- Strands queries MCP server: "What tools are available?"
- MCP server responds: "I have
generate_diagramtool" - Agent calls tool:
generate_diagram(description="serverless web app with Lambda, API Gateway, DynamoDB") - MCP server generates: Python code using
diagramslibrary - MCP server executes: Runs code, generates PNG file
- MCP server returns: File path to generated diagram
- Agent responds: "I've created your diagram at
diagrams/generated-diagrams/diagram_abc123.png"




The Memory + MCP Combination: Iterative Diagram Design
Here's where the dual memory system shines—iterative diagram design:
Session 1 (Monday):
- User: "Create a diagram for a serverless web app with Lambda and DynamoDB"
- Agent: Generates diagram, saves to Mem0: "User created serverless web app diagram with Lambda and DynamoDB"
- Output:
diagram_v1.png
Session 2 (Wednesday):
- User: "Add API Gateway to my previous diagram"
- Agent (searches Mem0): Finds "User created serverless web app diagram with Lambda and DynamoDB"
- Agent: "I'll add API Gateway to your serverless web app diagram"
- Agent: Calls MCP tool with context: "Update diagram with Lambda, DynamoDB, and add API Gateway"
- Output:
diagram_v2.png(updated)
Session 3 (Friday):
- User: "Add CloudFront and S3 for static content"
- Agent (searches Mem0): Retrieves full diagram history
- Agent: Generates updated diagram with all components
- Output:
diagram_v3.png(complete architecture)
The magic: The agent remembers your previous diagrams and can iterate on them across sessions, days, or even weeks!
Real-World Example: Building a Complete Architecture
Conversation flow:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
User: "Create a diagram for a three-tier web application on AWS"
Agent: "I'll create a three-tier architecture diagram for you."
[Generates diagram with: ALB → EC2 (web tier) → EC2 (app tier) → RDS (data tier)]
User: "Make it serverless"
Agent: "I'll convert your three-tier architecture to serverless."
[Searches memory: "User has three-tier web app diagram"]
[Generates new diagram: CloudFront → API Gateway → Lambda → DynamoDB]
User: "Add authentication with Cognito"
Agent: "I'll add Cognito authentication to your serverless architecture."
[Searches memory: "User has serverless three-tier app with CloudFront, API Gateway, Lambda, DynamoDB"]
[Generates updated diagram with Cognito user pool integrated]
User: "Add monitoring and logging"
Agent: "I'll add CloudWatch for monitoring and logging."
[Generates final diagram with CloudWatch Logs and CloudWatch Metrics]Result: A complete, production-ready architecture diagram built iteratively through natural conversation.
Technical Implementation Details
Diagram Generation Tool
The agent uses a custom tool that wraps the MCP client:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def create_aws_diagram(description: str, workspace_dir: str = "diagrams") -> str:
"""
Create AWS architecture diagram from description.
Args:
description: Natural language description of architecture
workspace_dir: Directory to save diagram
Returns:
Path to generated diagram
"""
# MCP client handles the actual generation
result = mcp_client.generate_diagram(
description=description,
workspace_dir=workspace_dir
)
# Save diagram context to memory
memory.add(
f"User created AWS diagram: {description}",
user_id=user_id
)
return result['diagram_path']Memory-Enhanced Diagram Context
When generating diagrams, the agent searches memory for previous diagram context:
1
2
3
4
5
6
7
8
9
10
11
12
# In agent system prompt
system_prompt = f"""
You are a helpful AI assistant with diagram generation capabilities.
When users ask to create or modify diagrams:
1. Search memory for previous diagram context
2. Use MCP tools to generate diagrams
3. Save diagram context to memory for future iterations
4. Always pass workspace_dir="{diagrams_dir}" to generate_diagram tool
User: {user_id}
"""Benefits of MCP Integration
1. No Custom Integration Code
- MCP server handles all diagram generation logic
- Strands provides seamless MCP client integration
- Zero boilerplate for tool registration
2. Community Ecosystem
- Leverage thousands of existing MCP servers
- AWS Diagram MCP Server is just one example
- Add new capabilities without writing code
3. Standardized Protocol
- Works with any MCP-compatible client
- Future-proof as MCP adoption grows
- Interoperable across platforms
4. Separation of Concerns
- Diagram logic in MCP server (Python diagrams library)
- Agent logic in Strands (conversation, memory)
- Clean architecture, easy to maintain
Other MCP Servers You Can Integrate
The MCP ecosystem is growing rapidly. Here are some useful MCP servers:
AWS-specific:
aws-documentation-mcp-server: Search AWS docsaws-kb-retrieval-mcp-server: Query AWS Knowledge Basesaws-s3-mcp-server: Interact with S3 buckets
General purpose:
filesystem-mcp-server: File operationsgithub-mcp-server: GitHub API accesspostgres-mcp-server: Database queriesslack-mcp-server: Slack integration
All of these work with Strands using the same pattern:
1
2
3
4
5
6
7
8
9
from strands.tools.mcp import MCPClient
# Add any MCP server
mcp_client = MCPClient(lambda: stdio_client(StdioServerParameters(
command="uvx",
args=["mcp-server-name@latest"]
)))
agent = Agent(tools=[mcp_client])Windows Compatibility Note
One challenge I encountered was Windows compatibility. The MCP server uses
uvx (Unix-style command), which doesn't work on Windows. The solution:1
2
3
4
5
6
7
8
9
10
if sys.platform == "win32":
command = "uv"
args = [
"tool", "run",
"--from", "awslabs.aws-diagram-mcp-server@latest",
"awslabs.aws-diagram-mcp-server.exe" # Windows executable
]
else:
command = "uvx"
args = ["awslabs.aws-diagram-mcp-server@latest"]This ensures cross-platform compatibility.
Performance Considerations
Diagram generation time:
- Simple diagram (3-5 components): 2-3 seconds
- Complex diagram (10+ components): 5-8 seconds
- MCP server startup (first call): 10-15 seconds
- Subsequent calls: 2-3 seconds (server stays alive)
Optimization tip: Keep MCP server alive between requests by reusing the MCPClient instance.
Real-World Use Cases
1. Solution Architects
- Quickly prototype architectures during client meetings
- Iterate on designs based on feedback
- Generate documentation automatically
2. DevOps Teams
- Document existing infrastructure
- Plan migrations and upgrades
- Create runbooks with visual diagrams
3. Learning and Training
- Students learning AWS can visualize concepts
- Instructors can generate diagrams for lessons
- Interactive learning through conversation
4. Compliance and Auditing
- Generate architecture diagrams for compliance reviews
- Document security controls visually
- Track architecture changes over time
Aha Moment: MCP as the Future of AI Tools
Integrating the AWS Diagram MCP Server was a revelation. Instead of writing custom diagram generation code, I simply connected to an MCP server and got professional diagram capabilities instantly.
This is the future of AI development:
- Tools as services (MCP servers)
- Agents as orchestrators (Strands)
- Memory as context (Mem0)
The combination is incredibly powerful and demonstrates how modern AI applications should be built: modular, interoperable, and leveraging community ecosystems.
Architecture: How It All Comes Together
The Big Picture
Here's the complete architecture of the memory-enabled AI agent:

Key Architecture Components
1. User Interface Layer
- Streamlit Frontend: Interactive chat UI with diagram display and memory viewer
- FastAPI Backend: RESTful API handling requests, user management, and streaming
2. Agent Orchestration Layer
- Strands Agent: Model-driven orchestration with per-user instances
- Session Memory: In-memory conversation context (lost on logout)
- Tool Categories: Native tools, Memory tools, AWS tools, MCP tools
3. AWS Bedrock Services Layer
- Amazon Nova Premier: Primary LLM for reasoning, planning, and response generation
4. MCP Integration Layer
- AWS Diagram MCP Server: Generates professional AWS architecture diagrams
- Diagram Storage: PNG files saved to
diagrams/generated-diagrams/
5. Long-Term Memory Layer (Mem0)
- Nova Micro: Fast, cost-effective fact extraction
- Titan Embeddings v2: 1024-dimensional vector generation
- Qdrant: Vector database with semantic search and user isolation
Data Flow (AWS Diagram MCP Server) Example
Scenario: Alice asks to create a diagram and mentions her name
- Input: "Create a serverless web app diagram and remember my name is Alice"
- Memory Search: Agent checks Mem0 for previous diagram context
- Tool Selection: Nova Premier decides to use diagram tool + save memory
- Diagram Generation: MCP server creates
diagram_abc123.png - Fact Extraction: Nova Micro extracts "User's name is Alice"
- Vector Storage: Titan creates embedding → Qdrant stores with user_id
- Response: Returns text + diagram path to user
Days Later: "Update my previous diagram to add DynamoDB"
- Agent searches Mem0 → Finds previous diagram context
- Calls MCP with full context → Generates updated diagram
- The magic: Agent remembers the previous architecture across sessions!
Why This Architecture Works
✅ Separation of Concerns: Each layer has a clear responsibility
✅ Scalability: Per-user agent instances, isolated memory
✅ Flexibility: Swap models, add tools, integrate MCP servers easily
✅ Cost Optimization: Right model for each task (Premier, Micro, Titan)
✅ Persistence: Long-term memory survives restarts and sessions
✅ Extensibility: MCP protocol enables community tool integration
✅ Scalability: Per-user agent instances, isolated memory
✅ Flexibility: Swap models, add tools, integrate MCP servers easily
✅ Cost Optimization: Right model for each task (Premier, Micro, Titan)
✅ Persistence: Long-term memory survives restarts and sessions
✅ Extensibility: MCP protocol enables community tool integration
Data Flow: A Conversation Example
Let's trace what happens when Alice says: "Hi, I'm Alice and I love Python programming"
- Frontend (Streamlit): Captures input, sends POST request to endpoint /chat
- Backend (FastAPI): Receives request, retrieves or creates agent for user "alice"
- Strands Agent: Processes the message with Nova Premier
- Memory Tool Activation: Agent recognizes this contains important information, calls tool save_memory
- Mem0 Processing:
- Sends conversation to Nova Micro: "Extract facts from: 'Hi, I'm Alice and I love Python programming'"
- Nova Micro returns: "User's name is Alice. User loves Python programming."
- Sends facts to Titan Embeddings v2, generates 1024-dim vectors
- Stores in Qdrant with user_id="alice"
- Response Generation: Nova Premier generates friendly response
- Return Path: Response flows back through FastAPI → Streamlit → User
Later, when Alice asks: "What do you know about me?"
- Agent receives query
- Calls search_memory("user information", user_id="alice")
- Mem0 converts query to vector, searches Qdrant for similar vectors
- Returns: "User's name is Alice. User loves Python programming."
- Nova Premier uses this context to generate: "I know your name is Alice and you love Python programming!"
Building the Application: Key Components
Component 1: Memory Configuration (The Foundation)
The
1
memory_config.pyfile is the heart of the system—a singleton pattern ensuring all parts of the application share the same memory instance:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
def get_memory():
"""Get or create the shared memory instance"""
global _memory_instance
if _memory_instance is None:
custom_llm = NovaMem0LLM({
"model": "amazon.nova-micro-v1:0",
"temperature": 0.2,
"max_tokens": 500
})
config = {
"vector_store": {
"provider": "qdrant",
"config": {
"collection_name": "ajay_memory_v2",
"embedding_model_dims": 1024,
"on_disk": True
}
},
"embedder": {
"provider": "aws_bedrock",
"config": {
"model": "amazon.titan-embed-text-v2:0"
}
},
"version": "v1.1"
}
_memory_instance = Memory.from_config(config)
_memory_instance.llm = custom_llm
return _memory_instance
Why this matters: By using a singleton, we avoid creating multiple Qdrant connections and ensure memory consistency across all agent instances.
Aha Moment #2: Initially, I created a new memory instance for each user. This caused connection pool exhaustion and inconsistent memory states. The singleton pattern solved both issues elegantly.
Component 2: Memory-Enabled Tools
The magic happens in the tools that bridge the agent and memory layer:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def search_memory(query: str, user_id: str = "default") -> str:
"""Search through user's stored memories."""
try:
result = memory.search(query, user_id=user_id, limit=5)
memories = result.get('results', []) if isinstance(result, dict) else result
if memories:
memory_text = "\n".join([f"- {mem['memory']}" for mem in memories])
return f"Found relevant memories:\n{memory_text}"
return "No relevant memories found."
except Exception as e:
return f"Error searching memory: {str(e)}"
def save_memory(content: str, user_id: str = "default") -> str:
"""Save important information to user's memory."""
try:
result = memory.add(content, user_id=user_id)
return f"Successfully saved to memory"
except Exception as e:
return f"Error saving memory: {str(e)}"
These tools give the agent the ability to:
- Proactively save important information without explicit user commands
- Retrieve context when answering questions
- Build relationships by remembering preferences and history
Component 3: FastAPI Backend (The Orchestrator)
The API layer manages user sessions, agent instances, and memory operations:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
agents = {}# Store agents per userdef get_or_create_agent(user_id: str):
"""Get existing agent for user or create new one"""
if user_id not in agents:
agents[user_id] = create_memory_agent(user_id)
logger.info(f"Created new agent for user: {user_id}")
return agents[user_id]
async def chat(request: ChatRequest):
"""Process chat messages with memory-enabled agent"""
user_id = request.user_id or "default"
latest_message = request.messages[-1]
agent = get_or_create_agent(user_id)
result = agent(latest_message.content)
# Clean up thinking tags
response = re.sub(r'<thinking>.*?</thinking>', '', result, flags=re.DOTALL).strip()
return ChatResponse(message=response, success=True, user_id=user_id)
Key design decision: Caching agent instances per user dramatically improves response time (from ~3s to ~0.5s for subsequent requests) because we avoid reinitializing the agent and its tools.
Component 4: Streamlit Frontend (The Face)
The UI provides an intuitive interface for interacting with the memory-enabled agent:
Key features:
- Streaming responses: Words appear progressively, creating a natural conversation feel
- Memory visualization: Users can view and search their stored memories
- OAuth integration: Secure authentication with AWS Cognito
- Responsive design: Works on desktop and mobile
Aha Moment #3: Adding streaming made a huge UX difference. Instead of waiting 5-10 seconds for a complete response, users see words appearing immediately, making the agent feel more responsive even though total time is the same.
The "Aha!" Moments: Lessons from Development
Aha Moment #4: The Memory Extraction Timing Problem
Early versions saved memories after every single message. This created noise—the database filled with trivial facts like "User said hello" or "User asked about the weather."
The solution: Let the agent decide when to save memories. By making
1
save_memorya tool, Nova Premier intelligently determines what's worth remembering:
- User shares their name? Save it.
- User mentions a project? Save it.
- User says "okay" or "thanks"? Don't save it.
This selective memory dramatically improved relevance and reduced storage costs.
Aha Moment #5: The User Isolation Challenge
During testing, I discovered memories were leaking between users. Alice's preferences appeared in Bob's context!
The root cause: I was passing
1
user_idto the memory layer but not isolating agent instances. Multiple users shared the same agent, causing context contamination.
The fix: Creating separate agent instances per user and ensuring every memory operation includes the
1
user_idparameter:
1
2
3
4
agents = {}# Dictionary mapping user_id -> agent instancedef get_or_create_agent(user_id: str):
if user_id not in agents:
agents[user_id] = create_memory_agent(user_id)
return agents[user_id]
Aha Moment #6: The Thinking Tags Mystery
Nova Premier sometimes included
1
<thinking>tags in responses, exposing its internal reasoning to users:
1
2
3
4
5
<thinking>
The user is asking about their preferences. I should search memory for relevant information.
</thinking>
Based on what I remember, you love Python programming!
While fascinating for debugging, this confused users.
The solution: A simple regex filter in the API layer:
1
response = re.sub(r'<thinking>.*?</thinking>', '', response, flags=re.DOTALL).strip()
Clean responses, happy users.
Aha Moment #7: The Qdrant On-Disk Configuration
Initially, I used Qdrant's in-memory mode for development. Everything worked perfectly... until I restarted the application and all memories vanished.
The fix: Enabling persistent storage:
1
2
3
4
5
6
7
8
"vector_store": {
"provider": "qdrant",
"config": {
"collection_name": "ajay_memory_v2",
"embedding_model_dims": 1024,
"on_disk": True# ← This line saves lives
}
}
Now memories persist across restarts, as they should.
Real-World Use Cases and Benefits
Use Case 1: Customer Support Agent
Imagine a customer support chatbot that remembers:
- Customer's name and account details
- Previous issues and resolutions
- Communication preferences (email vs. phone)
- Product interests and purchase history
Before memory: "I already told you my account number three times!"
With memory: "Hi Sarah! I see you contacted us last week about the billing issue. Has that been resolved?"
Use Case 2: Personal AI Assistant
A productivity assistant that knows:
- Your work schedule and preferences
- Ongoing projects and deadlines
- Frequently used tools and workflows
- Personal goals and priorities
Example interaction:
- Monday: "I'm working on the Q4 presentation using Python and Matplotlib"
- Wednesday: "What was I working on this week?"
- Agent: "You're working on the Q4 presentation using Python and Matplotlib. Would you like help with data visualization?"
Use Case 3: Educational Tutor
A learning assistant that tracks:
- Student's knowledge level and learning pace
- Topics they struggle with
- Preferred learning styles
- Progress over time
Benefit: Personalized learning paths that adapt to each student's needs.
Use Case 4: Healthcare Assistant
A medical information assistant that remembers:
- Patient's medical history (with proper security)
- Medications and allergies
- Previous questions and concerns
- Preferred communication style
Critical advantage: Continuity of care and reduced risk of miscommunication.
Conclusion: The Future of Memory-Enabled AI
Building this memory-powered AI agent has been a journey of discovery, frustration, and ultimately, satisfaction. What started as a simple experiment evolved into a production-ready system that demonstrates the power of combining the right tools.
Key Takeaways
- Memory transforms AI from tools to assistants: The difference between a stateless chatbot and a memory-enabled agent is like the difference between a vending machine and a personal assistant.
- Framework choice matters: Strands' simplicity and AWS-native design made development faster and more enjoyable than alternatives.
- Mem0 solves a hard problem elegantly: Building a memory layer from scratch would have taken months; Mem0 provided it in days.
- Multi-model strategies optimize cost and performance: Using the right model for each task (Nova Premier, Nova Micro, Titan) dramatically improved both.
- User experience details matter: Streaming responses, clean error handling, and memory visualization turned a functional prototype into a delightful product.
What's Next?
Future enhancements I'm considering:
- Multi-modal memory: Remembering images, documents, and code snippets
- Memory summarization: Condensing long conversation histories
- Proactive suggestions: Agent initiates conversations based on memory
- Memory sharing: Controlled sharing of memories between users (e.g., team contexts)
- Advanced analytics: Insights into memory usage patterns
Final Thoughts
The combination of Amazon Bedrock's powerful models, Strands' elegant framework, and Mem0's intelligent memory layer creates something greater than the sum of its parts. It's not just about building an AI agent—it's about building an AI that builds relationships.
If you're building AI applications, I encourage you to think beyond stateless interactions. Memory isn't just a feature; it's the foundation of genuine intelligence.
The code is available on GitHub https://github.com/Ajaykumarkv17/AWS_strands_agents_aws_mcp and I'd love to hear about your experiences building memory-enabled agents. What challenges did you face? What solutions did you discover?
Let's build AI that remembers, learns, and grows with its users.
Enjoyed reading this content? Let the author know!
Your likes, comments, shares, and saves help creators reach more builders.
Loading recommendations
Loading article