Session Management
Session management in Strands Agents provides a robust mechanism for persisting agent state and conversation history across multiple interactions. This enables agents to maintain context and continuity even when the application restarts or when deployed in distributed environments.
Overview
Section titled “Overview”A session represents all of stateful information that is needed by agents and multi-agent systems to function, including:
Single Agent Sessions:
- Conversation history (messages)
- Agent state (key-value storage)
- Other stateful information (like Conversation Manager)
Multi-Agent Sessions:
- Orchestrator state and configuration
- Individual agent states and result within the orchestrator
- Cross-agent shared state and context
- Execution flow and node transition history
Strands provides built-in session persistence capabilities that automatically capture and restore this information, allowing agents to seamlessly continue conversations where they left off.
Beyond the built-in options, third-party session managers provide additional storage and memory capabilities.
Basic Usage
Section titled “Basic Usage”Single Agent Sessions
Section titled “Single Agent Sessions”Simply create an agent with a session manager and use it:
from strands import Agentfrom strands.session import SnapshotSessionManagerfrom strands.storage import LocalFileStorage
# Create a snapshot session manager with a unique session IDsession_manager = SnapshotSessionManager( session_id="test-session", storage=LocalFileStorage("./sessions/"),)
# Create an agent with the session manageragent = Agent(session_manager=session_manager)
# Use the agent - all messages and state are automatically persistedagent("Hello!") # This conversation is persistedSessionManager implements both Plugin (for agents) and MultiAgentPlugin (for orchestrators). The sessionManager constructor field is a convenience shorthand — you can also pass it directly in the plugins array:
const session = new SessionManager({ sessionId: 'test-session', storage: new LocalFileStorage('./sessions/'),})
const agent = new Agent({ sessionManager: session })
// Use the agent - all messages and state are automatically persistedawait agent.invoke('Hello!') // This conversation is persistedconst session = new SessionManager({ sessionId: 'test-session', storage: new LocalFileStorage('./sessions/'),})
// Equivalent to passing via sessionManager fieldconst agent = new Agent({ plugins: [session] })await agent.invoke('Hello!')The conversation, and associated state, is persisted to the underlying storage backend.
FileSessionManager and S3SessionManager remain supported in Python, but use
SnapshotSessionManager for new single-agent sessions.
Multi-Agent Sessions
Section titled “Multi-Agent Sessions”Multi-agent systems (Graph/Swarm) can also use session management to persist their state.
from strands import Agentfrom strands.multiagent import GraphBuilderfrom strands.session import FileSessionManager
# Create agentsagent1 = Agent(name="researcher")agent2 = Agent(name="writer")
# Create a repository-based session manager for the graphsession_manager = FileSessionManager(session_id="multi-agent-session")
# Build the graph with session management on the orchestratorbuilder = GraphBuilder()builder.add_node(agent1, "researcher")builder.add_node(agent2, "writer")builder.add_edge("researcher", "writer")builder.set_session_manager(session_manager)graph = builder.build()
result = graph("Research and write about AI")const session = new SessionManager({ sessionId: 'graph-session', storage: new LocalFileStorage('./sessions/'),})
const researcher = new Agent({ id: 'researcher', systemPrompt: 'You are a research specialist.',})const writer = new Agent({ id: 'writer', systemPrompt: 'You are a writing specialist.',})
const graph = new Graph({ nodes: [researcher, writer], edges: [['researcher', 'writer']], sessionManager: session,})
// Orchestrator state is automatically persisted after each node completesconst result = await graph.invoke('Research and write about AI')Swarm works the same way:
const session = new SessionManager({ sessionId: 'swarm-session', storage: new LocalFileStorage('./sessions/'),})
const researcher = new Agent({ id: 'researcher', description: 'Researches a topic and gathers key facts.', systemPrompt: 'Research the answer, then hand off to the writer.',})
const writer = new Agent({ id: 'writer', description: 'Writes a polished final answer.', systemPrompt: 'Write the final answer. Do not hand off.',})
const swarm = new Swarm({ nodes: [researcher, writer], start: 'researcher', sessionManager: session,})
const result = await swarm.invoke('Explain quantum computing')Multi-agent session managers only track the current state of the Graph/Swarm execution and do not persist individual agent conversation histories.
Storage Backends
Section titled “Storage Backends”Snapshot-based session managers accept any Storage backend, including
InMemoryStorage, LocalFileStorage, S3Storage, and custom implementations. See
Storage for backend configuration, tradeoffs, custom backends, and
required S3 permissions.
Pass storage to SnapshotSessionManager or to the agent. Manager-level storage takes
precedence over agent-level storage. If neither provides storage,
SnapshotSessionManager uses LocalFileStorage("./.strands/").
Repository-based managers configure storage directly through FileSessionManager,
S3SessionManager, or a custom SessionRepository. They do not use the unified Storage
backend.
Pass storage to SessionManager or to the agent. Manager-level storage takes precedence
over agent-level storage. If neither provides storage, initialization fails.
How Session Management Works
Section titled “How Session Management Works”Snapshot-based Session Managers
Section titled “Snapshot-based Session Managers”SnapshotSessionManager in Python and SessionManager in TypeScript persist a complete
point-in-time snapshot. Both restore snapshot_latest during initialization and support
immutable checkpoints.
Single Agent Events
- Agent Initialization: Restores state from
snapshot_latestif it exists. - Message Addition (
save_latest_on="message"): Saves after each message and again when the invocation ends. - Agent Invocation (
save_latest_on="invocation", default): Saves when the invocation ends. - Snapshot Trigger: Creates an immutable checkpoint when
snapshot_triggerreturnsTrue. - Message Redaction: Flushes redacted content to the latest snapshot under every strategy.
- Agent Initialization: Restores state from
snapshot_latestif it exists. - Message Addition (
saveLatestOn: 'message'): Saves after each message and again when the invocation ends. - Agent Invocation (
saveLatestOn: 'invocation', default): Saves when the invocation ends. - Snapshot Trigger: Creates an immutable checkpoint when
snapshotTriggerreturnstrue. - Message Redaction: Saves redacted content for the
messageandinvocationstrategies, but not fortrigger.
See Basic Usage for configuration examples.
Multi-Agent Events
SnapshotSessionManager does not support Graph or Swarm. Use a repository-based manager
for Python multi-agent persistence.
- Before Multi-Agent Invocation: Restores orchestrator state from
snapshot_lateston the first invocation. - After Node Call (
multiAgentSaveLatestOn: 'node', default): Saves after each node and again when the invocation ends. - After Multi-Agent Invocation (
multiAgentSaveLatestOn: 'invocation'): Saves only when the full invocation ends.
const session = new SessionManager({ sessionId: 'my-session', storage: new LocalFileStorage('./sessions/'), // Save orchestrator state after each node completes (default) multiAgentSaveLatestOn: 'node', // Or save only after the full orchestrator invocation completes: // multiAgentSaveLatestOn: 'invocation',})Repository-based Session Managers
Section titled “Repository-based Session Managers”FileSessionManager, S3SessionManager, and RepositorySessionManager store individual
message records and agent metadata. Use them for existing repository-format sessions,
Graph, Swarm, or bidirectional streaming.
SnapshotSessionManager remains the recommended option for new single-agent sessions.
Use the repository-based managers when you need one of the compatibility cases above:
from strands import Agentfrom strands.session import FileSessionManager, S3SessionManager
file_session_manager = FileSessionManager( session_id="file-session", storage_dir="./sessions/",)file_agent = Agent(session_manager=file_session_manager)
s3_session_manager = S3SessionManager( session_id="s3-session", bucket="my-agent-sessions", prefix="production/",)s3_agent = Agent(session_manager=s3_session_manager)- Agent Initialization: Restores stored messages, agent state, conversation manager state, interrupt state, and model state.
- Message Addition: Appends the message record and synchronizes changed agent state.
- Agent Invocation: Synchronizes changed agent and conversation manager state.
- Message Redaction: Updates the latest stored message record.
- Multi-Agent and Bidirectional Events: Restore state during initialization and synchronize state after node, message, and invocation events.
Immutable Snapshots
Section titled “Immutable Snapshots”In addition to snapshot_latest, snapshot-based session managers support immutable
snapshots. Strands assigns each append-only checkpoint a UUIDv7 identifier, which lets
you restore the agent to any prior checkpoint instead of only the latest state.
Creating Immutable Snapshots
Section titled “Creating Immutable Snapshots”Use the snapshot trigger callback to control when an immutable snapshot is created. The callback receives the current agent data and returns a boolean:
from strands import Agentfrom strands.session import SnapshotSessionManagerfrom strands.storage import LocalFileStorage
session_manager = SnapshotSessionManager( session_id="my-session", storage=LocalFileStorage("./sessions/"), snapshot_trigger=lambda *, agent_data, **_: len(agent_data.messages) % 4 == 0,)
agent = Agent(session_manager=session_manager)const session = new SessionManager({ sessionId: 'my-session', storage: new LocalFileStorage('./sessions/'), // Create an immutable snapshot after every 4 messages snapshotTrigger: ({ agentData }) => agentData.messages.length % 4 === 0,})
const agent = new Agent({ sessionManager: session })await agent.invoke('First message') // 2 messages — no snapshotawait agent.invoke('Second message') // 4 messages — immutable snapshot createdListing and Restoring Snapshots
Section titled “Listing and Restoring Snapshots”Snapshot IDs are UUID v7, so they sort lexicographically in chronological order. Use
list_snapshot_idslistSnapshotIdssnapshot_idsnapshotIdrestore_snapshotrestoreSnapshot
import asyncio
from strands import Agentfrom strands.session import SnapshotSessionManagerfrom strands.storage import LocalFileStorage
session_manager = SnapshotSessionManager( session_id="my-session", storage=LocalFileStorage("./sessions/"),)agent = Agent(session_manager=session_manager)
async def restore_snapshot() -> None: snapshot_id = await session_manager.save_snapshot(agent, is_latest=False) assert snapshot_id is not None snapshot_ids = await session_manager.list_snapshot_ids(agent) assert snapshot_id in snapshot_ids await session_manager.restore_snapshot(agent, snapshot_id=snapshot_id)
asyncio.run(restore_snapshot())const storage = new LocalFileStorage('./sessions/')
const session = new SessionManager({ sessionId: 'my-session', storage,})const agent = new Agent({ sessionManager: session })await agent.initialize()
// List all immutable snapshot IDs (chronological order)const snapshotIds = await session.listSnapshotIds({ target: agent,})
// Restore agent to a specific checkpointawait session.restoreSnapshot({ target: agent, snapshotId: snapshotIds[0]!,})Deleting Sessions
Section titled “Deleting Sessions”To remove all snapshots for a session, call the session manager’s delete method:
import asyncio
from strands.session import SnapshotSessionManagerfrom strands.storage import LocalFileStorage
session_manager = SnapshotSessionManager( session_id="my-session", storage=LocalFileStorage("./sessions/"),)asyncio.run(session_manager.delete_session())const session = new SessionManager({ sessionId: 'my-session', storage: new LocalFileStorage('./sessions/'),})
// Remove all snapshots and manifests for this sessionawait session.deleteSession()Data Models
Section titled “Data Models”SnapshotSessionManager stores a versioned Snapshot JSON object. Its data field
contains managed messages, agent state, conversation manager state, interrupt state,
model state, and the system prompt.
The following record-based models apply to FileSessionManager, S3SessionManager, and
RepositorySessionManager:
Session
The Session model is the top-level container for session data:
- Purpose: Provides a namespace for organizing multiple agents and their interactions
- Key Fields:
session_id: Unique identifier for the sessionsession_type: Type of session (currently"AGENT"for both agent & multiagent in order to keep backward compatibility)created_at: ISO format timestamp of when the session was createdupdated_at: ISO format timestamp of when the session was last updated
SessionAgent
The SessionAgent model stores agent-specific data:
- Purpose: Maintains the state and configuration of a specific agent within a session
- Key Fields:
agent_id: Unique identifier for the agent within the sessionstate: Dictionary containing the agent’s state data (key-value pairs)conversation_manager_state: Dictionary containing the state of the conversation managercreated_at: ISO format timestamp of when the agent was createdupdated_at: ISO format timestamp of when the agent was last updated
SessionMessage
The SessionMessage model stores individual messages in the conversation:
- Purpose: Preserves the conversation history with support for message redaction
- Key Fields:
message: The original message content (role, content blocks)redact_message: Optional redacted version of the message (used when sensitive information is detected)message_id: Index of the message in the agent’s messages arraycreated_at: ISO format timestamp of when the message was createdupdated_at: ISO format timestamp of when the message was last updated
These data models work together to provide a complete representation of an agent’s state and conversation history. The session management system handles serialization and deserialization of these models, including special handling for binary data using base64 encoding.
Multi-Agent State
Multi-agent systems serialize their state as JSON objects containing:
- Orchestrator Configuration: Settings, parameters, and execution preferences
- Node State: Current execution state and node transition history
- Shared Context: Cross-agent shared state and variables
The TypeScript SDK stores session state as a Snapshot object written to JSON. Each snapshot contains:
data.messages: The full conversation historydata.state: Agent key-value statedata.systemPrompt: The agent’s system promptschemaVersion: Schema version for forward compatibilitycreatedAt: ISO 8601 timestamp
There are two kinds of snapshots:
snapshot_latest.json: A single mutable file overwritten on each save. Used to resume the most recent state after a restart.- Immutable snapshots (
immutable_history/snapshot_<uuid7>.json): Append-only checkpoints created whensnapshotTriggerfires. Used for time-travel restore.
Third-Party Session Managers
Section titled “Third-Party Session Managers”The following third-party session managers extend Strands with additional storage and memory capabilities:
| Session Manager | Provider | Description | Documentation |
|---|---|---|---|
| AgentCoreMemorySessionManager | Amazon | Advanced memory with intelligent retrieval using Amazon Bedrock AgentCore Memory. Supports both short-term memory (STM) and long-term memory (LTM) with strategies for user preferences, facts, and session summaries. | View Documentation |
| Contribute Your Own | Community | Have you built a session manager? Share it with the community! | Learn How |
Custom Session Repositories
Section titled “Custom Session Repositories”For advanced use cases, you can implement your own session storage backend.
For new single-agent sessions, implement the unified
Storage protocol and pass it to
SnapshotSessionManager.
For repository-based sessions, create a custom session repository by implementing the
SessionRepository interface:
from typing import Optionalfrom strands import Agentfrom strands.session.repository_session_manager import RepositorySessionManagerfrom strands.session.session_repository import SessionRepositoryfrom strands.types.session import Session, SessionAgent, SessionMessage
class CustomSessionRepository(SessionRepository): """Custom session repository implementation."""
def __init__(self): """Initialize with your custom storage backend.""" # Initialize your storage backend (e.g., database connection) self.db = YourDatabaseClient()
def create_session(self, session: Session) -> Session: """Create a new session.""" self.db.sessions.insert(asdict(session)) return session
def read_session(self, session_id: str) -> Optional[Session]: """Read a session by ID.""" data = self.db.sessions.find_one({"session_id": session_id}) if data: return Session.from_dict(data) return None
# Implement other required methods... # create_agent, read_agent, update_agent # create_message, read_message, update_message, list_messages
# Use your custom repository with RepositorySessionManagercustom_repo = CustomSessionRepository()session_manager = RepositorySessionManager( session_id="user-789", session_repository=custom_repo)
agent = Agent(session_manager=session_manager)The simplest approach is to pass any
Storage backend directly — the
SessionManager wraps it automatically. For full control, you
can implement the SnapshotStorage interface:
// Implement SnapshotStorage to plug in any backendclass MyStorage implements SnapshotStorage { async saveSnapshot({ location, snapshotId, snapshot, }: { location: SnapshotLocation snapshotId: string isLatest: boolean snapshot: Snapshot }) { // Store the snapshot JSON keyed by location + snapshotId }
async loadSnapshot({ location, snapshotId, }: { location: SnapshotLocation snapshotId?: string }) { // Return the snapshot, or null if not found return null }
async listSnapshotIds({ location, }: { location: SnapshotLocation limit?: number startAfter?: string }) { // Return immutable snapshot IDs sorted chronologically return [] }
async deleteSession({ sessionId }: { sessionId: string }) { // Remove all stored data for this session }
async loadManifest({ location, }: { location: SnapshotLocation }): Promise<SnapshotManifest> { return { schemaVersion: '1', updatedAt: new Date().toISOString(), } }
async saveManifest({ location, manifest, }: { location: SnapshotLocation manifest: SnapshotManifest }) { // Persist the manifest }}
const agent = new Agent({ sessionManager: new SessionManager({ sessionId: 'user-789', storage: { snapshot: new MyStorage() }, }),})This approach allows you to store session data in any backend system while leveraging the built-in session management logic.
Data Layout
Section titled “Data Layout”Both file and S3 backends use the same key structure:
Snapshot-based layout (recommended for new single-agent sessions):
<root>/└── session/ └── <session_id>/ └── scopes/ └── agent/ └── <agent_id>/ └── snapshots/ ├── snapshot_latest.json └── immutable_history/ └── snapshot_<uuid7>.jsonRepository-based layout:
<root>/└── session_<session_id>/ ├── session.json ├── agents/ │ └── agent_<agent_id>/ │ ├── agent.json │ └── messages/ │ ├── message_0.json │ └── message_1.json └── multi_agents/ └── multi_agent_<orchestrator_id>/ └── multi_agent.json<root>/└── <sessionId>/ └── scopes/ ├── agent/ │ └── <agentId>/ │ └── snapshots/ │ ├── snapshot_latest.json │ └── immutable_history/ │ └── snapshot_<uuid7>.json └── multiAgent/ └── <orchestratorId>/ └── snapshots/ └── snapshot_latest.jsonUsing the same session ID and storage location does not migrate repository-based Python
data to SnapshotSessionManager. Existing sessions can continue using their current
manager, or applications can migrate the required state explicitly.
Sessions, Agents, and Concurrency
Section titled “Sessions, Agents, and Concurrency”Session management is designed around a single live writer per conversation: the session ID plus the agent ID (agent_idid
One Conversation per Session
Section titled “One Conversation per Session”Give each conversation its own session ID. Several agents can share one session ID as long as their agent IDs differ: the session acts as a namespace, and each agent keeps its own messages and state inside it. A single agent instance processes one invocation at a time by default and rejects overlap, as described in Concurrent Invocations.
Create an Agent per Conversation
Section titled “Create an Agent per Conversation”Constructing an agent is cheap: it wires up tools, hooks, and plugins locally and makes no model call. Build one per request, invoke it, and let it go out of scope. The model provider is the part worth reusing: providers such as BedrockModel build their client in the constructor, so create the provider once per process and pass the same instance to every agent.
Common Failure Modes
Section titled “Common Failure Modes”The built-in session managers take no distributed lock, and the single-instance invocation guard is in-process, so neither can detect a second writer running elsewhere. Two patterns result:
- Two live agents addressing the same session ID and agent ID. The default IDs (
) make this easy to do by accident, including across separate executions that each build their own agent. Overlapping invocations overwrite each other’s turns, sequential ones merge two conversations into one history, and neither call errors.agent_id="default"id: 'agent' - Two callers creating the same session at the same time. Session creation is a check followed by a write, not an atomic operation, so simultaneous cold starts on a new session ID can both succeed, with the later write winning.
Session Persistence Best Practices
Section titled “Session Persistence Best Practices”When implementing session persistence in your applications, consider these best practices:
- Use Unique Session IDs: Generate unique session IDs for each user or conversation context to prevent data overlap.
- Session Cleanup: Implement a strategy for cleaning up old or inactive sessions. Consider adding TTL (Time To Live) for sessions in production environments.
- Understand Persistence Triggers: Remember that changes to agent state or messages are only persisted during specific lifecycle events.
- Concurrent Access: Session managers are not thread-safe and take no distributed lock. See Sessions, Agents, and Concurrency.
- Secure Storage Directories: The session storage directory is a trusted data store. Restrict filesystem permissions so that only the agent process can read and write to it. In shared or multi-tenant environments (shared volumes, containers), be aware that the SDK does not block symlinks in the session storage directory. If an attacker with write access to the storage directory creates a symlink (e.g.,
message_0.jsonpointing to an arbitrary file), the SDK will follow it, which could cause sensitive file contents to be loaded into the agent’s conversation history.