# Managing Conversation State and Handling ThreadAlreadyExistsError in aisuite

> Learn to manage conversation state and handle ThreadAlreadyExistsError in aisuite. Prevent overwrites and resume persisted state using continuation APIs.

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

---

**aisuite prevents accidental conversation overwrites by raising `ThreadAlreadyExistsError` when you attempt to start a run with an existing thread ID, requiring you to use continuation APIs like `Runner.continue_sync` to resume persisted state.**

The **aisuite** library separates agent execution from state persistence through a thread-based architecture. When building multi-turn conversational agents, you must understand how the `Runner` class manages conversation history and why it enforces strict thread ID uniqueness. This guide examines the source code implementation in [`aisuite/agents/runner.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/agents/runner.py) and [`aisuite/agents/state_store.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/agents/state_store.py) to show you how to properly handle conversation state and resolve `ThreadAlreadyExistsError`.

## How aisuite Manages Conversation State

### Thread-ID to State Mapping

At the core of aisuite's state management is a strict one-to-one mapping between a **thread ID** and a persisted `RunState` object. When you invoke `Runner.run()` with a `thread_id` parameter, the framework checks the provided `StateStore` for existing entries before execution begins.

According to the source code in [`aisuite/agents/runner.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/agents/runner.py) (lines 38-45), the `_run_impl` method performs this validation:

```python

# From aisuite/agents/runner.py

if thread_id is not None and state_store is not None:
    existing_state = state_store.load_state(thread_id)
    if existing_state is not None:
        raise ThreadAlreadyExistsError(
            f"Thread with ID '{thread_id}' already exists. "
            f"Use continue_run() to continue an existing thread."
        )

```

This check ensures that you cannot accidentally overwrite an active conversation. If state exists, you must explicitly choose to continue the thread rather than replace it.

### The StateStore Protocol

The persistence layer is defined by the `StateStore` protocol in [`aisuite/agents/state_store.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/agents/state_store.py) (lines 53-64), which specifies three required operations:

- **`save_state(thread_id, run_state)`**: Persists the conversation state
- **`load_state(thread_id)`**: Retrieves existing state or returns `None`
- **`delete_state(thread_id)`**: Removes persisted state

Both `InMemoryStateStore` and `FileStateStore` implement this protocol with **optimistic concurrency** checks using revision numbers to prevent race conditions when multiple workers access the same thread.

## Understanding ThreadAlreadyExistsError

`ThreadAlreadyExistsError` is defined in [`aisuite/agents/runner.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/agents/runner.py) (lines 30-33) as a subclass of `RuntimeError`:

```python
class ThreadAlreadyExistsError(RuntimeError):
    """Raised when a thread ID already exists in the state store."""
    pass

```

The framework raises this error exclusively when you call `Runner.run()` with a `thread_id` that already exists in the configured `StateStore`. This serves as a safety mechanism to prevent data loss in long-running conversational applications. The error is re-exported in [`aisuite/__init__.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/__init__.py) as part of the public API, allowing you to catch it with `except ai.ThreadAlreadyExistsError`.

## Starting New Threads vs. Continuing Conversations

### Starting a Fresh Thread with Runner.run

To initiate a new conversation, provide a unique `thread_id` and a `StateStore` instance to `Runner.run()`:

```python
import aisuite as ai

agent = ai.Agent(
    name="weather-assistant",
    model="openai:gpt-4o-mini",
    instructions="Answer weather queries concisely."
)

store = ai.FileStateStore(root=".aisuite/state")

result = ai.Runner.run(
    agent,
    "What is the weather in Tokyo?",
    thread_id="weather-session-tokyo-001",
    state_store=store,
)
print(result.final_output)

```

If `weather-session-tokyo-001` already exists in the file store, the call raises `ThreadAlreadyExistsError` immediately before executing the agent.

### Continuing an Existing Thread with Runner.continue_sync

To resume a conversation, use `Runner.continue_sync()` (or the async `continue_run()`), which loads the stored `RunState`, appends your new message, and executes the next turn. The implementation in [`aisuite/agents/runner.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/agents/runner.py) (lines 84-92) validates that both `state_store` and `thread_id` are provided together:

```python

# Continue the previous conversation

result = ai.Runner.continue_sync(
    agent,
    "What about tomorrow's forecast?",
    thread_id="weather-session-tokyo-001",
    state_store=store,
)
print(result.final_output)

```

If you attempt to continue a thread that does not exist, the framework raises `StateNotFoundError`, the logical counterpart to `ThreadAlreadyExistsError`.

## State Persistence Implementations

### InMemoryStateStore

The `InMemoryStateStore` class ([`aisuite/agents/state_store.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/agents/state_store.py), lines 68-99) maintains state in a Python dictionary, making it ideal for testing or transient single-process applications. It implements optimistic concurrency by tracking revision numbers and raises `StateConflictError` if the stored revision does not match the expected value (lines 71-81).

