AWS Builder Center

Understanding Storage, Memory, and State in Amazon Bedrock AgentCore

How AI Agents Persist Files, Remember Conversations, and Learn from Experience

TL;DR: AgentCore provides four complementary persistence layers—Persistent Session Storage for files and project state, Short-Term Memory for conversation context, Long-Term Memory with episodic learning for accumulated knowledge, and MCP State for tool-level interactivity. Together, these enable agents to maintain sophisticated state across sessions, tools, and interactive workflows.

The Four Pillars of Agent Persistence


When building production AI agents, one of the most critical challenges is state management. How do agents remember what they've done? How do they learn from past interactions? How do they maintain context across long-running tasks? How do they interact with users mid-execution?
Amazon Bedrock AgentCore addresses this through four distinct but complementary mechanisms:
  1. Persistent Session Storage - The filesystem layer
  1. Short-Term Memory - Conversation context
  1. Long-Term Memory - Extracted knowledge with episodic learning
  1. MCP State - Tool-level interactivity (elicitation, sampling, progress)
Let's break down each layer and understand how they work together.



Layer 1: Persistent Session Storage (The Filesystem)

What It Is

Persistent Session Storage provides agents with a durable filesystem at  /mnt/workspace  (or any configured mount path) that survives session stop/resume cycles. Think of it as giving your agent a "hard drive" that persists between executions.

What It Stores

text
/mnt/workspace/
├── main.py # Source code
├── auth.py
├── requirements.txt # Dependencies
├── .venv/ # Virtual environments
│ └── node_modules/
├── .git/ # Version control history
└── .sessions/ # Conversation state (if using FileSessionManager)
└── session-123.json
File types stored:
  • ✅ Application code, artifacts, build outputs
  • ✅ Configuration files, environment setups
  • ✅ Installed packages (node_modules, .venv, etc.)
  • ✅ Git repositories with full history
  • ✅ Intermediate results, logs, state files

Characteristics

  • 14-day retention: Files automatically persist for 14 days of inactivity
  • Survives stop/resume: Agent can be stopped, compute terminated, then resumed with complete filesystem intact
  • Session-isolated: Each  runtimeSessionId  gets isolated storage; sessions can't access each other's files
  • S3-backed: Automatically replicated to AgentCore-managed S3 buckets
  • POSIX-compatible: Works with standard tools ( git ,  npm ,  pip ,  ls ,  cat , etc.)

Use Case Example: Coding Agent

Python
from strands import Agent
from strands.session import FileSessionManager
from bedrock_agentcore.runtime import BedrockAgentCoreApp

app = BedrockAgentCoreApp()
WORKSPACE = "/mnt/workspace"

@app.entrypoint
def handle_request(payload):
# Files written to /mnt/workspace persist across sessions
session_manager = FileSessionManager(
session_id=payload.get("session_id"),
storage_dir=f"{WORKSPACE}/.sessions"
)

agent = Agent(
model=model,
tools=[file_read, file_write, shell],
session_manager=session_manager,
system_prompt="Project files are in /mnt/workspace."
)

return agent(payload.get("prompt"))
Invocation pattern:
Python
# First invocation - create project
invoke("Set up a FastAPI app in /mnt/workspace with authentication")

# Stop session (compute terminates, files flush to S3)
client.stop_runtime_session(runtimeSessionId="session-123")

# Hours/days later - resume with same session-id
invoke("Add rate limiting to the API you created")
# Agent resumes with complete project: main.py, auth.py, requirements.txt, .venv/
Why this matters: The agent doesn't need to recreate the project structure, reinstall dependencies, or lose context about what was built. It's like picking up exactly where you left off.



Layer 2: Short-Term Memory (Conversation Context)

What It Is

Short-Term Memory stores raw conversation events—every user message, assistant response, and tool invocation within the current session. This is the agent's "working memory" for the active conversation.

What It Stores

JSON
Raw Conversation Events:
{
"events": [
{"role": "user", "content": "Create FastAPI app..."},
{"role": "assistant", "content": "I've created main.py..."},
{"role": "user", "content": "Sprint planning 60min timebox"},
{"role": "assistant", "content": "Meeting completed on time"}
]
}
Characteristics:
  • Ephemeral by default: Conversation state is session-scoped
  • Full fidelity: Every message, tool call, and response preserved
  • Context for follow-ups: Enables "Add authentication" after "Create API" without repeating context
  • API access:  ListEvents()  retrieves conversation history

Persistence Options

Option A: In-Memory (Default)
Python
from strands.session import InMemorySessionManager

session_manager = InMemorySessionManager(session_id="conv-001")
# Lost when session stops
Option B: Filesystem (Persistent)
Python
from strands.session import FileSessionManager

session_manager = FileSessionManager(
session_id="conv-001",
storage_dir="/mnt/workspace/.sessions"
)
# Conversation history persists alongside project files
Option C: DynamoDB (Shared)
Python
from strands.session import DynamoDBSessionManager

session_manager = DynamoDBSessionManager(
session_id="conv-001",
table_name="agent-conversations"
)
# Conversation accessible across multiple agent instances

The AgentCore Memory API

AgentCore provides a Memory API for storing and retrieving conversation context:
Python
# Store conversation context
memory_client.create_session(
sessionId="conv-001",
context={"user_id": "u-123", "project": "FastAPI-auth"}
)

