Building Persistent Memory Multi-Agent AI with Amazon Bedrock AgentCore + Mem0
A hands-on guide to wiring Amazon Bedrock (Nova Pro + Titan Embed v2) with Mem0 and FAISS to give multi-agent systems durable, semantically searchable long-term memory and deploy them on AgentCore Runtime.
Building a Memory-Persistent Multi-Agent AI System with Amazon Bedrock AgentCore + Mem0
Author: Sweta Jha | Senior Data Scientist & AI/ML Engineer | AWS Community Builder (AI/ML) Stack: Strands Agents SDK · Amazon Bedrock Nova Pro · AgentCore Runtime · Mem0 · FAISS · FastAPI
TL;DR
Most AI agents forget everything between conversations. In this post I'll show you how to wire Amazon Bedrock AgentCore Memory together with Mem0 to give your multi-agent system persistent, semantically-searchable long-term memory — deployed as a production-ready container on AgentCore Runtime, with zero OpenAI dependency.
Why Agents Need Memory
Imagine hiring a consultant who forgets every project the moment they walk out the door. You'd brief them from scratch every single time. That's exactly what most LLM-based agents do today.
AgentCore Memory + Mem0 solves this by:
- Persisting every agent interaction to a vector store
- Semantically retrieving the most relevant past context before each new request
- Sharing memory across agents — your ResearchAgent's findings are visible to your WriterAgent
- Surviving session restarts — memory lives in FAISS on disk (or OpenSearch in production)
Architecture Overview
Plain text
┌─────────────────────────────────────────────────────────────────┐│ User / Client Application │└────────────────────────────┬────────────────────────────────────┘│ HTTP POST /invoke▼┌─────────────────────────────────────────────────────────────────┐│ Amazon Bedrock AgentCore Runtime ││ ┌──────────────────────────────────────────────────────────┐ ││ │ FastAPI / Handler │ ││ └───────────────────────┬──────────────────────────────────┘ ││ │ ││ ┌──────────────▼──────────────┐ ││ │ Orchestrator Agent │ ◄── Strands SDK ││ │ (Amazon Nova Pro) │ ││ └──┬───────────┬──────────────┘ ││ │ │ ││ ┌──────────▼──┐ ┌─────▼──────┐ ┌────────────────┐ ││ │ ResearchAgent│ │WriterAgent │ │ ReviewerAgent │ ││ │ (Nova Pro) │ │ (Nova Pro) │ │ (Nova Pro) │ ││ └──────┬──────┘ └──────┬─────┘ └───────┬────────┘ ││ │ │ │ ││ └────────────────┴─────────────────┘ ││ │ ││ ┌──────▼───────┐ ││ │ Mem0 Layer │ ◄── AgentCore Memory ││ │ ┌─────────┐ │ ││ │ │ FAISS │ │ ◄── Vector Store (local) ││ │ │ (local) │ │ ││ │ └─────────┘ │ ││ │ ┌─────────┐ │ ││ │ │ SQLite │ │ ◄── History DB ││ │ └─────────┘ │ ││ └──────────────┘ ││ ││ Embeddings: Amazon Titan Embed v2 (Bedrock) ││ LLM: Amazon Nova Pro (Bedrock) │└─────────────────────────────────────────────────────────────────┘
Three specialist agents, one shared memory layer. Every agent reads from and writes to the same Mem0 store, so knowledge compounds over time.
Prerequisites
Requirement | Notes |
|---|---|
Python 3.11+ | Tested on 3.11 and 3.12 |
AWS Account | With Bedrock model access enabled |
Amazon Nova Pro access | Enable in Bedrock console → Model Access |
Amazon Titan Embed v2 access | Same — enable in Model Access |
Docker (optional) | For AgentCore Runtime deployment |
Project Structure
Plain text
agentcore-mem0/├── agents/│ ├── orchestrator.py # Routes requests, injects memory context│ ├── research_agent.py # Fetches docs, summarises findings│ └── writer_reviewer_agents.py # Drafts & reviews content├── memory/│ └── mem0_layer.py # Mem0 + Bedrock Titan Embed integration├── agentcore_handler.py # AgentCore Runtime entry point├── api_server.py # FastAPI wrapper for local dev├── Dockerfile # AgentCore Runtime container├── requirements.txt└── .env.example
Step 1 — Install Dependencies
Bash
# Clone the repogit clone https://github.com/sweta-jha/agentcore-mem0-multiagentcd agentcore-mem0# Create virtual environmentpython -m venv venv && source venv/bin/activate# Installpip install -r requirements.txt
Step 2 — Configure Environment
Bash
cp .env.example .env# Edit .env with your AWS credentials and Bedrock model IDs
Key variables:
Bash
AWS_REGION=us-east-1BEDROCK_MODEL_ID=amazon.nova-pro-v1:0BEDROCK_EMBEDDING_MODEL=amazon.titan-embed-text-v2:0MEM0_USER_ID=your_username
Step 3 — The Memory Layer (Core Innovation)
The heart of this system is
memory/mem0_layer.py . Here's how Mem0 is configured to use Amazon Bedrock entirely — no OpenAI key required:Python
def build_mem0_config() -> dict:return {# LLM: Amazon Bedrock Nova Pro for memory distillation"llm": {"provider": "aws_bedrock","config": {"model": "amazon.nova-pro-v1:0","aws_region": "us-east-1","temperature": 0.1,"max_tokens": 1500,},},# Embedder: Amazon Titan Embed v2 (1024-dim vectors)"embedder": {"provider": "aws_bedrock","config": {"model": "amazon.titan-embed-text-v2:0","aws_region": "us-east-1","embedding_dims": 1024,},},# Vector Store: FAISS persisted to disk"vector_store": {"provider": "faiss","config": {"embedding_model_dims": 1024,"path": "./memory_store",},},"history_db_path": "./memory_store/mem0_history.db",}
How Memory Injection Works
Before every agent call, we retrieve semantically relevant past interactions and inject them into the system prompt:
Python
def format_for_prompt(self, query: str, limit: int = 5) -> str:"""Retrieve relevant memories and format as a context blockready to inject into any agent's system prompt."""memories = self.search(query, limit=limit)if not memories:return "No relevant past context found."lines = ["## Relevant Memory Context\n"]for i, mem in enumerate(memories, 1):score = mem.get("score", 0)text = mem.get("memory", "")agent = mem.get("metadata", {}).get("agent_id", "unknown")lines.append(f"{i}. [{agent}] (relevance: {score:.2f}) {text}")return "\n".join(lines)
This means your agent's system prompt becomes:
Plain text
[Standard system prompt]## Relevant Memory Context1. [research_agent] (relevance: 0.91) User researched Strands SDK on 2025-06-01...2. [writer_agent] (relevance: 0.84) Blog draft written on AgentCore Memory topic...3. [orchestrator] (relevance: 0.79) User asked about Mem0 FAISS integration...
The agent knows what it has done before and builds on it.
Step 4 — The Orchestrator Agent
The Orchestrator reads memory, decides which specialist to call, and writes back:
Python
def run_orchestrator(user_message: str, user_id: str = "default_user") -> str:memory = get_memory(user_id)# 1. Retrieve relevant past contextmemory_context = memory.format_for_prompt(query=user_message, limit=5)# 2. Enrich the prompt with memoryenriched_prompt = f"""[MEMORY CONTEXT]{memory_context}[USER REQUEST]{user_message}"""# 3. Run the Strands Agentagent, _ = build_orchestrator(user_id)result = agent(enriched_prompt)response_text = str(result)# 4. Persist this interaction to Mem0memory.add(content=f"User asked: {user_message[:200]} | Response: {response_text[:300]}",agent_id="orchestrator",)return response_text
The read-enrich-run-write cycle is the key pattern. Every agent follows it.
Step 5 — Specialist Agents with Tools
ResearchAgent Tools (Strands SDK @tool decorator)
Python
from strands import Agent, tool@tooldef search_aws_docs(service_name: str, topic: str) -> str:"""Search AWS documentation for a given service and topic.In production: replace with Bedrock Knowledge Base retrieval."""# Swap this body for real RAG / KB retrievalreturn f"Documentation results for {service_name} → {topic} ..."@tooldef extract_keywords(text: str) -> list[str]:"""Extract AWS service names from text."""aws_services = ["AgentCore", "Bedrock", "Lambda", "DynamoDB", "Mem0", ...]return [s for s in aws_services if s.lower() in text.lower()]
WriterAgent Tools
Python
@tooldef generate_section_outline(topic: str, num_sections: int = 6) -> str:"""Generate a structured blog post outline."""sections = ["Introduction & Motivation","Architecture Overview","Prerequisites & Setup","Step-by-Step Implementation","Code Deep Dive","Deployment on AgentCore Runtime",]return "\n".join(f"{i+1}. {s}" for i, s in enumerate(sections[:num_sections]))
Step 6 — AgentCore Runtime Handler
AgentCore Runtime calls your
handler() function for every request:Python
def handler(event: dict, context=None) -> dict:user_message, user_id, task_type = parse_event(event)memory = AgentMemoryLayer(user_id=user_id)# Route to the right agent based on sessionAttributes.taskTypeif task_type == "research":response = run_research(user_message, memory)elif task_type == "write":response = run_writer(user_message, memory)elif task_type == "review":response = run_reviewer(user_message, memory)else:response = run_orchestrator(user_message, user_id=user_id)return {"statusCode": 200,"body": json.dumps({"response": response,"metadata": {"user_id": user_id,"task_type": task_type,"memory_backend": "Mem0 + FAISS + Bedrock Titan Embed",},}),}
The event envelope from AgentCore looks like this:
JSON
{"inputText": "Research AgentCore Memory and write me a blog outline.","sessionId": "session-abc-123","sessionAttributes": {"userId": "sweta_jha","taskType": "orchestrate"}}
Step 7 — Local Testing
Bash
# Option A: Run handler directlypython agentcore_handler.py# Option B: Run FastAPI serveruvicorn api_server:app --reload --port 8080
Test with curl:
Bash
# Full orchestrationcurl -X POST http://localhost:8080/invoke \-H "Content-Type: application/json" \-d '{"message": "What is AgentCore Memory and how does Mem0 integrate?","user_id": "sweta_jha","task_type": "orchestrate"}'# Research onlycurl -X POST http://localhost:8080/invoke \-d '{"message": "Strands SDK key features", "task_type": "research"}'# View all stored memoriescurl http://localhost:8080/memory/sweta_jha# Semantic memory searchcurl "http://localhost:8080/memory/sweta_jha/search?q=AgentCore&limit=3"
Step 8 — Deploy to AgentCore Runtime
Build and Push Container
Bash
# Builddocker build -t agentcore-mem0-agent .# Tag for ECRaws ecr get-login-password --region us-east-1 \| docker login --username AWS --password-stdin \123456789.dkr.ecr.us-east-1.amazonaws.comdocker tag agentcore-mem0-agent:latest \123456789.dkr.ecr.us-east-1.amazonaws.com/agentcore-mem0-agent:latestdocker push 123456789.dkr.ecr.us-east-1.amazonaws.com/agentcore-mem0-agent:latest
Create AgentCore Agent
Bash
aws bedrock-agent create-agent \--agent-name "mem0-multiagent" \--description "Multi-agent system with Mem0 persistent memory" \--foundation-model "amazon.nova-pro-v1:0" \--agent-resource-role-arn "arn:aws:iam::123456789:role/AmazonBedrockAgentRole" \--region us-east-1
Deploy to AgentCore Runtime
Bash
# AgentCore Runtime (GA - check latest CLI syntax in AWS docs)aws bedrock-agentcore create-agent-runtime \--agent-runtime-name "mem0-multiagent-runtime" \--container-configuration '{"imageUri": "123456789.dkr.ecr.us-east-1.amazonaws.com/agentcore-mem0-agent:latest","port": 8080}' \--region us-east-1
AgentCore Runtime handles:
- Auto-scaling based on request volume
- Health checks via your
/healthendpoint
- OAuth 2.0 authentication
- Session management across requests
- CloudWatch metrics and logging out of the box
The Memory Lifecycle in Action
Here's what happens across three separate sessions with the same user:
Plain text
Session 1 (Day 1)User: "Research Strands SDK"→ ResearchAgent runs, stores findings in Mem0→ Memory: "Researched Strands SDK: ReAct loop, tool decorators, Bedrock integration"Session 2 (Day 2)User: "Write a blog post"→ Orchestrator retrieves Session 1 memory (relevance: 0.89)→ WriterAgent gets: "## Memory Context: User researched Strands SDK yesterday..."→ Blog draft references Strands SDK correctly without re-researching→ Memory: "Wrote blog post on Strands SDK + AgentCore"Session 3 (Day 3)User: "Review the draft"→ ReviewerAgent retrieves Sessions 1 & 2 memories→ Knows the topic, knows the draft exists, provides targeted review→ Memory: "Review complete: draft approved with minor edits"
Each agent builds on what came before. This is the power of shared persistent memory.
Production Considerations
Concern | Local Dev | Production |
|---|---|---|
Vector Store | FAISS on disk | Amazon OpenSearch Serverless |
History DB | SQLite | Amazon RDS (PostgreSQL) |
Memory Scope | Per-user files | DynamoDB + S3 |
Auth | None | AgentCore OAuth 2.0 |
Scaling | Single instance | AgentCore Runtime auto-scale |
Monitoring | Stdout logs | CloudWatch + X-Ray |
Swap FAISS for OpenSearch (Production)
Python
"vector_store": {"provider": "opensearch","config": {"host": "your-opensearch-endpoint.us-east-1.aoss.amazonaws.com","port": 443,"index_name": "mem0-agent-memory","embedding_model_dims": 1024,"use_ssl": True,},}
Key Takeaways
- AgentCore Memory + Mem0 = persistent agent intelligence. Agents remember past interactions semantically, not just as raw chat history.
- 100% AWS-native stack. Amazon Bedrock Nova Pro for LLM inference, Amazon Titan Embed v2 for embeddings — no OpenAI dependency.
- Strands SDK
@tooldecorator makes it trivially easy to extend any agent with custom capabilities.
- Shared memory across agents means your ResearchAgent's findings flow automatically into your WriterAgent's context — no manual data passing needed.
- AgentCore Runtime handles all the infrastructure concerns (scaling, auth, health checks) so you ship agent logic, not DevOps boilerplate.
What's Next?
- Add Bedrock Knowledge Bases as a RAG source inside
search_aws_docsfor real-time AWS documentation retrieval
- Swap FAISS for Amazon OpenSearch Serverless for production-scale vector search
- Add AgentCore Gateway to expose your multi-agent system as a managed API endpoint
- Integrate Amazon Bedrock Guardrails for content filtering and PII redaction
- Add a PlannerAgent that decomposes complex multi-step tasks before routing to specialists
Repository
Enjoyed reading this content? Let the author know!
Your likes, comments, shares, and saves help creators reach more builders.
Loading recommendations
Loading article