Persist the Conversation¶
The renderer makes every action visible until the process exits. Restart
harness.py, ask a follow-up question, and the assistant has no earlier
project context. MemoryContextStore stores only Python objects in that process.
Outcome¶
The harness stores messages and usage in SQLite. Reusing one stable session ID after a restart resumes the same conversation.
Fast Track¶
Install
axio-context-sqlite.Open one connection with
connect()at application startup.Bind
SQLiteContextStoreto a stable session ID and project name.Keep the connection open while the store is in use.
Close the connection in the application shutdown path.
Hands-on delta¶
1. Replace the context construction¶
Add the context package if the application does not already depend on it. Run
uv add axio-context-sqlite from the application root.
Replace the process-local memory store with a connection and a session-bound store:
DATA_DIRECTORY = Path(os.environ.get("AXIO_TUTORIAL_DATA_DIR", "data"))
DATABASE_PATH = DATA_DIRECTORY / "harness.db"
PROJECT_ID = "project-workbench"
SESSION_ID = os.environ.get("AXIO_SESSION_ID", "local-project-demo")
async def run_persistent_turn(
prompt: str,
) -> tuple[int, tuple[int, int], SessionEndEvent]:
connection = await connect(DATABASE_PATH)
context = SQLiteContextStore(
connection,
session_id=SESSION_ID,
project=PROJECT_ID,
)
try:
message_count = len(await context.get_history())
previous_usage = await context.get_context_tokens()
ending = await render_turn(agent, prompt, context)
return message_count, previous_usage, ending
finally:
await context.close()
await connection.close()
AXIO_TUTORIAL_DATA_DIR selects the data directory for this runnable example.
It defaults to data. The application-level AXIO_SESSION_ID selects the
conversation to resume.
Open the connection once around the application lifetime. A REPL should keep it open around the complete input loop, not reconnect for every user turn.
2. Resume by stable ID¶
session_id selects a conversation. local-project-demo is useful for one
local user because it is stable across process starts. A production harness
should receive a globally unique conversation ID from its authenticated
application layer.
Do not generate a fresh UUID at startup when the goal is to resume. Also do not share one fixed ID between users. Use a new stable ID when the user explicitly starts another conversation.
project groups sessions for listing and usage reporting. Keep it stable too,
but do not treat it as an authorization boundary.
3. Own the connection lifetime¶
connect() creates and initializes the aiosqlite.Connection. The code that
calls connect() owns that connection and must close it.
SQLiteContextStore can share the connection with other session stores.
Consequently, SQLiteContextStore.close() is intentionally a no-op. Calling it
still honors the ContextStore lifecycle, but only connection.close() releases
the database resource.
See Context & Messages for session and storage semantics. See Writing Context Stores for the ownership contract.
Try It¶
The example opens the same database through a new connection after the agent turn. It confirms that the stable ID restores messages and cumulative usage. A different ID starts empty:
async def demonstrate_restart() -> None:
message_count, previous_usage, ending = await run_persistent_turn(
"What does README.md say about the agent harness?",
)
connection = await connect(DATABASE_PATH)
resumed = SQLiteContextStore(
connection,
session_id=SESSION_ID,
project=PROJECT_ID,
)
try:
history = await resumed.get_history()
usage = await resumed.get_context_tokens()
assert len(history) == message_count + 4
assert usage == (
previous_usage[0] + ending.total_usage.input_tokens,
previous_usage[1] + ending.total_usage.output_tokens,
)
assert ending.total_usage == Usage(input_tokens=20, output_tokens=10)
separate = SQLiteContextStore(
connection,
session_id=f"{SESSION_ID}-new",
project=PROJECT_ID,
)
assert await separate.get_history() == []
finally:
await resumed.close()
await connection.close()
print(f"Resumed {len(history)} messages for {SESSION_ID}.")
Run uv run python examples/tutorial/persist_the_conversation.py twice. The
message count increases because both processes use the same default session and
data path. Set a different AXIO_SESSION_ID to start another conversation.
Done when¶
[ ] Restarting the process preserves the conversation.
[ ] The same session ID resumes the same message history.
[ ] A different session ID starts a separate conversation.
[ ] The connection stays open for every store operation.
[ ] Application shutdown closes the connection explicitly.
Next failure¶
Persistence fixes forgetting, but it does not limit growth. The database now keeps every old message and every large document result. The next lesson bounds tool output and compacts older context.
Continue with Compact Long Context.