# Retrieve conversation history
events = memory_client.list_events(
sessionId="conv-001",
maxResults=50
)
This powers the Short-Term Memory box in the diagram, enabling agents to maintain conversation continuity.



Layer 3: Long-Term Memory (Extracted Knowledge)

What It Is

Long-Term Memory is where agents extract and persist insights from conversations using multiple strategies. Unlike Short-Term Memory (which stores raw events), Long-Term Memory stores structured, searchable knowledge.

The Four Memory Strategies

1. Semantic Memory - Facts & Knowledge

Extracts discrete facts and knowledge from conversations:
text
Examples:
- "User prefers Python"
- "Team has 5 developers"
- "FastAPI project: /mnt/workspace/api-project"
Storage: Vector embeddings for semantic search Retrieval:  retrieve_memory_records(query="sprint planning patterns") 

2. Summary Memory - Conversation Condensation

Distills multi-turn conversations into concise summaries:
text
Example:
Original (300 messages): "Create API... Add auth... Fix bug... Deploy..."
Summary: "36-min pricing discussion. Decided on Enterprise plan."
Purpose: Compress conversation history while retaining key decisions

3. User Preference Memory - Settings & Personalization

Captures user preferences and settings:
text
Examples:
- "User wants dark mode"
- "Email digest at 9 AM"
- "Prefers concise responses"
Purpose: Personalize agent behavior across sessions

4. Episodic Memory - Experience Sequences ⭐

The most powerful strategy—captures sequences of experiences across multiple episodes:
text
Episodes:
- "Sprint planning #1 - 90 min"
- "Sprint planning #2 - 60 min ✓"
- "Sprint planning #3 - 60 min ✓"

Reflections (Cross-Episode):
- "60-min timebox optimal"
- "Pre-estimate stories first"
- "Success rate: 90%"
- "Learns what works"
What makes it special:
  • Cross-session learning: Generates reflections across multiple conversations
  • Pattern detection: Identifies what works ("60-min sprints work best")
  • Temporal awareness: Understands sequences ("learned from 5 episodes")
  • Compound knowledge: Combines insights from multiple experiences

How Long-Term Memory Works

Python
# Background extraction job runs after conversation
extract_memory(
session_id="conv-001",
strategies=["semantic", "summary", "episodic", "user_preference"]
)

# Later, agent retrieves relevant memories
memories = retrieve_memory_records(
query="sprint planning patterns"
)
# Returns:
# - Semantic: "User prefers 60-min timeboxes"
# - Episodic: "60-min sprints work best (learned from 5 episodes)"
# - Custom: "Pre-estimate stories before planning"

Memory Retrieval API

Python
retrieve_memory_records(
query="sprint planning patterns"
)

# Returns:
# - Semantic facts across strategies
# - Episodic episodes with reflections
# - Includes cross-episode reflections
# - Semantic search finds relevant patterns
The diagram shows Long-Term Memory as a collection of Semantic, Summary, User Preference, Episodic, and Custom extraction strategies, all feeding into a unified retrieval API.



Layer 4: MCP State (Tool-Level Interactivity)

What It Is

MCP (Model Context Protocol) State is a framework-managed execution context that enables tools to perform interactive, multi-turn operations. Unlike the previous three layers (which focus on data persistence), MCP State enables behavioral persistence—the ability for tools to pause mid-execution, collect user input, request LLM generations, and report progress.

The Critical Distinction

AgentCore State
MCP State
Files (Persistent Storage)
Tool execution context
Conversations (Short-Term Memory)
Interactive workflows
Knowledge (Long-Term Memory)
Suspended operations
What you persist
How you interact

The Three MCP Stateful Features

1. Elicitation 🔄 - Interactive User Input

Tools can pause execution to collect structured user input via forms.
Real-World Example: Exploring Workspace Projects
Python
from mcp import Context
from pydantic import BaseModel, Field

class ProjectQuestionsSchema(BaseModel):
"""Schema for collecting project clarification questions"""
purpose: str = Field(description="What is the main purpose of this project?")
tech_stack: str = Field(description="What technologies/frameworks does it use?")
status: str = Field(
description="What is the project status?",
examples=["Active", "Archived", "Prototype", "Production"]
)
priority: str = Field(
description="What is the priority level?",
examples=["High", "Medium", "Low"]
)

@mcp.tool()
async def ask_project_questions(ctx: Context, project_name: str) -> dict:
"""Ask clarifying questions about a project using elicitation."""

# Check if project exists in persistent storage
project_path = f"/mnt/workspace/{project_name}"
if not os.path.exists(project_path):
return {"status": "error", "message": f"Project {project_name} not found"}

# ⏸️ TOOL EXECUTION PAUSES HERE
result = await ctx.elicit(
message=f"Please provide information about project: {project_name}",
schema=ProjectQuestionsSchema
)

# ▶️ TOOL EXECUTION RESUMES after user responds
if result.action == "accept":
project_info = result.data # Validated project data

# Store project context in persistent storage
context_file = f"{project_path}/.project-context.json"
with open(context_file, "w") as f:
json.dump({
"project_name": project_name,
"purpose": project_info.purpose,
"tech_stack": project_info.tech_stack,
"status": project_info.status,
"priority": project_info.priority,
"questions_asked": True,
"timestamp": datetime.now().isoformat()
}, f)

