How to Persist and Resume Agent Runs Using State Stores in aisuite

aisuite provides a plug-in StateStore protocol that persists full conversation RunState after each turn and resumes threads later without losing context, supporting both durable file storage and in-memory backends.

aisuite is a unified interface for LLM providers that includes a lightweight agent framework for building conversational workflows. When your agents need to survive process restarts or span multiple user sessions, you must persist dialogue history and intermediate state. The aisuite/agents/state_store.py module defines a clean abstraction for storage backends, making it straightforward to persist and resume agent runs using state stores in aisuite with minimal configuration.

Understanding the StateStore Architecture

The persistence system centers on a protocol-based design that separates storage concerns from agent execution logic.

The StateStore Protocol Contract

At aisuite/agents/state_store.py (lines 53-66), aisuite defines the StateStore protocol. Any compliant backend must implement three methods:

  • save_state(thread_id, state, revision=None) – Persists a StoredRunState object. Pass revision=None for new threads or the existing revision number for updates.
  • load_state(thread_id) – Retrieves the stored state for a given thread identifier.
  • delete_state(thread_id) – Removes the persisted state.

This contract allows you to swap storage implementations without modifying agent code.

Built-in Storage Implementations

aisuite ships with two concrete implementations in the same file:

InMemoryStateStore (lines 68-89) stores StoredRunState objects in a Python dictionary. This backend is ideal for unit tests and ephemeral workflows, performing optimistic concurrency checks using revision counters but losing data when the process exits.

FileStateStore (lines 101-149) provides durable persistence by writing JSON files to a configurable root directory (<root>/<quoted_thread_id>.json). It handles atomic writes, revision tracking, and metadata timestamps (created_at and updated_at). Each file stores the complete RunState including messages, steps, and agent metadata.

Persisting New Agent Runs

To start a persisted conversation, pass a StateStore instance and a unique thread_id to Runner.run_sync. The method validates that the thread does not already exist (raising ThreadAlreadyExistsError if it does), executes the first turn, and automatically calls save_state.

from aisuite.agents.runner import Runner
from aisuite.agents.state_store import FileStateStore
from aisuite.agents.utils import simple_agent

# Initialize durable storage in the .my_state directory

store = FileStateStore(root=".my_state")

# Execute first turn and persist automatically

result = Runner.run_sync(
    agent=simple_agent,
    input="Hello, who are you?",
    state_store=store,
    thread_id="demo_thread",
)

print("First response:", result.last_message.content)

As implemented in aisuite/agents/runner.py (lines 38-45), run_sync integrates with the store before returning the RunResult, ensuring the initial state is safely written before your application proceeds.

Resuming Conversations with continue_sync

When a user returns to an existing thread, use Runner.continue_sync to load the previous state, append new messages, and persist the updated dialogue. This method requires the same state_store and thread_id used during the initial run.


# Resume the conversation where it left off

result2 = Runner.continue_sync(
    target=simple_agent,
    input="Tell me a joke.",
    state_store=store,
    thread_id="demo_thread",
)

print("Second response:", result2.last_message.content)

According to the source in aisuite/agents/runner.py (lines 94-104), this method:

  1. Loads the existing StoredRunState via state_store.load_state
  2. Recovers the RunState and appends the new user input
  3. Executes the next agent turn
  4. Saves the updated state using the previously recorded revision number

Handling Concurrent Updates with Optimistic Locking

Both storage implementations protect against lost updates using optimistic concurrency control. Each StoredRunState carries a monotonically increasing revision integer. When save_state is called with a specific revision, the backend verifies the stored revision has not changed since loading.

If another process has written to the same thread in the meantime, StateConflictError (defined in aisuite/agents/state_store.py, lines 15-22) is raised, signaling that your copy of the state is stale.

from aisuite.agents.state_store import StateConflictError

# Simulate concurrent modification

stored = store.load_state("demo_thread")

# Another process writes here, incrementing the revision...

try:
    # This fails because revision no longer matches

    store.save_state("demo_thread", stored.state, revision=stored.revision)
except StateConflictError:
    print("Conflict detected: reload state and retry")

This mechanism ensures thread safety without requiring database-level locks, making it suitable for file-based or distributed storage backends.

Testing with InMemoryStateStore

For unit tests where filesystem persistence is unnecessary, swap FileStateStore with InMemoryStateStore:

from aisuite.agents.state_store import InMemoryStateStore

mem_store = InMemoryStateStore()

# Full persistence cycle in memory

first = Runner.run_sync(simple_agent, "Hi", state_store=mem_store, thread_id="mem")
second = Runner.continue_sync(simple_agent, "How's the weather?", state_store=mem_store, thread_id="mem")

The in-memory store follows identical semantics for revision checking and thread isolation, ensuring your tests validate the same concurrency guarantees as production code.

Summary

  • aisuite provides a protocol-based StateStore API in aisuite/agents/state_store.py that abstracts persistence logic from agent execution.
  • Use Runner.run_sync with a thread_id and state_store to start persisted runs; it automatically saves initial state via save_state.
  • Use Runner.continue_sync to load existing threads and append new turns, preserving complete dialogue history.
  • FileStateStore writes durable JSON files to disk, while InMemoryStateStore provides volatile storage for testing.
  • The revision-based system raises StateConflictError when concurrent modifications occur, preventing data loss in multi-process scenarios.

Frequently Asked Questions

What data is stored in a SavedRunState?

StoredRunState contains the thread_id, the serialized RunState (including all messages, tool calls, and metadata), a revision number for concurrency control, and timestamps (created_at and updated_at). The schema is defined in aisuite/agents/types.py and serialized to JSON when using FileStateStore.

How do I implement a custom state store backend?

Create a class implementing the three methods defined in the StateStore protocol: save_state, load_state, and delete_state. Accept thread_id as a string and handle revision integers for optimistic locking. Pass your custom instance to Runner.run_sync or Runner.continue_sync via the state_store parameter.

What happens if I try to create a thread that already exists?

Runner.run_sync raises ThreadAlreadyExistsError if the state_store already contains data for the provided thread_id. To resume existing threads instead, use Runner.continue_sync, which expects the thread to exist and will raise an error if it does not.

Can multiple processes share a FileStateStore safely?

Yes, within the limitations of optimistic concurrency. While FileStateStore does not provide file locking, the revision check ensures that concurrent writes fail fast with StateConflictError rather than corrupting data. Applications should catch this exception, reload the current state, and retry the operation.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →