Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.conversimple.com/llms.txt

Use this file to discover all available pages before exploring further.

Overview

Effective state management is crucial for building complex voice agents. This guide covers strategies for managing both conversation-specific and persistent state.

Conversation State

Per-Conversation State

Store data specific to each conversation:
from conversimple import ConversimpleAgent, tool

class StatefulAgent(ConversimpleAgent):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        # Dictionary to store conversation-specific state
        self.conversations = {}

    def on_conversation_started(self, conversation_id: str):
        """Initialize state for new conversation"""
        self.conversations[conversation_id] = {
            "customer_id": None,
            "authenticated": False,
            "cart": [],
            "preferences": {},
            "conversation_history": [],
            "created_at": datetime.now()
        }

    def get_state(self, conversation_id: str) -> dict:
        """Get state for a conversation"""
        return self.conversations.get(conversation_id, {})

    def update_state(self, conversation_id: str, **updates):
        """Update conversation state"""
        if conversation_id in self.conversations:
            self.conversations[conversation_id].update(updates)

    def on_conversation_ended(self, conversation_id: str):
        """Clean up conversation state"""
        if conversation_id in self.conversations:
            # Optionally persist state before deleting
            state = self.conversations.pop(conversation_id)
            self.persist_conversation(conversation_id, state)

State in Tools

Access and modify state within tools:
@tool("Add item to cart")
def add_to_cart(self, product_id: str, quantity: int = 1) -> dict:
    """Add item to cart"""
    conv_id = self.current_conversation_id
    state = self.get_state(conv_id)

    if not state.get("authenticated"):
        return {"error": "Please log in first"}

    # Update cart
    state["cart"].append({
        "product_id": product_id,
        "quantity": quantity,
        "added_at": datetime.now()
    })

    return {
        "success": True,
        "cart_size": len(state["cart"])
    }

@tool("Get cart total")
def get_cart_total(self) -> dict:
    """Calculate cart total"""
    conv_id = self.current_conversation_id
    state = self.get_state(conv_id)

    total = sum(
        item.get("price", 0) * item.get("quantity", 1)
        for item in state.get("cart", [])
    )

    return {"total": total, "items": len(state["cart"])}

Persistent State

Database Storage

Store state in a database for persistence:
import sqlite3
import json

class PersistentAgent(ConversimpleAgent):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.db = sqlite3.connect('conversations.db')
        self.create_tables()

    def create_tables(self):
        """Create database tables"""
        self.db.execute('''
            CREATE TABLE IF NOT EXISTS conversation_state (
                conversation_id TEXT PRIMARY KEY,
                customer_id TEXT,
                state JSON,
                created_at TIMESTAMP,
                updated_at TIMESTAMP
            )
        ''')
        self.db.commit()

    def save_state(self, conversation_id: str, state: dict):
        """Save state to database"""
        self.db.execute('''
            INSERT OR REPLACE INTO conversation_state
            (conversation_id, customer_id, state, updated_at)
            VALUES (?, ?, ?, ?)
        ''', (
            conversation_id,
            state.get('customer_id'),
            json.dumps(state),
            datetime.now()
        ))
        self.db.commit()

    def load_state(self, conversation_id: str) -> dict:
        """Load state from database"""
        cursor = self.db.execute(
            'SELECT state FROM conversation_state WHERE conversation_id = ?',
            (conversation_id,)
        )
        row = cursor.fetchone()
        return json.loads(row[0]) if row else {}

    def on_conversation_ended(self, conversation_id: str):
        """Persist state on conversation end"""
        state = self.conversations.get(conversation_id, {})
        self.save_state(conversation_id, state)

Redis for Fast Access

Use Redis for high-performance state storage:
import redis
import json

class RedisAgent(ConversimpleAgent):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.redis = redis.Redis(
            host='localhost',
            port=6379,
            db=0,
            decode_responses=True
        )

    def save_state(self, conversation_id: str, state: dict):
        """Save state to Redis"""
        key = f"conversation:{conversation_id}"
        self.redis.setex(
            key,
            86400,  # Expire after 24 hours
            json.dumps(state)
        )

    def load_state(self, conversation_id: str) -> dict:
        """Load state from Redis"""
        key = f"conversation:{conversation_id}"
        data = self.redis.get(key)
        return json.loads(data) if data else {}

    @tool("Get customer history")
    def get_customer_history(self, customer_id: str) -> dict:
        """Load customer data from Redis"""
        key = f"customer:{customer_id}"
        data = self.redis.get(key)
        return json.loads(data) if data else {"orders": [], "preferences": {}}

State Patterns

Session Pattern

Manage authentication and session data:
class SessionAgent(ConversimpleAgent):
    @tool("Login")
    def login(self, username: str, password: str) -> dict:
        """Authenticate user"""
        conv_id = self.current_conversation_id

        # Verify credentials
        user = auth_service.authenticate(username, password)

        if user:
            # Store session data
            self.update_state(conv_id,
                authenticated=True,
                user_id=user.id,
                username=user.username,
                session_started=datetime.now()
            )

            return {"success": True, "user": user.username}

        return {"success": False, "error": "Invalid credentials"}

    @tool("Logout")
    def logout(self) -> dict:
        """End session"""
        conv_id = self.current_conversation_id
        self.update_state(conv_id,
            authenticated=False,
            user_id=None
        )
        return {"success": True}