return {
"status": "success",
"project": project_name,
"details": project_info,
"context_saved": context_file
}

return {"status": "cancelled"}
Execution Flow:
Plain text
1. User: "Ask me questions about project-a"
2. Tool starts → Checks project exists → Reaches ctx.elicit()
3. 🔒 Tool execution PAUSES (local variables preserved)
4. User sees form → Answers questions about purpose, tech, status, priority
5. 🔓 Tool execution RESUMES with validated answers
6. Tool saves context to /mnt/workspace/project-a/.project-context.json
7. Returns success with project details
Why MCP State Required:
  • ✅ Execution Suspension: The  await ctx.elicit()  preserves entire tool state
  • ✅ Context Preservation: Local variables ( session ) remain in memory
  • ✅ SSE Stream: HTTP connection stays open for response
  • ❌ Impossible in stateless mode: Tool would be destroyed when request completes

2. Sampling 🤖 - LLM-Powered Content

Tools can request LLM-generated content from the client, enabling server-side agents to leverage client-side AI.
Real-World Example: AI-Powered Project Insights
Python
@mcp.tool()
async def generate_project_insights(ctx: Context, project_name: str) -> dict:
"""Generate AI-powered project insights using client's LLM (reverse data flow)."""

# Read project files from AgentCore persistent storage
project_path = f"/mnt/workspace/{project_name}"

# Scan project structure
files = []
for root, dirs, filenames in os.walk(project_path):
files.extend([os.path.join(root, f) for f in filenames])

# Read key files (README, main code)
project_description = f"Project: {project_name}\n"
project_description += f"Total files: {len(files)}\n\n"

# Read README if exists
readme_path = os.path.join(project_path, "README.md")
if os.path.exists(readme_path):
with open(readme_path) as f:
project_description += f"README:\n{f.read()[:500]}\n\n"

# Sample a few Python files
python_files = [f for f in files if f.endswith('.py')][:3]
for py_file in python_files:
with open(py_file) as f:
project_description += f"File: {os.path.basename(py_file)}\n"
project_description += f"{f.read()[:300]}...\n\n"

# ⏸️ SERVER REQUESTS LLM FROM CLIENT (reverse flow!)
sampling_result = await ctx.session.create_message(
messages=[{
"role": "user",
"content": {
"type": "text",
"text": f"""You are a software architect assistant analyzing a codebase.

{project_description}

Provide:
1. Project purpose and architecture pattern
2. Technologies and frameworks used
3. Security concerns (if any)
4. Code quality assessment

Keep it concise (4-5 sentences)."""
}
}],
max_tokens=300
)

# ▶️ TOOL RESUMES with LLM-generated content
ai_insights = sampling_result.content.text

# Store insights in persistent storage
insights_file = f"{project_path}/.ai-insights.json"
with open(insights_file, "w") as f:
json.dump({
"project": project_name,
"insights": ai_insights,
"model": sampling_result.model,
"timestamp": datetime.now().isoformat()
}, f)

# Also logged in Short-Term Memory (conversation event)
return {
"status": "success",
"project": project_name,
"insights": ai_insights,
"model_used": sampling_result.model,
"saved_to": insights_file
}
Execution Flow:
Plain text
1. User: "Generate insights for project-a"
2. Server: Reads project files from /mnt/workspace → Builds analysis prompt
3. 🔄 Server → Client: Sampling request with project description
4. Client routes to LLM (Claude, Nova, GPT, etc.)
5. LLM analyzes code structure, identifies patterns, checks security
6. 🔄 Client → Server: Sampling response with insights
7. Server: Tool resumes → Saves to .ai-insights.json → Returns analysis
Why MCP State Required:
  • ✅ Request Correlation: Server must match response to waiting tool
  • ✅ Reverse Data Flow: Server has no LLM—must request from client
  • ✅ Multi-Turn Support: Advanced sampling can have tool use → results → completion
  • ❌ Impossible in stateless mode: No way to correlate response to tool

3. Progress Reporting 📊 - Real-Time Updates

Tools can report progress during long-running operations.
Real-World Example: Codebase Analysis
Python
@mcp.tool()
async def analyze_codebase(ctx: Context, project_path: str) -> dict:
"""Analyze codebase with real-time progress updates."""

# MCP progress: Report start
await ctx.report_progress(progress=0, total=100, message="Starting analysis")

# Read files from AgentCore persistent storage
files = []
for root, dirs, filenames in os.walk(f"/mnt/workspace/{project_path}"):
files.extend([os.path.join(root, f) for f in filenames if f.endswith('.py')])

await ctx.report_progress(progress=20, total=100, message=f"Found {len(files)} files")

# Analyze each file
issues = []
for i, filepath in enumerate(files):
with open(filepath) as f:
code = f.read()

# MCP sampling: Get LLM code review
review = await ctx.sample(
messages=f"Review this Python code for security issues:\n{code}",
max_tokens=300
)
issues.extend(review.issues)

# Report progress after each file
progress = 20 + int((i / len(files)) * 70)
await ctx.report_progress(
progress=progress,
total=100,
message=f"Analyzed {i+1}/{len(files)} files"
)

