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

> Learn how to persist and resume agent runs using state stores in aisuite. aisuite's StateStore protocol saves conversation state, enabling seamless thread resumption without context loss.

- Repository: [Andrew Ng/aisuite](https://github.com/andrewyng/aisuite)
- Tags: how-to-guide
- Published: 2026-07-28

---

**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`](https://github.com/andrewyng/aisuite/blob/main/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`](https://github.com/andrewyng/aisuite/blob/main/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`.

```python
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`](https://github.com/andrewyng/aisuite/blob/main/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.

```python

# 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`](https://github.com/andrewyng/aisuite/blob/main/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`](https://github.com/andrewyng/aisuite/blob/main/aisuite/agents/state_store.py), lines 15-22) is raised, signaling that your copy of the state is stale.

```python
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`**:

```python
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`](https://github.com/andrewyng/aisuite/blob/main/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`](https://github.com/andrewyng/aisuite/blob/main/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.