Skip to content
Open
Show file tree
Hide file tree
Changes from 41 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
40932f6
docs(session): add migration guide from message-log managers to Snaps…
Oct 1, 2026
765b2d3
docs(session): emphasize why to migrate and what to watch when portin…
Oct 2, 2026
8bc5666
docs(session): drop the Looking ahead note from the migration guide
Oct 2, 2026
e828392
docs(session): reframe the constructor swap as a valid choice, not a …
Oct 2, 2026
4f0dbd6
docs(session): make the before/after swap snippet parallel
Oct 2, 2026
b2d513c
docs(session): tighten the migration guide to reference-doc register
Oct 2, 2026
b5c83ba
docs(session): fold verify/cut-over into the running-the-migration list
Oct 2, 2026
92955a4
docs(session): add the why-migrate rationale and clarify the snapshot…
Oct 2, 2026
02f8286
docs: reframe session manager guidance to recommend SnapshotSessionMa…
Oct 2, 2026
8af9e08
docs: flag the migration reference script as a minimal, extend-for-co…
Oct 2, 2026
a4cf4ef
Merge branch 'main' into agent-tasks/3663-session-migration-guide
nicolas-m-romero Oct 2, 2026
6b66a79
docs: clarify multi-agent migration limitations
Oct 5, 2026
63724bd
Apply suggestion from @strandly-the-agent
nicolas-m-romero Oct 5, 2026
8513a67
docs: clarify legacy migration source mutations
Oct 5, 2026
62e8856
docs: separate prompt and model migration guidance
Oct 5, 2026
0f1df60
docs: document Bidi restart snapshots
Oct 5, 2026
09a9059
Update site/src/content/docs/user-guide/sdk/agents/session-management…
nicolas-m-romero Oct 5, 2026
cfb79d9
docs: define migration prompt placeholder
Oct 5, 2026
54a4911
Merge branch 'main' into agent-tasks/3663-session-migration-guide
nicolas-m-romero Oct 5, 2026
69472f9
Merge branch 'main' into agent-tasks/3663-session-migration-guide
nicolas-m-romero Oct 6, 2026
7630259
Merge branch 'main' into agent-tasks/3663-session-migration-guide
nicolas-m-romero Oct 7, 2026
72d16cc
Merge branch 'main' into agent-tasks/3663-session-migration-guide
nicolas-m-romero Oct 13, 2026
ba12ce3
docs: remove em dashes from session migration guide
Oct 13, 2026
44fcbe4
docs: align migration section on repository-based terminology
Oct 13, 2026
84d7d0d
Merge branch 'main' into agent-tasks/3663-session-migration-guide
nicolas-m-romero Oct 13, 2026
8b352b7
docs(session): separate repository migration guide
Oct 20, 2026
a6a3473
feat(session): deprecate repository-based managers
Oct 20, 2026
46d6bf5
docs(session): retain deprecated manager guidance
Oct 21, 2026
908293a
Merge remote-tracking branch 'origin/main' into agent-tasks/3663-sess…
Oct 21, 2026
9bed83f
fix(session): avoid deprecation warning on subclassing
Oct 27, 2026
b83cb01
feat(session): deprecate session repository interface
Oct 27, 2026
c14ac88
docs(session): use sdk deprecation docstring style
Oct 27, 2026
c94325f
docs(session): link migration guide from api docs
Oct 27, 2026
b38544d
Merge branch 'main' into agent-tasks/3663-session-migration-guide
nicolas-m-romero Oct 28, 2026
ccf0cea
Merge branch 'main' into agent-tasks/3663-session-migration-guide
nicolas-m-romero Oct 28, 2026
d4d5ea3
Merge remote-tracking branch 'origin/main' into agent-tasks/3663-sess…
Oct 29, 2026
901bd78
Merge branch 'main' into agent-tasks/3663-session-migration-guide
nicolas-m-romero Oct 30, 2026
e3ae8b6
Merge branch 'main' into agent-tasks/3663-session-migration-guide
nicolas-m-romero Nov 2, 2026
ee7c40b
Merge branch 'main' into agent-tasks/3663-session-migration-guide
nicolas-m-romero Nov 2, 2026
27afc23
docs(session): accept stateful-model sessions in migration guard
Nov 2, 2026
08a641e
docs: steer remaining examples to SnapshotSessionManager
Nov 2, 2026
a8a2785
Merge branch 'main' into agent-tasks/3663-session-migration-guide
nicolas-m-romero Nov 3, 2026
fcd85ab
docs(session): check migrated data through take_snapshot in guard
Nov 4, 2026
b4b5e69
test(site): expect migration guide in Sessions sidebar group
Nov 4, 2026
d8787c1
docs(session): scope the migration script to one session at a time
Nov 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion harness-py/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,7 @@ agent("Where did we leave off?")
```

Set `session={"dir": ...}` to choose where state lives (default `./.agent/sessions`). For
multi-host setups, pass your own `SessionManager` instance (for example `S3SessionManager`) as `session`
multi-host setups, pass your own `SessionManager` instance (for example `SnapshotSessionManager` with `S3Storage`) as `session`
and it takes over. Turn persistence off with `session=False` for a throwaway run.

The default manager saves after each completed message, including tool results, so an interrupted
Expand Down
2 changes: 2 additions & 0 deletions site/src/config/navigation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,8 @@ sidebar:
items:
- label: "Persist state across sessions"
slug: docs/user-guide/sdk/agents/session-management
- label: "Migrate repository-based sessions"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Critical (CI blocker)

Issue: Adding this sidebar entry breaks Docs / CI. site/test/sidebar.test.ts:192 asserts that the Sessions group contains exactly one item, so it now fails with expected [ {…}, {…} ] to deeply equal [ {…} ]. This failed on 08a641e and I reproduced it locally on a8a2785 (npx vitest run test/sidebar.test.ts: 1 failed, 9 passed).

Suggestion: Update the expectation in site/test/sidebar.test.ts to include the new page. With this change the file passes locally (10/10):

    expect(groups[1]?.items).toEqual([
      { label: 'Persist state across sessions', slug: 'docs/user-guide/sdk/agents/session-management' },
      { label: 'Migrate repository-based sessions', slug: 'docs/user-guide/sdk/agents/migrate-repository-sessions' },
    ])

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in b4b5e69: the expectation now includes the migration guide, and npx vitest run test/sidebar.test.ts passes 10/10 locally. Docs / CI is still running on this head. This thread can be resolved once it's green.

slug: docs/user-guide/sdk/agents/migrate-repository-sessions
- label: Memory
items:
- label: "Overview"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,67 +24,21 @@ notices below.

Session managers persist conversation history and agent state across runs. Without them, everything resets when the process stops. They're implemented as hook providers, so persistence is just another harness behavior layered in through hooks.

## File session manager (record-based)

Run these examples from `samples/09-persistent-memory`.

The record-based approach persists each message individually. It's still supported for existing sessions and available for existing record-based Graph/Swarm sessions.

```python
from strands import Agent
from strands.session.file_session_manager import FileSessionManager