# Save results to persistent storage
with open(f"/mnt/workspace/{project_path}/.analysis.json", "w") as f:
json.dump({"issues": issues, "files_analyzed": len(files)}, f)

await ctx.report_progress(progress=100, total=100, message="Complete")

return {
"status": "success",
"issues_found": len(issues),
"files_analyzed": len(files)
}
Execution Flow:
Plain text
1. User: "Analyze the codebase"
2. Tool starts → Reports 0% progress
3. Tool scans files → Reports 20% progress
4. Tool analyzes file 1 → Reports 30% progress
5. Tool analyzes file 2 → Reports 40% progress
...
N. Tool completes → Reports 100% progress
Why MCP State Helpful:
  • ✅ User Feedback: Shows what's happening during long operations
  • ✅ Non-Blocking: User sees progress without blocking
  • ✅ Debugging: Helps identify where operations stall

Example Classification from Project Explorer:

Tool
Requires MCP State?
Why?
 list_projects 
❌ NO
Read-only operation, returns immediately
 add_project_context 
❌ NO
Simple CRUD, could use DB instead
 explore_project_files 
❌ NO
Executes command, returns result
 ask_project_questions 
✅ YES
ELICITATION - pauses for user input
 generate_project_insights 
✅ YES
SAMPLING - requests LLM content



How MCP State Integrates with AgentCore

The Model Context Protocol (MCP) adds another layer of state management, particularly for tool-level state and server-side context.

MCP Session State

When MCP servers run in stateful mode ( stateless_http=False ), they maintain session state:
Python
from mcp import Context

@mcp.tool()
async def plan_trip(ctx: Context, destination: str) -> str:
# ctx.session_id contains the MCP session identifier

# Elicitation: Ask user for input during tool execution
trip_type = await ctx.elicit(
message="What type of trip? (business/leisure/adventure)",
response_type=str
)

# Progress: Report long-running operation status
await ctx.report_progress(progress=1, total=5)

# Sampling: Request LLM-generated content
tips = await ctx.sample(
messages=f"Give 3 tips for {trip_type} trip to {destination}",
max_tokens=150
)

return f"Planned {trip_type} trip to {destination}"

MCP Stateful Features

