Managing Conversation State and Handling ThreadAlreadyExistsError in aisuite
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 and 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 (lines 38-45), the _run_impl method performs this validation:
# 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 (lines 53-64), which specifies three required operations:
save_state(thread_id, run_state): Persists the conversation stateload_state(thread_id): Retrieves existing state or returnsNonedelete_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 (lines 30-33) as a subclass of RuntimeError:
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 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():
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 (lines 84-92) validates that both state_store and thread_id are provided together:
# 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, 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:
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:
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:
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.
ThreadAlreadyExistsErroris raised inaisuite/agents/runner.pywhenRunner.run()detects an existing state entry, preventing accidental overwrites.- Use
Runner.continue_sync()with the samethread_idandStateStoreto resume conversations safely. - The
StateStoreprotocol supports custom implementations, with built-inInMemoryStateStorefor testing andFileStateStorefor 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 (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 (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.
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 →