from customer_service_tools import lookup_customer, get_order_history, process_refund

SYSTEM_PROMPT = "Help customers with their orders using the available tools."

session_manager = FileSessionManager(
session_id="customer-session-001",
storage_dir="./sessions",
)

agent = Agent(
tools=[lookup_customer, get_order_history, process_refund],
system_prompt=SYSTEM_PROMPT,
context_manager="auto",
session_manager=session_manager,
)
```

📂 [file_session_manager.py](https://github.com/aws-samples/sample-building-with-strands-course/tree/main/samples/09-persistent-memory/file_session_manager.py)

## S3 session manager (record-based)

Same record-based approach, backed by S3 instead of local disk:

```python
from strands import Agent
from strands.session.s3_session_manager import S3SessionManager

session_manager = S3SessionManager(
session_id="customer-session-001",
bucket="my-agent-sessions",
prefix="customer-service/",
region_name="us-east-1",
)

agent = Agent(session_manager=session_manager)
```

📂 [s3_session_manager.py](https://github.com/aws-samples/sample-building-with-strands-course/tree/main/samples/09-persistent-memory/s3_session_manager.py)

## Snapshot session manager

<CourseNotice title="" variant="addition">

The `FileSessionManager` and `S3SessionManager` examples shown in the video are
still valid. `SnapshotSessionManager` is an optional alternative for saving and
restoring a conversation. You do not need to switch to follow this lesson.
The video shows `FileSessionManager` and `S3SessionManager`. These record-based
managers are deprecated and will be removed in v2.0.0. Use `SnapshotSessionManager`
as shown below. To keep existing record-based sessions, see
[Migrate repository-based sessions](/docs/user-guide/sdk/agents/migrate-repository-sessions/).

See the [session management docs](/docs/user-guide/sdk/agents/session-management/) for setup and examples.

</CourseNotice>

Run these examples from `samples/09-persistent-memory`.

`SnapshotSessionManager` saves the session state together, instead of saving each
message as a separate record.

Expand Down Expand Up @@ -125,13 +79,8 @@ session_manager = SnapshotSessionManager(

📂 [snapshot_session_manager.py](https://github.com/aws-samples/sample-building-with-strands-course/tree/main/samples/09-persistent-memory/snapshot_session_manager.py)

## When to use which

| Manager | Best For |
|---|---|
| `SnapshotSessionManager` | Save and restore a single agent, Graph/Swarm, or Bidi session. |
| `FileSessionManager` | Existing record-based sessions; retains their storage format. |
| `S3SessionManager` | Same as File but with S3 for cross-machine access. |
`SnapshotSessionManager` saves and restores a single agent, a Graph or Swarm, or a
Bidi session.

## Sessions, memory, and storage

Expand Down
6 changes: 3 additions & 3 deletions site/src/content/docs/user-guide/migrate/langgraph.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -272,12 +272,12 @@ graph:

```python
builder.set_session_manager(
FileSessionManager(session_id=case_id, storage_dir="./cases/")
SnapshotSessionManager(session_id=case_id, storage=LocalFileStorage("./cases/"))
)
```

Attach a `FileSessionManager`, `S3SessionManager` or `SnapshotSessionManager`
(with a `storage` argument) to the graph with `set_session_manager`. An agent
Attach a `SnapshotSessionManager` to the graph with `set_session_manager`. Use
`LocalFileStorage` for local runs or `S3Storage` for shared storage. An agent
inside the graph must **not** carry its own session manager; `add_node` raises
`ValueError` if it does.
</Tab>
Expand Down
5 changes: 3 additions & 2 deletions site/src/content/docs/user-guide/migrate/langgraph.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

from strands import Agent, tool
from strands.multiagent import GraphBuilder
from strands.session import FileSessionManager
from strands.session import SnapshotSessionManager
from strands.storage import LocalFileStorage


# --8<-- [start:graph_migrated]
Expand Down Expand Up @@ -51,7 +52,7 @@ def run_case(case_id: str, prompt: str):

# case_id is the session id: an interrupted graph resumes from it on the next call
builder.set_session_manager(
FileSessionManager(session_id=case_id, storage_dir="./cases/")
SnapshotSessionManager(session_id=case_id, storage=LocalFileStorage("./cases/"))
)
# The task text is what the model sees, so the case id is prefixed onto it.
return builder.build()(f"Case {case_id}: {prompt}")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# --8<-- [start:graph_migrated_imports]
from strands import Agent, tool
from strands.multiagent import GraphBuilder
from strands.session import FileSessionManager
from strands.session import SnapshotSessionManager
Comment thread
nicolas-m-romero marked this conversation as resolved.
Dismissed
from strands.storage import LocalFileStorage
Comment thread
nicolas-m-romero marked this conversation as resolved.
Dismissed

# --8<-- [end:graph_migrated_imports]
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
---
title: Migrate repository-based sessions
description: "Migrate Python sessions from RepositorySessionManager,
FileSessionManager, or S3SessionManager to SnapshotSessionManager before v2.0.0."
languages: [python]
tags: [session-management, snapshots, state]
sourceLinks:
- path: strands-py/src/strands/session/repository_session_manager.py
- path: strands-py/src/strands/session/file_session_manager.py
- path: strands-py/src/strands/session/s3_session_manager.py
- path: strands-py/src/strands/session/snapshot_session_manager.py
- path: strands-py/src/strands/types/session.py
---

The repository-based session managers are deprecated and will be removed in v2.0.0.
Use this guide to preserve existing session data when moving `RepositorySessionManager`,
`FileSessionManager`, or `S3SessionManager` sessions to `SnapshotSessionManager`.

For an overview of session persistence and snapshot behavior, see
[Session management](./session-management.mdx).

## Decide whether to migrate

`SnapshotSessionManager` cannot read repository-format records. Reusing the same session
and agent IDs starts an empty snapshot session and leaves the source records unchanged.

If you do not need the existing data, start a new snapshot-backed session. To preserve the
data, restore each repository-format session through a live agent, then save the restored
state with `SnapshotSessionManager`.

## Customize the procedure

Match the migrating agent to the production agent before restoring its state:

- **Storage**: Configure the repository-based source and snapshot target. The example
reads local records with `FileSessionManager` and writes to `LocalFileStorage`. For an
S3 source, use `S3SessionManager` and `S3Storage`. For a custom source, construct the
same `SessionRepository` implementation used by the application.
- **Identifiers**: Use the same `session_id` and `agent_id` for the source and target.
Migrate each agent ID in a session separately.
- **Conversation manager**: Use the same type and settings so the restored message offset
and compaction behavior match the production agent.
- **System prompt**: Pass the production system prompt. Repository-based managers do not
restore it, but the new snapshot records it.
- **Model and tools**: Recreate the production model, tools, and custom components that
affect agent initialization or restored behavior.

## Review what restoration preserves

Attaching the repository-based manager to the temporary agent restores the persisted data
before the snapshot is created. This includes application state, conversation-manager
state, interrupt state, model state, and message history.

The restore also applies saved redactions and repairs incomplete tool-use pairs in the
history. A trailing orphan is repaired when the agent receives its next prompt. For a
stateful model, the manager restores model state instead of local messages because the
model provider manages conversation history server-side. The migration copies only the
provider's response ID, not the conversation itself. The history remains available only
while the provider retains it, so verify a stateful session by sending it a prompt after
cutover.

## Protect existing data

Complete these checks before constructing a repository-based manager or writing a
snapshot:

- Validate every source `session_id` and `agent_id`. Repository-based managers create
empty records for unknown identifiers, so a typo changes the source store.
- Run the migration offline against a copy of the session store first. Do not race a live
agent that uses the same session.
- Stop if the target snapshot already exists. Keep the source records until you verify
that the snapshot restores correctly.

## Migrate one local session

Attach the repository-based manager to a temporary agent, then capture the restored agent
with the snapshot manager.

:::caution
The script can complete even when its configuration does not match the production agent.
Complete the customization and data-protection checks above before running it.
:::

```python
import asyncio

