---
title: "Stateful Agents"
description: "Build persistent, stateful AI agents with built-in storage and WebSocket support"
---

ai-query provides a powerful `Agent` class for building stateful AI applications. Unlike standard LLM calls, **Agents** maintain memory, state, and identity over time.

## Quick Start

```python
from ai_query.agents import Agent
from ai_query.providers import openai

agent = Agent("assistant", model=openai("gpt-4o"))

async with agent:
    response = await agent.chat("Hello!")
    print(response)

    response = await agent.chat("What did I just say?")
    print(response)  # Agent remembers the conversation
```

## 30 Seconds to Your First Agent

```python
from ai_query.agents import Agent
from ai_query.providers import openai

agent = Agent("my-agent", model=openai("gpt-4o"))

async with agent:
    print(await agent.chat("Hello!"))
```

## 60 Seconds to Persistence

```python
from ai_query.agents import Agent, SQLiteStorage
from ai_query.providers import openai

agent = Agent(
    "my-agent",
    model=openai("gpt-4o"),
    storage=SQLiteStorage("agents.db")
)

async with agent:
    print(await agent.chat("Remember: my favorite color is blue"))

# Later, in a new process...
agent = Agent("my-agent", storage=SQLiteStorage("agents.db"))
async with agent:
    print(await agent.chat("What's my favorite color?"))  # "blue"
```

## Core Concepts

### State & Messages

Every agent has a `state` dictionary and a `messages` list that are automatically persisted.

```python
async with agent:
    # Set state (automatically persisted)
    await agent.set_state({"user_name": "Alice", "preferences": {}})

    # Update state
    await agent.update_state(mood="happy")

    # Access state
    print(agent.state)  # {"user_name": "Alice", "preferences": {}, "mood": "happy"}

    # Access conversation history
    print(agent.messages)

    # Clear conversation
    await agent.clear()
```

### Typed State (Pydantic)

For better DX, you can define your state structure directly in the class body. This provides IDE autocompletion and automatic validation.

```python
from pydantic import BaseModel
from ai_query.agents import Agent

class UserState(BaseModel):
    name: str = "Anonymous"
    age: int | None = None

class MyAgent(Agent):
    state = UserState  # Blueprint

    async def update_profile(self, name: str):
        # Full autocomplete for .name!
        await self.update_state(name=name)
```

You can also use a plain dictionary as a blueprint:

```python
class CounterAgent(Agent):
    state = {"count": 0}

    async def increment(self):
        await self.update_state(count=self.state["count"] + 1)
```

### Durability & Event Replay

A common challenge with real-time agents is network stability. If a client disconnects while the agent is streaming a response, that data is typically lost.

**ai-query** provides **Durable Event Logging** to solve this. When `enable_event_log` is set to `True`, every event emitted by the agent is persisted to storage.

```python
class MyDurableAgent(Agent):
    enable_event_log = True  # <--- Persist every event
    storage = SQLiteStorage("agents.db")

async with MyDurableAgent("researcher") as agent:
    await agent.emit("status", {"text": "Starting research..."})
```

**Benefits of Durability:**

1.  **Resilience**: Research or long-running tasks continue even if all clients disconnect.
2.  **Automatic Replay**: When a client reconnects with a `last_event_id`, the server automatically replays all missed events before continuing the live stream.
3.  **Audit Trail**: Every interaction and intermediate status update is saved for later analysis.

### Storage Backends

Choose where your data lives by passing a storage backend:

| Backend               | Description           | Best For                       |
| --------------------- | --------------------- | ------------------------------ |
| `MemoryStorage()`     | RAM-only (default)    | Testing, development           |
| `SQLiteStorage(path)` | Local file            | Single-server apps, prototypes |
| Custom                | Redis, DynamoDB, etc. | Production, distributed        |

```python
from ai_query.agents import Agent, MemoryStorage, SQLiteStorage

# In-memory (default)
agent = Agent("bot", storage=MemoryStorage())

# SQLite persistence
agent = Agent("bot", storage=SQLiteStorage("agents.db"))
```

### Custom Storage

Implement the `Storage` protocol to use any backend:

```python
from ai_query import Storage

class RedisStorage:
    def __init__(self, url: str):
        self.client = redis.from_url(url)

    async def get(self, key: str) -> Any | None:
        value = await self.client.get(key)
        return json.loads(value) if value else None

    async def set(self, key: str, value: Any) -> None:
        await self.client.set(key, json.dumps(value))

    async def delete(self, key: str) -> None:
        await self.client.delete(key)

    async def keys(self, prefix: str = "") -> list[str]:
        return await self.client.keys(f"{prefix}*")

# Use it
agent = Agent("bot", storage=RedisStorage("redis://localhost"))
```

### Tools

Give your agent capabilities:

```python
from ai_query.agents import Agent
from ai_query.providers import openai, tool, Field

@tool(description="Search the web")
async def search(query: str = Field(description="Search query")) -> str:
    return f"Results for: {query}"

@tool(description="Send an email")
async def send_email(
    to: str = Field(description="Recipient"),
    body: str = Field(description="Email body")
) -> str:
    return f"Email sent to {to}"

agent = Agent(
    "assistant",
    model=openai("gpt-4o"),
    system="You are a helpful assistant with access to search and email.",
    tools={"search": search, "send_email": send_email}
)

async with agent:
    response = await agent.chat("Search for the latest AI news and email a summary to bob@example.com")
```

### Streaming

Stream responses for real-time output:

```python
async with agent:
    async for chunk in agent.stream("Tell me a story"):
        print(chunk, end="", flush=True)
```

### Abort / Cancellation

Cancel long-running operations:

```python
from ai_query.agents import Agent, AbortController, AbortError

controller = AbortController()

try:
    async with agent:
        # Pass signal to chat
        response = await agent.chat(
            "Write a very long essay",
            signal=controller.signal
        )
except AbortError as e:
    print(f"Cancelled: {e.reason}")

# Abort from another task
controller.abort("User cancelled")
```

Use timeout for automatic cancellation:

```python
from ai_query import AbortSignal

async with agent:
    response = await agent.chat(
        "Complex question",
        signal=AbortSignal.timeout(30)  # Auto-cancel after 30s
    )
```

## Deployment Options

Once your agent is built, you can deploy it in three main ways:

1.  **Agent Server**: A standalone WebSocket/HTTP server for long-running processes.
2.  **Serverless**: Deploy to AWS Lambda, Vercel, or Cloud Run using Adapters.
3.  **Embedded**: Use the `Agent` class directly within your existing Python application (CLI, script, or framework).

See the [Deployment Guide](/core/deployment) for details.

## API Reference

<CardGroup cols={2}>
  <Card title="Agent API" icon="book" href="/api-reference/agent">
    Full methods and properties
  </Card>
  <Card
    title="Storage Backends"
    icon="database"
    href="/api-reference/agent-backends"
  >
    MemoryStorage, SQLiteStorage, and custom
  </Card>
  <Card title="Agent Server" icon="server" href="/core/agent-server">
    Hosting agents
  </Card>
  <Card title="Deployment" icon="rocket" href="/core/deployment">
    Serverless and production guides
  </Card>
</CardGroup>