Workflow Pattern

Track multi-step processes:
class WorkflowAgent(ConversimpleAgent):
    def on_conversation_started(self, conversation_id: str):
        """Initialize workflow state"""
        super().on_conversation_started(conversation_id)
        self.update_state(conversation_id,
            workflow_step="initial",
            workflow_data={}
        )

    @tool("Start booking")
    def start_booking(self) -> dict:
        """Start booking workflow"""
        conv_id = self.current_conversation_id
        self.update_state(conv_id, workflow_step="collecting_dates")
        return {"message": "When would you like to book?"}

    @tool("Set dates")
    def set_dates(self, check_in: str, check_out: str) -> dict:
        """Set booking dates"""
        conv_id = self.current_conversation_id
        state = self.get_state(conv_id)

        state["workflow_data"]["check_in"] = check_in
        state["workflow_data"]["check_out"] = check_out

        self.update_state(conv_id, workflow_step="collecting_guests")
        return {"message": "How many guests?"}

    @tool("Confirm booking")
    def confirm_booking(self) -> dict:
        """Complete booking workflow"""
        conv_id = self.current_conversation_id
        state = self.get_state(conv_id)

        if state.get("workflow_step") != "ready_to_confirm":
            return {"error": "Please complete all booking details first"}

        # Process booking
        booking = booking_service.create(state["workflow_data"])

        self.update_state(conv_id,
            workflow_step="completed",
            booking_id=booking.id
        )

        return {"success": True, "booking_id": booking.id}

Cache Pattern

Cache expensive operations:
from functools import lru_cache
from datetime import timedelta

class CachedAgent(ConversimpleAgent):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.cache = {}
        self.cache_ttl = timedelta(minutes=5)

    def get_cached(self, key: str, default=None):
        """Get cached value"""
        entry = self.cache.get(key)

        if entry:
            if datetime.now() - entry["timestamp"] < self.cache_ttl:
                return entry["value"]
            else:
                del self.cache[key]

        return default

    def set_cached(self, key: str, value):
        """Set cached value"""
        self.cache[key] = {
            "value": value,
            "timestamp": datetime.now()
        }

    @tool("Get product details")
    def get_product(self, product_id: str) -> dict:
        """Get product with caching"""
        cache_key = f"product:{product_id}"

        # Try cache first
        cached = self.get_cached(cache_key)
        if cached:
            return {**cached, "cached": True}

        # Fetch from database
        product = database.get_product(product_id)
        self.set_cached(cache_key, product)

        return product

State Synchronization

Cross-Conversation State

Share data across multiple conversations for the same customer:
class CustomerStateAgent(ConversimpleAgent):
    @tool("Get customer context")
    def get_customer_context(self, customer_id: str) -> dict:
        """Load shared customer state"""
        # Load from persistent storage
        customer_data = self.redis.get(f"customer:{customer_id}")

        # Update current conversation
        conv_id = self.current_conversation_id
        self.update_state(conv_id,
            customer_id=customer_id,
            customer_data=customer_data
        )

        return customer_data

    def on_conversation_ended(self, conversation_id: str):
        """Update shared customer state"""
        state = self.get_state(conversation_id)

        if state.get("customer_id"):
            # Update customer profile with conversation data
            customer_id = state["customer_id"]
            customer_key = f"customer:{customer_id}"

            # Merge conversation data into customer profile
            profile = self.redis.get(customer_key) or {}
            profile["last_conversation"] = conversation_id
            profile["last_interaction"] = datetime.now().isoformat()

            self.redis.set(customer_key, json.dumps(profile))

Best Practices

1. Keep State Minimal

# ❌ Bad - storing too much
state = {
    "entire_product_catalog": products,  # Don't store large data
    "all_customer_orders": orders        # Store IDs instead
}

# ✅ Good - storing references
state = {
    "selected_product_ids": ["P123", "P456"],
    "recent_order_id": "ORD789"
}

2. Clean Up State

def on_conversation_ended(self, conversation_id: str):
    """Always clean up"""
    # Save important data
    state = self.conversations.get(conversation_id)
    if state and state.get("customer_id"):
        self.save_important_data(state)

    # Remove from memory
    self.conversations.pop(conversation_id, None)

3. Handle State Errors

@tool("Get cart")
def get_cart(self) -> dict:
    """Safely access state"""
    try:
        conv_id = self.current_conversation_id
        state = self.get_state(conv_id)
        return {"cart": state.get("cart", [])}
    except Exception as e:
        logger.error(f"Error getting cart: {e}")
        return {"cart": [], "error": "Could not load cart"}

Next Steps

Multi-Conversation

Manage multiple conversations

Best Practices

Production best practices