from strands import Agent
from strands.session import FileSessionManager, SnapshotSessionManager
from strands.storage import LocalFileStorage

SYSTEM_PROMPT = "<your production system prompt>"


async def migrate_session(session_id: str, *, agent_id: str = "default") -> None:
"""Migrate one repository-format session without overwriting the target."""
snapshot = SnapshotSessionManager(
session_id=session_id,
storage=LocalFileStorage("./snapshot-sessions"),
)

# Stop rather than overwrite an existing target snapshot.
if await snapshot.restore_snapshot(Agent(agent_id=agent_id)):
raise RuntimeError(
f"session={session_id!r} agent={agent_id!r} already migrated"
)
Comment on lines +109 to +113

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't this fail when loading into a default agent? Wondering if an interrupted batch can actually resume here, did you verify?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I checked this against the branch source, since the latest commits don't change it:

  • Loading into a default agent works: restore_snapshot(Agent(agent_id=agent_id)) restores an existing snapshot into a bare agent without error. It returns False for a new target and True for one that was already migrated.
  • Resume doesn't work as the guide describes: on a second run the function raises RuntimeError("... already migrated"). A plain for sid in ids: await migrate_session(sid) therefore stops at the first session it already did, which contradicts L142 ("Keep each call independent and idempotent so an interrupted run can resume").