### FileStateStore

For production durability, `FileStateStore` (lines 101-148) persists state as JSON files under `.<repo>/state/`. It uses atomic write operations via temporary files to prevent corruption during system crashes. Like the in-memory variant, it supports revision-based conflict detection for concurrent access scenarios.

### Optimistic Concurrency and Conflict Handling

Both store implementations validate revision numbers before saving. If two processes attempt to update the same thread simultaneously, the second write fails with `StateConflictError`, forcing the application to reload the current state and retry. This mechanism ensures conversation integrity without requiring database locks.

## Practical Code Examples

### Example 1: Handling Duplicate Thread IDs

This pattern shows how to catch `ThreadAlreadyExistsError` and automatically switch to continuation mode:

```python
import aisuite as ai

agent = ai.Agent(name="assistant", model="openai:gpt-4o")
store = ai.FileStateStore(root=".aisuite/state")
thread_id = "user-session-123"

try:
    result = ai.Runner.run(
        agent,
        "Analyze this code repository",
        thread_id=thread_id,
        state_store=store,
    )
except ai.ThreadAlreadyExistsError:
    # Thread exists, so continue instead

    result = ai.Runner.continue_sync(
        agent,
        "Provide additional analysis on the test files",
        thread_id=thread_id,
        state_store=store,
    )

print(result.final_output)

```

### Example 2: Multi-Turn Conversation with In-Memory State

For stateless applications or testing, use `InMemoryStateStore` to maintain conversation context across multiple turns:

```python
import aisuite as ai

store = ai.InMemoryStateStore()
agent = ai.Agent(name="coder", model="anthropic:claude-3.5-sonnet")

# First turn - creates the thread

first = ai.Runner.run(
    agent,
    "Write a Python function to calculate fibonacci numbers",
    thread_id="coding-session",
    state_store=store,
)

# Second turn - continues automatically

second = ai.Runner.continue_sync(
    first,  # Can pass the previous result or the agent

    "Now optimize it for memoization",
    thread_id="coding-session",
    state_store=store,
)

print(second.final_output)

```

### Example 3: Checking for State Existence Before Running

To proactively check if a thread exists before attempting creation:

```python
import aisuite as ai

store = ai.FileStateStore(root=".aisuite/state")
thread_id = "persistent-thread-001"

# Check existence manually

existing = store.load_state(thread_id)

if existing is None:
    # Safe to create new thread

    result = ai.Runner.run(agent, "Initial message", thread_id=thread_id, state_store=store)
else:
    # Continue existing

    result = ai.Runner.continue_sync(agent, "Follow-up", thread_id=thread_id, state_store=store)

```

## Summary

- **aisuite** uses thread IDs to uniquely identify persisted conversation state across runs.
- `ThreadAlreadyExistsError` is raised in [`aisuite/agents/runner.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/agents/runner.py) when `Runner.run()` detects an existing state entry, preventing accidental overwrites.
- Use `Runner.continue_sync()` with the same `thread_id` and `StateStore` to resume conversations safely.
- The `StateStore` protocol supports custom implementations, with built-in `InMemoryStateStore` for testing and `FileStateStore` for persistence.
- Both stores implement optimistic concurrency using revision numbers to handle race conditions.

## Frequently Asked Questions

### What causes ThreadAlreadyExistsError in aisuite?

`ThreadAlreadyExistsError` occurs when you call `Runner.run()` with a `thread_id` that already exists in the provided `StateStore`. According to the implementation in [`aisuite/agents/runner.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/agents/runner.py) (lines 38-45), the framework checks for existing state before execution and raises this error to prevent you from overwriting an active conversation thread.

### How do I resume a conversation after ThreadAlreadyExistsError?

Catch the error and call `Runner.continue_sync()` instead, passing the same `thread_id` and `StateStore`. The continuation API loads the existing `RunState` from the store, appends your new user message, and executes the next agent turn without losing conversation history.

### Can I use a custom database for conversation state?

Yes. Implement the `StateStore` protocol defined in [`aisuite/agents/state_store.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/agents/state_store.py) (lines 53-64) with `save_state()`, `load_state()`, and `delete_state()` methods. You can then pass your custom store to `Runner.run()` or `Runner.continue_sync()`, allowing you to back conversation state with PostgreSQL, Redis, or any other database.

### What's the difference between InMemoryStateStore and FileStateStore?

`InMemoryStateStore` keeps conversation state in a Python dictionary and loses data when the process exits, making it suitable for testing. `FileStateStore` persists state as JSON files under `.<repo>/state/` using atomic write operations, enabling conversation durability across process restarts and deployments.