Restore a Strands conversation after a process restart
We will record a passphrase with a Strands agent, exit the process, and start a second process using the saved session ID. Before the second model call, we will verify that the original message was restored from AuraDB.
This path uses the Bolt backend connected to AuraDB, Bedrock for the agent and embeddings, and the Strands session manager for persistence. Entity extraction is disabled so the observable outcome is conversation restoration, not an unverified automatically built knowledge graph.
This lesson uses neo4j-agent-memory 0.7.0 from PyPI and the complete programs on this page in a POSIX shell. Create the local files shown below; the checks below verify the results in your environment.
Before you begin
-
Python 3.10 or newer and a POSIX shell.
-
A Neo4j Aura account for a dedicated lesson instance.
-
AWS credentials already configured in the standard credential chain.
-
Access to a Bedrock chat model or inference-profile ID and
amazon.titan-embed-text-v2:0in the selected region.
Confirm the provider prerequisites using Bedrock configuration before beginning. Complete AWS account and model-access setup before the lesson.
1. Install the integration and connect to Aura
Create a local folder and virtual environment for the examples on this page. The commands reuse an existing environment without changing its files:
mkdir -p ~/agent-memory-tutorials
cd ~/agent-memory-tutorials
if [ -e .venv ]; then
printf '%s\n' 'Using the existing virtual environment.'
else
python3 -m venv .venv
fi
source .venv/bin/activate
Expected: ~/agent-memory-tutorials is your working directory and its virtual environment is active. Install the published SDK with the command below.
Each complete code block labelled Save as names a file to create in this folder using your editor. Copy the entire block, including imports and the entry point. Expand each helper disclosure and use Copy code to copy its full source. Keep all files together so their imports resolve.
When continuing from another tutorial or guide, retain the existing environment, configuration, session files, and .tutorial-state/. Reuse unchanged helper files; compare an existing file before replacing it, and finish any pending cleanup or recovery before changing the code that owns its state.
python -m pip install 'neo4j-agent-memory[strands,bedrock]==0.7.0'
python -c "from importlib.metadata import version; \
import neo4j_agent_memory; \
assert version('neo4j-agent-memory') == '0.7.0'; \
print('SDK 0.7.0 import verified')"
Expected: SDK 0.7.0 import verified. Keep agent-memory-tutorials as your working directory for every command below.
Create and check the local files
Create each file below in ~/agent-memory-tutorials. All complete sources follow this manifest. On a first visit every file is new; when returning, reuse unchanged helpers and compare their contents before replacing them.
| File | Purpose | Returning reader |
|---|---|---|
|
Read explicit Aura credentials; the database defaults to |
Reuse unchanged |
|
Probe the selected database and close the driver. |
Reuse unchanged |
|
Record, inspect, or recall the saved Strands session. This is the lesson entry point. |
New for this lesson; retain it with unfinished state |
The manifest above names this lesson’s entry point and its helpers. Save every file it lists, then run the offline assembly checks that follow the sources before the lesson commands.
Open the helper below and save its complete source as aura_connection.py in ~/agent-memory-tutorials.
Show aura_connection.py
aura_connection.py"""Read the Aura connection exported by the documentation setup commands."""
import os
class AuraConfigurationError(ValueError):
"""Missing or invalid tutorial settings, with no credential values in errors."""
def aura_config():
required = ("NEO4J_URI", "NEO4J_USERNAME", "NEO4J_PASSWORD")
missing = [name for name in required if not os.environ.get(name, "").strip()]
if missing:
raise AuraConfigurationError("Export the Aura connection settings: " + ", ".join(missing))
uri = os.environ["NEO4J_URI"]
if not uri.startswith("neo4j+s://"):
raise AuraConfigurationError("NEO4J_URI must use the Aura neo4j+s:// connection scheme")
database = os.environ.get("NEO4J_DATABASE", "neo4j")
if not database.strip():
raise AuraConfigurationError("NEO4J_DATABASE must not be empty")
return {
"uri": uri,
"username": os.environ["NEO4J_USERNAME"],
"password": os.environ["NEO4J_PASSWORD"],
"database": database,
}
Open the helper below and save its complete source as wait_for_tutorial_neo4j.py in ~/agent-memory-tutorials.
Show wait_for_tutorial_neo4j.py
wait_for_tutorial_neo4j.py"""Check the exported Aura connection, waiting at most three minutes."""
import asyncio
import sys
from aura_connection import AuraConfigurationError, aura_config
from neo4j import AsyncGraphDatabase
from neo4j.exceptions import DriverError, Neo4jError, ServiceUnavailable, SessionExpired
async def wait_until_ready(timeout=180):
if timeout <= 0:
raise ValueError("Readiness timeout must be positive")
config = aura_config()
async def probe(driver):
while True:
try:
await driver.verify_connectivity()
records, _, _ = await driver.execute_query(
"RETURN 1 AS ready", database_=config["database"], routing_="r"
)
if len(records) != 1 or records[0]["ready"] != 1:
raise RuntimeError("Unexpected readiness query result")
return
except (ServiceUnavailable, SessionExpired):
await asyncio.sleep(2)
async with AsyncGraphDatabase.driver(
config["uri"],
auth=(config["username"], config["password"]),
connection_timeout=min(10, timeout),
connection_acquisition_timeout=min(10, timeout),
) as driver:
await asyncio.wait_for(probe(driver), timeout=timeout)
def main():
try:
asyncio.run(wait_until_ready())
except AuraConfigurationError as error:
print(str(error), file=sys.stderr)
raise SystemExit(1) from None
except (asyncio.TimeoutError, DriverError, Neo4jError) as error:
# Driver exceptions can include connection details. Report the category
# and next action without echoing credentials or a raw server message.
print(
f"Neo4j Aura readiness failed ({type(error).__name__}). "
"Check that the instance is Running, the exported NEO4J_* values "
"match its credentials, and your network allows the connection.",
file=sys.stderr,
)
raise SystemExit(1) from None
print("Verified: Neo4j Aura answered the readiness query")
if __name__ == "__main__":
main()
strands_memory_tutorial.py"""Two-process AuraDB conversation persistence using a Strands session manager."""
import argparse
import json
import os
from pathlib import Path
from uuid import uuid4
from aura_connection import aura_config
SESSION_FILE = Path("strands-tutorial-session.txt")
SENTINEL = "The workshop passphrase is cedar-lantern-47."
def inspect_session():
"""Read the saved session directly; never construct an agent or write memory."""
from neo4j import GraphDatabase
session_id = SESSION_FILE.read_text().strip()
if not session_id:
raise RuntimeError("The session file is empty; preserve it and follow recovery")
config = aura_config()
with GraphDatabase.driver(
config["uri"], auth=(config["username"], config["password"])
) as driver:
records, _, _ = driver.execute_query(
"MATCH (c:Conversation {session_id: $session_id}) "
"OPTIONAL MATCH (c)-[:HAS_MESSAGE]->(m:Message) "
"RETURN count(DISTINCT c) AS conversations, "
"collect(DISTINCT m { .id, .role, .content }) AS messages",
session_id=session_id,
database_=config["database"],
routing_="r",
)
if len(records) != 1 or records[0]["conversations"] > 1:
raise RuntimeError(
"Ambiguous session; preserve the file and inspect the dedicated instance"
)
messages = records[0]["messages"]
sentinel_stored = any(
message["role"] == "user" and message["content"] == SENTINEL for message in messages
)
result = {
"session_id": session_id,
"message_ids": [message["id"] for message in messages],
"message_count": len(messages),
"sentinel_stored": sentinel_stored,
}
print(json.dumps(result, indent=2))
print(
"Next: recall may make another model call and store another turn."
if sentinel_stored
else "Not ready for recall. Preserve the session file and follow the recovery instructions."
)
return result
def main():
parser = argparse.ArgumentParser()
parser.add_argument("phase", choices=["record", "inspect", "recall"])
phase = parser.parse_args().phase
if phase == "inspect":
inspect_session()
return
from strands import Agent
from neo4j_agent_memory import BoltSettings
from neo4j_agent_memory.integrations.strands import Neo4jSessionManager
model_id = os.environ.get("BEDROCK_MODEL_ID", "").strip()
if not model_id or model_id.startswith("replace-with-"):
raise ValueError("Export an accessible BEDROCK_MODEL_ID before recording a session")
settings = BoltSettings(
neo4j=aura_config(),
embedding="bedrock/amazon.titan-embed-text-v2:0",
extraction={"extractor_type": "none"},
)
if phase == "record":
if SESSION_FILE.exists():
raise RuntimeError(
"A session file exists; run inspect before choosing recall or recovery"
)
session_id = f"strands-docs-{uuid4().hex[:8]}"
with SESSION_FILE.open("x") as saved:
saved.write(session_id)
else:
session_id = SESSION_FILE.read_text().strip()
with Neo4jSessionManager(session_id, settings=settings, extract_entities=False) as manager:
agent = Agent(
model=model_id,
session_manager=manager,
system_prompt="Use the conversation history to answer. Do not invent a missing passphrase.",
)
restored_text = str(agent.messages)
if phase == "recall":
if SENTINEL not in restored_text:
raise RuntimeError("The stored message was not restored; no recall claim verified")
print("Verified: prior message restored before the model call")
prompt = SENTINEL if phase == "record" else "What workshop passphrase did I give you?"
response = agent(prompt)
print(response)
print(f"Session ID: {session_id}; phase={phase}")
print("Session manager closed after persisting the turn")
if __name__ == "__main__":
main()
Check the copied files before exporting credentials or making a service request:
python -m py_compile aura_connection.py wait_for_tutorial_neo4j.py strands_memory_tutorial.py
python -c "import aura_connection, wait_for_tutorial_neo4j, strands_memory_tutorial; assert all(callable(f) for f in [aura_connection.aura_config, wait_for_tutorial_neo4j.wait_until_ready, strands_memory_tutorial.main]); print('Lesson imports verified')"
python strands_memory_tutorial.py --help
Expected: compilation exits without errors, then Lesson imports verified and the command help. These checks import the local files without calling their entry points, downloading models, or contacting Aura/providers. Compilation alone does not check imports. If a file or function is missing, recopy its entire source and repeat the checks; leave existing session/state files intact. Provider/framework calls are checked when the lesson runs.
This starts a new lesson with a new empty database. To return to an unfinished lesson, use Resume this lesson later instead.
-
Sign in to the Neo4j Aura console and create an AuraDB Free instance named
agent-memory-tutorial. Use an empty instance dedicated to this lesson; do not load a sample dataset. -
Download the generated credentials and keep them outside the tutorial folder. Wait until the instance shows Running.
-
Copy its connection URI, username, password and database name into the exports below. Keep the
neo4j+s://scheme supplied by Aura.
Aura permits one Free instance per account. This lesson needs that slot for a dedicated tutorial instance; do not delete an existing database containing other work to make room. See Aura instance creation for account and tier requirements.
The files from the preceding assembly checkpoint read these exports and verify the selected database.
export NEO4J_URI='neo4j+s://<instance-id>.databases.neo4j.io'
export NEO4J_USERNAME='neo4j'
export NEO4J_PASSWORD='replace-with-the-generated-password'
export NEO4J_DATABASE='neo4j'
python wait_for_tutorial_neo4j.py
Expected: Verified: Neo4j Aura answered the readiness query. The helper checks connectivity and executes RETURN 1 in the selected database, retrying temporary connection failures for up to three minutes. Authentication failures stop immediately. If the check fails, confirm the instance is Running and recopy its connection settings.
The examples read these exported variables through aura_connection.py. They require the Aura URI, username and password and never fall back to a local instance. When NEO4J_DATABASE is unset, they use the neo4j database, which is the Aura default. Keep this shell and virtual environment active for the remaining commands. See Aura connection instructions for help locating the settings.
In the Aura console, open Query, select this instance and database in the connection bar, and connect using the same credentials. Run:
MATCH (n) RETURN count(n) AS node_count
Expected: 0. This confirms that the lesson starts with an empty database. The Python SDK still uses backend="bolt": Aura hosts Neo4j and accepts encrypted Bolt connections.
2. Select the authorized Bedrock deployment
Use the Bedrock supported-model catalog to select a chat model in your intended region. Copy its model ID from the Bedrock console. If that deployment requires an inference profile, use its ID or ARN as described in using an inference profile; a provider display name is not an invocation ID. Confirm the same identity can invoke both that chat deployment and the Titan embedding model. Set AWS_REGION to that region; the value below is an example, not a promise of model access there.
export AWS_REGION="us-east-1"
export AWS_DEFAULT_REGION="$AWS_REGION"
export BEDROCK_MODEL_ID="replace-with-your-accessible-Bedrock-model-or-profile-id"
python -c "import boto3; \
print('AWS identity:', boto3.client('sts').get_caller_identity()['Arn'])"
Expected: your intended AWS identity ARN. This is a read-only identity check; it does not prove model access. Keep the same region and identity for both runs. The later chat and embedding requests can incur provider usage charges. An access-denied response needs a check of IAM/model/profile permissions; a validation error needs the exact model/profile ID and region checked; a quota/throttling error needs the account limits or provider retry guidance checked.
3. Read the complete agent
Read the complete file copied in step 1. The shared aura_config() helper reads the exported Aura connection variables. The session manager owns message persistence and client shutdown. record writes the session ID to a local file; recall reads it and does not reseed the passphrase. The sentinel is checked in restored message history before another model request. Keep the same Aura instance and exported connection variables for both processes.
4. Record the first turn
python strands_memory_tutorial.py record
Expected: a model response, the Session ID: with phase=record, and Session manager closed after persisting the turn. The process now exits. Keep strands-tutorial-session.txt for the next step.
5. Verify a fresh process restores the message
python strands_memory_tutorial.py recall
Expected before the response: Verified: prior message restored before the model call. The response should identify cedar-lantern-47; wording can vary. If the explicit history check fails, conversation restoration is not verified, even if the model happens to guess the phrase.
The script uses the same saved session ID rather than starting an unrelated conversation with the same user name.
What we learned
A session manager can load and persist framework history through its lifecycle hooks. This example does not attach a tool catalog and does not rely on the model deciding to store the message. That ownership differs from agent-selected memory tools; see the integration guide for those separate patterns.
Troubleshooting
An identity check and model access are separate. If Bedrock rejects a request, inspect its error for the selected model/profile and region rather than changing memory scope. If Neo4j is unavailable, rerun the readiness check and stop before rerunning record. A leftover session file identifies the existing exercise; it does not prove that its first message reached Aura. record keeps that ID even after a startup failure and refuses to overwrite it.
Inspect an interrupted first turn
Keep the original Aura exports and stop any still-running lesson process. This command reads only the saved session’s messages, without constructing an agent, requesting embeddings, or calling Bedrock:
python strands_memory_tutorial.py inspect
Expected: JSON containing session_id, message_ids, message_count, and sentinel_stored, followed by a next-action message. Counts are observations of this database, not evidence about another instance. No messages or local IDs are changed.
| Result | Next action |
|---|---|
|
The initial user message exists. Keep the file; use |
|
Do not repeat |
Inspection fails or the session is ambiguous |
Preserve the file and instance. Fix connection settings or inspect the exact saved session in Aura Query; do not interpret a failed read as zero messages or erase the file to bypass the guard. |
The program validates missing model configuration before creating a new session file. A later network/provider failure can still leave partial data; the inspection/reset procedure handles that boundary.
Resume this lesson later
A new Python process keeps the same shell exports; a new terminal does not. If this lesson’s Aura instance still exists, resume it without creating another instance or asserting that its database is empty:
-
Return to the existing folder and activate its environment:
cd ~/agent-memory-tutorials source .venv/bin/activate -
Re-export the original instance’s
NEO4J_URI,NEO4J_USERNAME,NEO4J_PASSWORD, andNEO4J_DATABASEfrom its saved credentials. Restore this page’s provider key/model/region settings too. Keep secrets out of the Python files. -
Check the same database:
python wait_for_tutorial_neo4j.py -
Use the readback or inspection step described below. Do not repeat the seed/record/write command just to recover context. Preserve existing IDs and state until you know which operations completed.
If the instance was already destroyed, its stored results cannot be resumed; complete local cleanup and start a new empty lesson instance. A different lesson also starts with a fresh instance, while keeping the folder and environment.
Run python strands_memory_tutorial.py inspect using the existing strands-tutorial-session.txt. Follow its result and the recovery table above. recall makes a model request and stores another turn; record must not replace the saved ID.
Cleanup and next steps
Cleanup ends this lesson and removes its stored results. To keep working with those results later, follow Resume this lesson later before cleanup.
In the Aura console, select only the agent-memory-tutorial instance created for this lesson. Use its trashcan action, enter its exact name and confirm Destroy.
Expected: the tutorial instance disappears from the instance list. This removes its data and snapshots, so verify the instance name before confirming. See Aura instance deletion for the console procedure.
Remove the downloaded credentials for that deleted instance and clear its connection variables:
unset NEO4J_URI NEO4J_USERNAME NEO4J_PASSWORD NEO4J_DATABASE
Keep the tutorial folder and virtual environment. Start each Aura tutorial with a new empty lesson instance and its new credentials; this also avoids carrying over vector indexes from a lesson using a different embedding dimension.
After confirming that the dedicated instance is gone, remove only this lesson’s local session-ID file:
rm strands-tutorial-session.txt
This ends the exercise. Keep the file while any write or cleanup is unresolved; a new exercise creates a new ID only after this deliberate disposal.
Continue with Strands integration for retrieval configuration, memory tools, and backend-specific limits.