Suggestion (pick one):

  1. Make re-runs a no-op: return False (or log "skipped") instead of raising, and return True after a successful save. That makes the L142 claim true.
  2. Keep the raise, but change L142 to say the caller must catch the "already migrated" error to skip completed sessions when resuming.

Option 1 matches the "idempotent" wording better.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

d8787c1 resolves this by dropping the batch-resume claim. L146 now says to re-run a failed session and treat "already migrated" as done. I verified that this matches the script:

  • A failure inside save_snapshot leaves no snapshot behind (atomic write), so the retry migrates.
  • The next run raises "already migrated".
  • The probe now goes through build_agent(), so it uses the production configuration (I tested it with a custom conversation manager).

I've left a small wording suggestion inline on L146 for the ValueError case. Otherwise this thread looks ready to resolve, @opieter-aws.


# Restore conversation-manager state, redactions, and message-history repairs.
source = FileSessionManager(
session_id=session_id,
storage_dir="./repository-sessions",
)
agent = Agent(
agent_id=agent_id,
system_prompt=SYSTEM_PROMPT,
session_manager=source,
)

# Stateful models restore only model state, so check it as well.
if not agent.messages and not agent.state.get() and not agent._model_state:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important

Issue: The guard now reads agent._model_state, a private attribute, in user-facing sample code. This is the only place in the docs that uses it, and users who copy the script will depend on an internal field that can change without a deprecation cycle.

