---
title: "Per-User Assistant"
description: "Create isolated AI assistants for each user with persistent state"
---

Build a system where each user gets their own AI assistant with isolated state, conversation history, and tools.

## Quick Start

Use `serve_many()` to create per-user agents:

```python
from ai_query.agents import ChatAgent, InMemoryAgent

class UserAssistant(ChatAgent, InMemoryAgent):
    system = "You are a personal assistant. Remember user preferences."
    initial_state = {"preferences": {}, "tasks": []}

# Each user gets their own assistant
UserAssistant.serve_many(port=8080)

# Connect:
# ws://localhost:8080/agent/user-alice/ws
# ws://localhost:8080/agent/user-bob/ws
```

## Complete Example: Personal Task Manager

A full per-user task management assistant:

```python
from ai_query.agents import ChatAgent, SQLiteAgent, AgentServer, AgentServerConfig
from ai_query import tool, Field
from ai_query.providers.google import google
from datetime import datetime

class PersonalAssistant(ChatAgent, SQLiteAgent):
    """Per-user AI assistant with persistent tasks and notes."""
    
    db_path = "./user_data.db"
    model = google("gemini-2.0-flash")
    system = """You are a personal productivity assistant.
    Help users manage tasks, take notes, and stay organized.
    Use tools to store and retrieve information."""
    
    initial_state = {
        "name": None,
        "preferences": {},
        "last_active": None,
    }
    
    async def on_start(self):
        # Create tables for this user
        self.sql("""
            CREATE TABLE IF NOT EXISTS tasks (
                id INTEGER PRIMARY KEY,
                title TEXT,
                done BOOLEAN DEFAULT 0,
                created_at TEXT
            )
        """)
        self.sql("""
            CREATE TABLE IF NOT EXISTS notes (
                id INTEGER PRIMARY KEY,
                content TEXT,
                created_at TEXT
            )
        """)
    
    @property
    def tools(self):
        @tool(description="Add a new task to the user's list")
        def add_task(title: str = Field(description="Task title")) -> str:
            self.sql(
                "INSERT INTO tasks (title, created_at) VALUES (?, ?)",
                title, datetime.now().isoformat()
            )
            return f"Added task: {title}"
        
        @tool(description="List all pending tasks")
        def list_tasks() -> str:
            rows = self.sql("SELECT id, title FROM tasks WHERE done = 0")
            if not rows:
                return "No pending tasks!"
            return "\n".join([f"{r['id']}. {r['title']}" for r in rows])
        
        @tool(description="Mark a task as complete")
        def complete_task(task_id: int = Field(description="Task ID")) -> str:
            self.sql("UPDATE tasks SET done = 1 WHERE id = ?", task_id)
            return f"Completed task #{task_id}"
        
        @tool(description="Save a note")
        def save_note(content: str = Field(description="Note content")) -> str:
            self.sql(
                "INSERT INTO notes (content, created_at) VALUES (?, ?)",
                content, datetime.now().isoformat()
            )
            return "Note saved!"
        
        @tool(description="Search notes")
        def search_notes(
            query: str = Field(description="Search query"),
        ) -> str:
            rows = self.sql(
                "SELECT content FROM notes WHERE content LIKE ?",
                f"%{query}%"
            )
            if not rows:
                return "No matching notes found."
            return "\n---\n".join([r['content'] for r in rows])
        
        @tool(description="Set a user preference")
        async def set_preference(
            key: str = Field(description="Preference name"),
            value: str = Field(description="Preference value"),
        ) -> str:
            prefs = {**self.state["preferences"], key: value}
            await self.set_state({**self.state, "preferences": prefs})
            return f"Set {key} = {value}"
        
        return {
            "add_task": add_task,
            "list_tasks": list_tasks,
            "complete_task": complete_task,
            "save_note": save_note,
            "search_notes": search_notes,
            "set_preference": set_preference,
        }
    
    async def on_connect(self, connection, ctx):
        await super().on_connect(connection, ctx)
        user_id = self.id.replace("user-", "")
        
        # Personalized greeting
        name = self.state.get("name") or user_id
        pending = self.sql("SELECT COUNT(*) as c FROM tasks WHERE done = 0")[0]['c']
        
        greeting = f"Welcome back, {name}!"
        if pending > 0:
            greeting += f" You have {pending} pending tasks."
        
        await connection.send(greeting)
    
    async def on_message(self, connection, message):
        # Update last active time
        await self.set_state({
            **self.state,
            "last_active": datetime.now().isoformat(),
        })
        
        # Stream AI response
        response = await self.stream_chat_sse(message)
        await connection.send(response)


if __name__ == "__main__":
    config = AgentServerConfig(
        idle_timeout=1800,  # 30 min idle timeout
        max_agents=100,
        enable_rest_api=True,
    )
    AgentServer(PersonalAssistant, config=config).serve(port=8080)
```

## With Authentication

Secure per-user access:

```python
from ai_query.agents import AgentServer, AgentServerConfig

async def auth_middleware(request):
    """Verify user owns this agent."""
    token = request.headers.get("Authorization", "").replace("Bearer ", "")
    user = await verify_jwt(token)
    
    if not user:
        return False
    
    # Ensure user can only access their own agent
    agent_id = request.match_info["agent_id"]
    return agent_id == f"user-{user.id}"

config = AgentServerConfig(
    auth=auth_middleware,
    idle_timeout=3600,
)

AgentServer(PersonalAssistant, config=config).serve(port=8080)
```

## Client Example

```javascript
// JavaScript client for per-user assistant
const userId = 'alice';
const ws = new WebSocket(`ws://localhost:8080/agent/user-${userId}/ws`);
const sse = new EventSource(`http://localhost:8080/agent/user-${userId}/events`);

ws.onmessage = (e) => console.log('Assistant:', e.data);

// AI streaming via SSE
sse.addEventListener('ai_chunk', (e) => process.stdout.write(e.data));
sse.addEventListener('ai_end', () => console.log());

// Send messages
ws.send("Add a task: Review quarterly report");
ws.send("What tasks do I have?");
ws.send("Set my preference for theme to dark");
```

## Persistence Options

Choose the right storage backend:

| Backend | Use Case |
|---------|----------|
| `InMemoryAgent` | Development, testing |
| `SQLiteAgent` | Single-server, local persistence |
| `DurableObjectAgent` | Cloudflare Workers, global edge |
| Custom Redis/Postgres | Shared state, horizontal scaling |

## Tips

<AccordionGroup>
  <Accordion title="Use meaningful agent IDs">
    Use `user-{user_id}` or `session-{session_id}` patterns for clear identification.
  </Accordion>
  
  <Accordion title="Set appropriate idle timeouts">
    Balance memory usage vs user experience. Longer for active users.
  </Accordion>
  
  <Accordion title="Personalize the experience">
    Use stored preferences and history to make each assistant feel personal.
  </Accordion>
  
  <Accordion title="Implement proper auth">
    Always verify users can only access their own agents in production.
  </Accordion>
</AccordionGroup>
