Choose persistence based on what you need to recover: a conversation, or unfinished execution.

Three kinds of state

State Storage Recovery
Ordinary Agent Process memory Lives as long as the instance
Agent snapshot Explicit files in a session directory Restore configuration, messages, usage, and supported extension state
AgentRuntime SQLite, PostgreSQL, or Temporal Resume persisted execution under replay and approval policies

Repeated calls to the same Agent keep its conversation. That alone does not make a session durable.

What persists

Three memory layers sit under those states, all optional.

ConversationMemory is created for every agent. It holds the message list. As the context limit approaches, the harness summarizes the oldest turns with the agent's own model and fires HookEvent.CONTEXT_CONDENSED; see Long-run reliability. A snapshot stores it; nothing else does.

agent.memory.get_messages()        # current message list
agent.memory.set_messages(saved)   # restore

AgentMemory writes titled, tagged notes as markdown files on disk. LongTermMemory stores categorized facts with ids. Both outlive the process on their own. register_memory_tools gives the model the built-in save_memory and recall_memories tools over the stores you pass, and your own tools can share the same stores.

from harnessx import AgentMemory, LongTermMemory, PermissionLevel
from harnessx.builtin import register_memory_tools

notes = AgentMemory(storage_dir=".agent_memory/agent")
facts = LongTermMemory(storage_dir=".agent_memory/long_term")

register_memory_tools(agent.tools, long_term=facts, agent_memory=notes)

@agent.tools.register(permission=PermissionLevel.ALLOW)
async def save_note(title: str, content: str, tags: str = "") -> str:
    """Save a titled note.

    Args:
        title: Short title for the memory.
        content: The information to remember.
        tags: Comma-separated tags.
    """
    return notes.save(title, content, tags=[t.strip() for t in tags.split(",") if t.strip()])

fact_id = facts.save("User prefers dark mode", category="preference")
facts.update(fact_id, "User prefers light mode")
facts.search(query="dark mode")

Tell the agent in the system prompt which tools exist and when to use them. AgentMemory.search(query=...) returns matching notes. examples/04-context/custom_memory_tools.py combines the built-in tools with two of its own over the same stores.

Save a conversation snapshot

from harnessx import Agent

async def save_and_continue():
    async with Agent() as agent:
        await agent.run("Remember that this project analyzes invoices.")
        session_id = await agent.save_session(storage_dir=".agent_sessions")

    async with await Agent.load_session(
        session_id, storage_dir=".agent_sessions"
    ) as restored:
        result = await restored.run("What does this project analyze?")
        return result

Snapshots contain serializable state, not executable Python handlers or live MCP connections. Rebind custom tools and provide compatible extensions when loading them. Supply the same extension names; explicitly supplied configuration must match the snapshot.

Try it with no API key: uv run python examples/06-durability/session_snapshots.py.

Before summarizing, the harness first clears old tool output, and agent.memory.prune_tool_results() runs that step on demand. Snapshots also carry the estimated cost and the model limits learned from provider refusals, scoped to each provider and model.

When you need crash recovery

Use AgentRuntime when you need to recover an unfinished run or preserve an approval wait across process restarts. This is covered under Advanced topics → Durable agent runs. The PostgreSQL walkthrough provides a runnable crash/restart demonstration.

Saving a conversation snapshot and persisting execution are separate operations. Loading a snapshot does not resume an in-flight tool call.

Ownership

A runtime closes the agent it executes. It closes a backend only if it created that backend. Explicitly close injected backends, providers, and other caller-owned resources. Agent instances support one active run at a time.