Suggestion: Use the public snapshot API instead. I checked it against the branch source and it returns the same value ({} for message-based sessions, {'response_id': ...} for stateful ones):

    # Stateful models restore only model state, so check it as well.
    model_state = agent.take_snapshot(include=["model_state"]).data["model_state"]
    if not agent.messages and not agent.state.get() and not model_state:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in fcd85ab. I ran the updated guard against the branch source. take_snapshot(preset="session").data has messages, state and model_state. Message-only, state-only and model-state-only sessions all migrate and restore correctly, and an unknown session still raises ValueError. This can be resolved.

raise ValueError(
f"no source data found for session={session_id!r} agent={agent_id!r}"
)

# Write the snapshot restored during agent initialization.
await snapshot.save_snapshot(agent, is_latest=True)


asyncio.run(migrate_session("user-123"))
```

Save the script as `migrate_repository_session.py`, update its configuration, then run:

```bash
python migrate_repository_session.py
```

## Migrate and verify each session

- **Get the source session ID** from the existing agent or session manager. See
[Reading the session ID](./session-management.mdx#reading-the-session-id).
- **Migrate many sessions** by calling `migrate_session` per session and per `agent_id`.
Keep each call independent and idempotent so an interrupted run can resume.
- **Verify, then cut over:**
1. Dry-run against a copy of the session store.
2. Attach `SnapshotSessionManager` to a fresh agent with the same `session_id` and
`agent_id`. Compare `agent.messages`, `agent.state.get()`, `agent.system_prompt`, and
the conversation-manager state.
3. Cut application code over to `SnapshotSessionManager`.
4. Delete the source records only after verifying that the snapshot restores correctly.

## Adapt unsupported session types

The single-agent `migrate_session()` example does not work for Graph or Swarm sessions.
It passes an `Agent` to `save_snapshot()`, while Graph and Swarm persistence captures the
orchestrator through its lifecycle hooks. There is no public one-shot conversion API.
Start a new snapshot-backed multi-agent session before v2.0.0. If you must preserve an
in-progress orchestrator state, remain on v1 and its repository-based manager until a
dedicated migration path is available.

`BidiAgent` requires a separate adaptation. Construct the equivalent stopped `BidiAgent`
and preserve its streaming configuration before calling `save_snapshot()`. After cutover,
`SnapshotSessionManager` saves Bidi state after each message by default, when the agent
stops, and before a connection restart. Configure this behavior with
`bidi_agent_save_latest_on`.
Loading
Loading