Feature
Purpose
Requires State?
Elicitation
Server asks user for input during tool execution
✅ Yes
Sampling
Server requests LLM-generated content
✅ Yes
Progress
Real-time progress reporting
✅ Yes
Resources
Expose data via URIs ( travel://destinations )
❌ No (stateless)
Prompts
Reusable templates
❌ No (stateless)
Basic Tools
Function calls without state
❌ No (stateless)

The Overlap: MCP State + AgentCore Persistent Storage

Here's where it gets interesting—MCP state and AgentCore persistent storage complement each other:
Python
from mcp import Context
import json

@mcp.tool()
async def analyze_codebase(ctx: Context, project_path: str) -> str:
# MCP state: Track analysis progress
await ctx.report_progress(progress=0, total=100, message="Scanning files...")

# AgentCore persistent storage: Access project files
files = os.listdir(f"/mnt/workspace/{project_path}")

# MCP elicitation: Ask user for focus area
focus = await ctx.elicit(
message=f"Found {len(files)} files. What should I analyze?",
response_type=str
)

# AgentCore short-term memory: Store analysis results
analysis_result = {"files": files, "focus": focus, "issues": []}

# AgentCore persistent storage: Save analysis to file
with open(f"/mnt/workspace/{project_path}/.analysis.json", "w") as f:
json.dump(analysis_result, f)

# MCP sampling: Generate recommendations
recommendations = await ctx.sample(
messages=f"Suggest improvements for {focus} in codebase...",
max_tokens=200
)

return f"Analysis complete. Results saved to .analysis.json"
What's happening:
  1. MCP session state ( ctx.session_id ) tracks the tool execution context
  1. AgentCore persistent storage ( /mnt/workspace ) stores project files and analysis results
  1. MCP elicitation enables interactive user input mid-execution
  1. MCP progress reports long-running operation status
  1. MCP sampling generates LLM-powered content
  1. AgentCore short-term memory maintains conversation context
  1. AgentCore long-term memory extracts patterns from repeated analyses

State Persistence Comparison

State Type
Scope
Persistence
Use Case
MCP Session State
Single MCP tool execution
Until MCP session terminates
Multi-turn tool interactions, progress tracking
AgentCore Session Storage
Agent runtime session
14 days (survives stop/resume)
Project files, artifacts, dependencies
AgentCore Short-Term Memory
Conversation session
Session-scoped (configurable)
Conversation history, context for follow-ups
AgentCore Long-Term Memory
Cross-session knowledge
Persistent (vector DB)
Learned patterns, episodic reflections, facts



The Complete Picture: How All Four Layers Work Together

Let's walk through a real-world scenario to see how all four persistence layers collaborate:

Scenario: Multi-Day Software Development Project

Day 1 - Initial Setup:
Python
# User: "Create a FastAPI app with authentication"

# 1. AgentCore Runtime processes request
# 2. Agent uses tools to create files in /mnt/workspace/
# - main.py, auth.py, requirements.txt
# 3. Short-Term Memory stores conversation:
# "Created FastAPI app, added JWT auth"
# 4. Persistent Session Storage saves files to S3
# 5. Long-Term Memory extraction (background):
# Semantic: "User building FastAPI app"
# User Preference: "User prefers JWT authentication"
Day 2 - User Stops Session:
Python
client.stop_runtime_session(runtimeSessionId="project-abc")
# - Compute terminates
# - Files flush from /mnt/workspace to S3
# - Conversation state persisted (if using FileSessionManager)
Day 3 - User Resumes:
Python
# User: "Add rate limiting to the API"

# 1. AgentCore provisions new compute
# 2. Persistent Session Storage restores /mnt/workspace from S3:
# main.py, auth.py, requirements.txt, .venv/ all present
# 3. Short-Term Memory loaded from /mnt/workspace/.sessions/
# Agent knows context: "FastAPI app with JWT auth"
# 4. Agent executes task, creates rate_limiter.py
# 5. New conversation events added to Short-Term Memory
Day 4 - User Requests Code Review:
Python
# User: "Review the code and suggest improvements"

# MCP stateful tool execution:
@mcp.tool()
async def review_code(ctx: Context) -> str:
# Access files from AgentCore persistent storage
files = ["/mnt/workspace/main.py", "/mnt/workspace/auth.py"]

# MCP progress: Report review progress
await ctx.report_progress(progress=1, total=len(files))

# Analyze each file
issues = []
for file in files:
with open(file) as f:
code = f.read()
# MCP sampling: Get LLM code review
review = await ctx.sample(
messages=f"Review this code:\n{code}",
max_tokens=300
)
issues.append(review)
await ctx.report_progress(progress=len(issues), total=len(files))

# MCP elicitation: Ask which issues to fix first
priority = await ctx.elicit(
message="Found 5 issues. Which should I fix first?",
response_type=str
)

return f"Review complete. Focusing on: {priority}"
Day 5 - Learning from Multiple Projects:
Python
# After building 5 FastAPI projects, Long-Term Memory extraction:

# Episodic Memory generates cross-episode reflections:
{
"pattern": "FastAPI projects work best with",
"insights": [
"JWT auth + rate limiting combination (5/5 projects)",
"Pydantic models prevent bugs early (4/5 projects)",
"Docker deployment reduces setup time (5/5 projects)"
],
"learned_from": "5 episodes",
"confidence": "high"
}

# Next project, agent retrieves this episodic memory:
memories = retrieve_memory_records(query="FastAPI best practices")

# Agent proactively suggests:
"Based on past projects, I recommend starting with JWT auth,
rate limiting, Pydantic models, and Docker deployment."



Architecture Diagram Explained (4 Layers)

Let's decode the updated architecture diagram showing all four persistence layers:


Top: User Interaction

Plain text
User: "Build FastAPI app, learn from meetings"
↓
AgentCore Runtime (Session: session-123)
The entry point—user sends requests to AgentCore Runtime with a session ID.

Left: Persistent Session Storage (Green)

Plain text
Filesystem: /mnt/workspace
├── main.py
├── auth.py
├── requirements.txt
├── .venv/
├── .git/
└── node_modules/

Characteristics:
✓ Survives stop/resume
✓ 14-day retention
✓ Access: open(), ls, cat, git
✓ Backend: S3-backed filesystem
✓ Use case: Code, artifacts, state
Connection: Runtime performs File I/O to read/write files to persistent storage.

Top Right: Short-Term Memory (Blue)

Plain text
Raw Conversation Events:
- "Create FastAPI app..."
- "I've created main.py..."
- "Sprint planning 60min timebox"
- "Meeting completed on time"

Access:
- ListEvents() - Get conversation history
- Session-scoped, persists across restarts
Connection: Runtime creates events during conversation, stores in Short-Term Memory.

Bottom Right: Long-Term Memory (Purple)

Plain text
Multiple Extraction Strategies:

[Semantic]
Facts & Knowledge
- "User prefers Python"
- "Team has 5 developers"
- "FastAPI project"

[Summary]
Conversation Condensation
- "36-min pricing discussion"
- "Decided on Enterprise plan"

[User Preference]
Settings & Personalization
- "User wants dark mode"
- "Email digest at 9 AM"

[Episodic] ⭐
Experience Sequences
- "Sprint planning #1 - 90 min"
- "Sprint planning #2 - 60 min ✓"
- "Sprint planning #3 - 60 min ✓"

Reflections (Cross-Episode):
- "60-min timebox optimal"
- "Pre-estimate stories first"
- "Success rate: 90%"
- "Learns what works"

[Custom]
Domain-Specific Logic
- "Custom extraction patterns"

Memory Retrieval:
retrieve_memory_records(
query="sprint planning patterns"
)
Returns: Semantic facts + Episodic episodes + Reflections
✓ Semantic Search across strategies
✓ Cross-Session knowledge retrieval
✓ Includes episodic reflections!
Connection: Background Extraction Job processes Short-Term Memory, creates Long-Term Memory records.

Bottom Center: MCP State (Orange)

Plain text
MCP Session State:
- ctx.session_id: 'abc-12345'
- Tool execution state preserved
- Local variables saved
- SSE stream kept open

Stateful Features (REQUIRE MCP State):

🔄 Elicitation:
Interactive User Input
- Tool PAUSES at ctx.elicit()
- Requests user address/data
- Context preserved (cart, session)
- Tool RESUMES with validated data
✓ Execution suspension/resume
✓ Form-based collection

🤖 Sampling:
LLM-Powered Content
- Server builds prompt
- ctx.sample() requests LLM
- Tool PAUSES awaiting response
- Client routes to LLM
- Tool RESUMES with result
✓ Reverse data flow
✓ Request correlation

📊 Progress:
Real-Time Updates
- ctx.report_progress()
- progress=50, total=100
- User sees status
✓ Long-running operations
Connections:
  • MCP tools can read project files from Persistent Storage (dashed green)
  • MCP interactions (elicitation responses, sampling outputs) create Short-Term Memory events (dashed blue)
  • MCP State enables interactive behaviors impossible in stateless mode

Flow Summary (4 Layers)

Plain text
1. User → Runtime → Creates Events → Short-Term Memory
2. Runtime ↔ File I/O ↔ Persistent Session Storage
3. Short-Term Memory → Extraction Job → Long-Term Memory (4 strategies)
4. Long-Term Memory → retrieve_memory_records() → Runtime (context enrichment)
5. Runtime → MCP Tools → Elicitation/Sampling/Progress → Interactive Workflows



Deep Dive: MCP State Integration Patterns

Now that we understand all four layers, let's explore detailed integration patterns showing how MCP State works with the other three layers.

Integration Pattern 1: Elicitation + Persistent Storage

Plain text
User → AgentCore Runtime → Invokes MCP Tool
↓
MCP Server (Stateful Mode)
↓
ctx.session_id maintained
↓
┌─────────────────────┼─────────────────────┐
↓ ↓ ↓
ctx.elicit() ctx.sample() ctx.report_progress()
(User input) (LLM content) (Real-time updates)
↓ ↓ ↓
Response → Runtime → Short-Term Memory (conversation events)
Key insight: MCP state enables tool-level interactivity that feeds into AgentCore's Short-Term Memory, which later informs Long-Term Memory extraction.

Example: Episodic Learning + MCP State

Python
# Sprint planning tool with MCP state
@mcp.tool()
async def run_sprint_planning(ctx: Context, team_size: int) -> str:
# MCP elicitation: Ask for timebox
timebox = await ctx.elicit(
message="What timebox? (60min/90min/120min)",
response_type=str
)

# MCP progress: Track planning execution
await ctx.report_progress(progress=0, total=100)

# Execute planning (stored in persistent storage)
result = execute_planning(timebox, team_size)

# Store result in /mnt/workspace for persistence
with open("/mnt/workspace/.sprint-results.json", "w") as f:
json.dump(result, f)

await ctx.report_progress(progress=100, total=100)

return f"Sprint planning completed in {result['duration']}"

# After 5 sprint planning sessions:
# 1. MCP state tracked each execution (timebox choices, progress)
# 2. Short-Term Memory captured conversation events
# 3. Persistent Storage saved sprint results to files
# 4. Long-Term Memory (Episodic) extracted:
# "60-min timebox optimal (learned from 5 episodes)"



Best Practices: When to Use What

Use Persistent Session Storage When

  • ✅ Agent needs to persist files, code, artifacts
  • ✅ Installing dependencies (node_modules, .venv)
  • ✅ Multi-day projects requiring state between sessions
  • ✅ Git repositories, build outputs, logs
  • ✅ Anything that would normally be on disk
Example: Code generation, project exploration, analysis artifacts

Use Short-Term Memory When

  • ✅ Maintaining conversation context for follow-up questions
  • ✅ Agent needs to reference earlier messages
  • ✅ Multi-turn interactions within a session
  • ✅ "Remember what I asked 5 messages ago"
Example: Project exploration ("Tell me about project-a" after "List projects"), contextual assistance

Use Long-Term Memory When

  • ✅ Agent should learn patterns across sessions
  • ✅ Personalization based on user preferences
  • ✅ Episodic learning ("what worked in past projects")
  • ✅ Cross-session knowledge retrieval
Example: Personal assistants, learning systems, knowledge bases

Use MCP Stateful Features When

  • ✅ Tool needs to ask user questions during execution (elicitation)
  • ✅ Long-running operations require progress reporting
  • ✅ Tool needs LLM-generated content (sampling)
  • ✅ Multi-turn tool interactions
Example: Interactive tools, wizards, complex workflows



Code Example: Putting It All Together

Here's a complete example combining all four persistence mechanisms:
Python
from bedrock_agentcore.runtime import BedrockAgentCoreApp
from strands import Agent
from strands.session import FileSessionManager
from mcp import Context
import json
import os
from datetime import datetime

app = BedrockAgentCoreApp()
WORKSPACE = "/mnt/workspace"

# MCP stateful tools
@mcp.tool()
async def explore_and_analyze_project(ctx: Context, project_name: str) -> str:
"""Complete project exploration with all 4 persistence layers."""

# 1. Access AgentCore Persistent Storage (Layer 1)
project_path = f"{WORKSPACE}/{project_name}"
if not os.path.exists(project_path):
return f"Project {project_name} not found in workspace"

# 2. MCP Progress: Report start (Layer 4)
await ctx.report_progress(progress=0, total=100, message="Starting exploration")

# 3. Scan project files from persistent storage
files = []
for root, dirs, filenames in os.walk(project_path):
files.extend([os.path.join(root, f) for f in filenames])

await ctx.report_progress(progress=20, total=100, message=f"Found {len(files)} files")

# 4. MCP Elicitation: Ask user for clarification (Layer 4)
questions = await ctx.elicit(
message=f"Please provide details about {project_name}",
schema=ProjectQuestionsSchema # purpose, tech_stack, status, priority
)

await ctx.report_progress(progress=40, total=100, message="Questions answered")

# 5. Store project context in persistent storage (Layer 1)
context_file = f"{project_path}/.project-context.json"
with open(context_file, "w") as f:
json.dump({
"project_name": project_name,
"purpose": questions.data.purpose,
"tech_stack": questions.data.tech_stack,
"status": questions.data.status,
"priority": questions.data.priority,
"files_count": len(files),
"timestamp": datetime.now().isoformat()
}, f)

await ctx.report_progress(progress=60, total=100, message="Context saved")

# 6. MCP Sampling: Get LLM insights (Layer 4)
# Read sample files for analysis
sample_code = ""
python_files = [f for f in files if f.endswith('.py')][:3]
for py_file in python_files:
with open(py_file) as f:
sample_code += f"File: {os.path.basename(py_file)}\n{f.read()[:200]}...\n\n"

insights = await ctx.session.create_message(
messages=[{
"role": "user",
"content": {
"type": "text",
"text": f"""Analyze this {questions.data.tech_stack} project:

Purpose: {questions.data.purpose}
Status: {questions.data.status}
Files: {len(files)}

Sample code:
{sample_code}

Provide:
1. Architecture pattern
2. Security concerns
3. Improvement suggestions"""
}
}],
max_tokens=500
)

await ctx.report_progress(progress=80, total=100, message="Insights generated")

# 7. Save AI insights to persistent storage (Layer 1)
insights_file = f"{project_path}/.ai-insights.json"
with open(insights_file, "w") as f:
json.dump({
"project": project_name,
"insights": insights.content.text,
"model": insights.model,
"timestamp": datetime.now().isoformat()
}, f)

await ctx.report_progress(progress=100, total=100, message="Complete!")

# All interactions logged in Short-Term Memory (Layer 2) automatically
# Long-Term Memory (Layer 3) will extract patterns across explorations

return f"""Project exploration complete!

Context saved: {context_file}
Insights saved: {insights_file}

Files: {len(files)}
Tech: {questions.data.tech_stack}
Priority: {questions.data.priority}"""

# AgentCore entrypoint
@app.entrypoint
def handle_request(payload):
session_id = payload.get("session_id", "default")

# Short-Term Memory (Layer 2): Stores conversation in persistent storage
session_manager = FileSessionManager(
session_id=session_id,
storage_dir=f"{WORKSPACE}/.sessions"
)

# Long-Term Memory (Layer 3): Configure extraction strategies
memory_config = {
"strategies": ["semantic", "episodic", "user_preference", "summary"],
"episodic": {
"enable_reflections": True,
"reflection_threshold": 3 # Generate reflections after 3 explorations
}
}

agent = Agent(
model=model,
tools=[explore_and_analyze_project, list_projects, read_file, write_file],
session_manager=session_manager,
memory_config=memory_config,
system_prompt="""You are a workspace exploration assistant.

You have access to all 4 persistence layers:
- Layer 1: Persistent storage at /mnt/workspace (files, contexts, insights)
- Layer 2: Short-term memory (conversation history)
- Layer 3: Long-term memory (episodic learning from past explorations)
- Layer 4: MCP state (elicitation, sampling, progress)

Learn from past explorations and proactively suggest project insights."""
)

# Retrieve relevant long-term memories (Layer 3)
memories = retrieve_memory_records(
query=payload.get("prompt"),
strategies=["semantic", "episodic"]
)

# Inject memories into context
context = f"Past knowledge:\n{memories}\n\n{payload.get('prompt')}"

response = agent(context)
return {"response": response.message["content"][0]["text"]}

if __name__ == "__main__":
app.run()
What's happening (all 4 layers):
  1. Layer 1 - Persistent Storage: Project files, contexts, insights saved to  /mnt/workspace 
  1. Layer 2 - Short-Term Memory: Conversation history persisted to  .sessions/ 
  1. Layer 3 - Long-Term Memory: Extracts patterns like "User explores 12-15 projects/session"
  1. Layer 4 - MCP State: Elicitation (questions), Sampling (LLM insights), Progress (real-time updates)
  1. Integration: Elicitation responses → stored in filesystem, Sampling outputs → saved to files, All interactions → logged in STM



Performance Considerations

Persistent Session Storage

  • Latency: First write incurs ~100-200ms for S3 replication; subsequent writes cached
  • Throughput: Optimized for typical agent file I/O patterns (< 1GB/session)
  • Cost: Storage costs based on S3 Standard pricing (~$0.023/GB/month)

Short-Term Memory

  • In-Memory: Fastest, but lost on session stop
  • Filesystem: Slight latency (~10-50ms), persists with session storage
  • DynamoDB: ~50-100ms latency, enables cross-instance sharing

Long-Term Memory

  • Extraction: Background job, doesn't block conversation
  • Retrieval: Vector search ~50-200ms depending on corpus size
  • Storage: Vector embeddings + metadata in managed vector DB

MCP Stateful Features

  • Elicitation: Adds round-trip latency for user input (~1-5 seconds depending on user)
  • Progress: Minimal overhead (~1-5ms per update)
  • Sampling: Adds LLM inference latency (~500ms-3s depending on model)



Common Patterns and Anti-Patterns

✅ Do This

Pattern 1: Persist conversation with project files
Python
# Store session data alongside project in persistent storage
session_manager = FileSessionManager(
storage_dir=f"{WORKSPACE}/.sessions/{project_id}"
)
# Agent resumes with both files AND conversation context
Pattern 2: Use episodic memory for multi-project learning
Python
# After each project completion, extract episodic memory
extract_memory(strategies=["episodic"], session_id=session_id)
# Next project retrieves patterns: "Tests first reduces bugs 80%"
Pattern 3: MCP elicitation for ambiguous requests
Python
@mcp.tool()
async def deploy_app(ctx: Context, app_name: str):
# Ask for deployment target instead of guessing
target = await ctx.elicit(
message="Deploy to? (staging/production)",
response_type=str
)
# Explicit user confirmation for production deployments

❌ Don't Do This

Anti-Pattern 1: Storing large binaries in persistent storage
Python
# ❌ Don't store large ML models, datasets in /mnt/workspace
# Use S3 directly and cache locally:
boto3.client('s3').download_file('bucket', 'model.bin', '/tmp/model.bin')
Anti-Pattern 2: Relying on MCP state for long-term data
Python
# ❌ MCP session terminates when server restarts
# ✅ Use AgentCore persistent storage or long-term memory
with open(f"{WORKSPACE}/mcp-state.json", "w") as f:
json.dump(state, f)
Anti-Pattern 3: Not waiting for StopRuntimeSession
Python
# ❌ Don't resume immediately after stopping
client.stop_runtime_session(runtimeSessionId="session-123")
client.invoke_agent_runtime(runtimeSessionId="session-123") # Data loss risk!

# ✅ Wait for graceful shutdown
waiter = client.get_waiter('runtime_session_stopped')
waiter.wait(runtimeSessionId="session-123")
# Now safe to resume



Debugging and Observability

Inspect Persistent Storage

Bash
# SSH into agent container (development/debugging)
aws bedrock-agentcore debug-session --runtime-session-id session-123

# Inside container
ls -la /mnt/workspace
cat /mnt/workspace/.sessions/conv-001.json
git log # If using git in persistent storage

Monitor Memory Extraction

Python
# Check if extraction job completed
response = client.describe_memory_extraction(
sessionId="conv-001",
extractionJobId="job-abc"
)
print(response['status']) # COMPLETED, FAILED, IN_PROGRESS

View MCP Session State

Bash
# MCP server logs show session lifecycle
INFO: MCP session session-xyz initialized
INFO: Elicitation request: "Deploy to staging or production?"
INFO: Progress update: 50/100
INFO: Session session-xyz terminated

Long-Term Memory Queries

Python
# Query episodic reflections
memories = retrieve_memory_records(
query="what deployment patterns work best",
strategies=["episodic"],
include_reflections=True
)

for memory in memories:
print(f"Pattern: {memory['content']}")
print(f"Learned from: {memory['metadata']['episode_count']} episodes")
print(f"Confidence: {memory['metadata']['confidence']}")



Conclusion: The Power of Four-Layer Persistence

Amazon Bedrock AgentCore's four-layer persistence architecture enables agents to:
  1. Persist complex state (files, projects, dependencies) across days/weeks via Persistent Session Storage
  1. Maintain conversation context for natural follow-up interactions via Short-Term Memory
  1. Learn from experience through episodic reflections across sessions via Long-Term Memory
  1. Execute interactive workflows with user input, LLM sampling, and progress reporting via MCP State
The key insight is that these layers complement each other to cover different persistence needs:
Layer
Purpose
Scope
Example
Persistent Storage
Files & artifacts
14 days
Code, .venv/, build outputs
Short-Term Memory
Conversation context
Session
"Add auth" knows about "Create API"
Long-Term Memory
Accumulated knowledge
Cross-session
"60-min sprints work best" (learned from 5 episodes)
MCP State
Tool interactivity
Tool execution
Pause for address, request LLM summary
Together, they enable agents to truly "remember" across four dimensions:
  1. Filesystem state (what files exist)
  1. Conversation history (what was discussed)
  1. Learned patterns (what works)
  1. Interactive workflows (how to collect input and generate content)

Why MCP State Is the Missing Piece

Without MCP State, agents are limited to:
  • ❌ Fire-and-forget tool calls (no interactivity)
  • ❌ All data collected upfront (no progressive refinement)
  • ❌ No access to client-side AI capabilities (no sampling)
With MCP State, agents can:
  • ✅ Collect sensitive information securely (elicitation)
  • ✅ Leverage client-side LLMs for generation (sampling)
  • ✅ Provide real-time feedback during long operations (progress)
  • ✅ Build sophisticated multi-turn workflows

Next Steps




AWS Documentation

Model Context Protocol (MCP)

Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article