# How to Persist and Resume Agent State with State Stores in aisuite

> Learn to persist and resume agent state in aisuite using State Stores. Easily save and load conversation history to continue agent execution seamlessly.

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

---

**To persist and resume agent state in aisuite, pass a `StateStore` implementation (such as `FileStateStore` or `PostgresStateStore`) along with a unique `thread_id` to `Runner.run_sync()`, then use `Runner.continue_sync()` with the same identifiers to reload the conversation history and continue execution.**

The aisuite library decouples agent execution from state persistence through a clean abstraction layer. By leveraging the `StateStore` protocol defined in [`aisuite/agents/state_store.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/agents/state_store.py), developers can save conversation history, intermediate steps, and metadata to memory, disk, or PostgreSQL, enabling seamless resumption of long-running agent workflows across process restarts.

## Understanding the StateStore Protocol

The foundation of state persistence is the **`StateStore`** protocol located in [`aisuite/agents/state_store.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/agents/state_store.py). This interface defines three core operations: `save_state()`, `load_state()`, and `delete()`. When you invoke `Runner.run_sync()` with a `state_store` and `thread_id`, the runner serializes the complete **`RunState`**—including message history, execution steps, and metadata—and delegates storage to the provided implementation.

Each save operation returns a **`StoredRunState`** object containing a monotonically increasing revision number, timestamps, and optional metadata. The protocol enforces **optimistic concurrency control**: the `save_state()` method accepts an optional `revision` parameter, and implementations raise a `StateConflictError` if the stored revision does not match, preventing lost updates in concurrent environments.

## Built-in State Store Implementations

aisuite ships with three concrete implementations that share the same API but target different persistence requirements.

- **`InMemoryStateStore`**: Stores state in a Python dictionary. Ideal for unit tests and short-lived scripts where durability is not required.
- **`FileStateStore`**: Persists JSON files to a local directory (default `.aisuite/state`). Suitable for simple local development and debugging.
- **`PostgresStateStore`**: Stores data in PostgreSQL tables (`agent_thread_heads`, `agent_messages`, `agent_compactions`). Designed for production deployments requiring durability, multi-process access, and horizontal scalability.

## Persisting Agent State

To enable persistence, instantiate a store and pass it to the runner alongside a unique thread identifier. The runner validates that both `state_store` and `thread_id` are provided together, raising `ValueError` if only one is supplied.

```python
from aisuite.agents.runner import Runner
from aisuite.agents.state_store import FileStateStore

# Initialize store (defaults to ./.aisuite/state)

store = FileStateStore()

result = Runner.run_sync(
    agent=my_agent,
    input="Explain the difference between TCP and UDP.",
    thread_id="demo-thread-1",
    state_store=store,
    max_turns=3,
)

```

After execution, a JSON file named [`demo-thread-1.json`](https://github.com/andrewyng/aisuite/blob/main/demo-thread-1.json) appears in the state directory, containing the serialized `StoredRunState` with revision `1`.

## Resuming a Conversation

To continue a persisted conversation, use **`Runner.continue_sync()`** (or the async `continue_run`). This method loads the existing state via `state_store.load_state()`, appends the new user message to the conversation history, executes the agent, and writes the updated state back with an incremented revision.

```python
continued = Runner.continue_sync(
    target=my_agent,
    input="What about HTTP/2?",
    thread_id="demo-thread-1",
    state_store=store,
)

```

The runner handles hydration automatically, preserving context from previous turns without manual state management.

### Handling Optimistic Concurrency

For high-concurrency scenarios, pass the `revision` returned by the previous `load_state()` call when saving. If another process has modified the state in the interim, the store raises **`StateConflictError`** via the internal `_assert_revision` helper, allowing your application to retry or merge changes.

## Production-Grade Persistence with PostgreSQL

For distributed systems, **`PostgresStateStore`** in [`aisuite/agents/postgres_state_store.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/agents/postgres_state_store.py) provides ACID guarantees and schema management. It stores message history in `agent_messages` and maintains a lightweight head row in `agent_thread_heads`.

```python
from aisuite.agents.postgres_state_store import PostgresStateStore

pg_store = PostgresStateStore.from_dsn(
    "postgresql://user:pass@localhost/aisuite",
    create_schema=True
)

run1 = Runner.run_sync(
    agent=my_agent,
    input="Summarize the last 10 trades.",
    thread_id="pg-thread-42",
    state_store=pg_store,
)

```

The implementation supports **state compaction** via `compact_state()` to trim ancient messages and control storage growth.

## Attaching Custom Metadata

You can attach arbitrary dictionaries to persisted states for auditing or indexing. Pass a `metadata` dictionary to `run_sync()`, and retrieve it later via `load_state()`.

```python
result = Runner.run_sync(
    agent=my_agent,
    input="Explain quantum entanglement.",
    thread_id="meta-demo",
    state_store=store,
    metadata={"user_id": "alice", "session": "quiz-1"},
)

stored = store.load_state("meta-demo")
print(stored.metadata)  # {'user_id': 'alice', 'session': 'quiz-1'}

```

## Summary

- The **`StateStore`** protocol in [`aisuite/agents/state_store.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/agents/state_store.py) abstracts persistence behind `save_state()` and `load_state()` methods.
- Choose **`InMemoryStateStore`** for tests, **`FileStateStore`** for local development, or **`PostgresStateStore`** for production workloads.
- Always supply both **`thread_id`** and **`state_store`** to `Runner.run_sync()` or `Runner.continue_sync()` to enable persistence.
- The runner automatically manages revision numbers and optimistic concurrency control to prevent state corruption.
- Custom metadata and PostgreSQL compaction support advanced lifecycle management for long-running agents.

## Frequently Asked Questions

### What happens if I provide only a thread_id without a state_store?

The runner raises a `ValueError` because aisuite requires both parameters to ensure deterministic persistence behavior. You must supply both or neither.

### Can I resume a conversation from a different machine or process?

Yes, if you use **`PostgresStateStore`** or a shared **`FileStateStore`** directory (e.g., on a network filesystem). The state is serialized to JSON or database rows, making it portable across processes.

### How does aisuite handle concurrent writes to the same thread?

The **`StateStore`** protocol implements optimistic concurrency. Each save returns a revision number; subsequent saves must include this revision. If the revision mismatches, a `StateConflictError` is raised, forcing the caller to reload and retry.

### Is there a limit to how much conversation history is stored?

By default, no. However, **`PostgresStateStore`** provides a `compact_state()` method to truncate old messages, and you can implement similar logic for custom stores by manipulating the `RunState` object before saving.