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 aStoredRunStateobject. Passrevision=Nonefor 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:
- Loads the existing
StoredRunStateviastate_store.load_state - Recovers the
RunStateand appends the new user input - Executes the next agent turn
- 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
StateStoreAPI inaisuite/agents/state_store.pythat abstracts persistence logic from agent execution. - Use
Runner.run_syncwith athread_idandstate_storeto start persisted runs; it automatically saves initial state viasave_state. - Use
Runner.continue_syncto load existing threads and append new turns, preserving complete dialogue history. FileStateStorewrites durable JSON files to disk, whileInMemoryStateStoreprovides volatile storage for testing.- The revision-based system raises
StateConflictErrorwhen 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →