# Conversational vs Stateless Agents

## Overview

Broadie now supports two modes of operation for agents:

1. **Conversational Mode** (`conversational=True`) - Agents maintain conversation history
2. **Stateless Mode** (`conversational=False`) - Agents process each request independently

## Key Features

### Conversational Agents (Default for Main Agents)
- ✅ Maintain conversation history across multiple turns
- ✅ Reference previous messages and maintain context
- ✅ Ideal for: chatbots, assistants, customer support agents
- ✅ History stored in SQLAlchemy-backed persistence (SQLite/PostgreSQL/etc.)

### Stateless Agents (Default for Sub-Agents)
- ✅ Process each request independently without history
- ✅ Faster processing (no history retrieval)
- ✅ Ideal for: classifiers, analyzers, one-off tasks (e.g., phishing detection, sentiment analysis)
- ✅ Still saves messages for audit trail

## Usage

### Creating a Conversational Agent

```python
from broadie.factory import create_agent

agent = create_agent(
    name='chat_assistant',
    instruction='You are a helpful conversational assistant.',
    conversational=True,  # Enable conversation history (default)
    history_turns=10      # Include last 10 message exchanges
)
```

### Creating a Stateless Agent

```python
from broadie.factory import create_agent

classifier = create_agent(
    name='phishing_detector',
    instruction='Analyze emails for phishing indicators.',
    conversational=False,  # Disable conversation history
    history_turns=0        # No history needed
)
```

## Smart Content Extraction

For agents with structured outputs (Pydantic models), the system automatically extracts human-readable summaries for conversation history:

### Example: Phishing Detection Agent

```python
from pydantic import BaseModel, Field
from broadie.factory import create_agent

class PhishingOutput(BaseModel):
    classification: str
    risk_score: int
    message_to_recipient: str
    justification: str

phishing_agent = create_agent(
    name='phish_guardian',
    instruction='Analyze emails for phishing risks.',
    output_schema=PhishingOutput,
    conversational=False  # Stateless - each email analyzed independently
)
```

The system intelligently extracts the `message_to_recipient` and `justification` fields for any future conversation history (if you later enable conversational mode).

## How It Works

### Conversational Mode Flow:
1. Agent receives message
2. **Fetches conversation history** from database (last N turns)
3. Injects explicit context instructions to LLM
4. Includes all previous messages in context
5. LLM processes with full conversation awareness
6. Saves response with clean content extraction

### Stateless Mode Flow:
1. Agent receives message
2. **Skips history retrieval** entirely
3. Tells LLM to "Process this request independently"
4. LLM processes without context
5. Saves response for audit trail only

## Performance Benefits

| Aspect | Conversational | Stateless |
|--------|---------------|-----------|
| History Retrieval | Yes | **No** ✅ |
| Database Queries | 1-2 per request | 0 per request ✅ |
| Token Usage | Higher (includes history) | **Lower** ✅ |
| Processing Speed | Slower | **Faster** ✅ |
| Use Case | Multi-turn conversations | Single-shot tasks ✅ |

## Configuration Options

### Agent-Level Configuration

```python
agent = create_agent(
    name='my_agent',
    instruction='...',
    conversational=True,   # Enable/disable conversation history
    history_turns=10,      # Number of previous turns to include (if conversational=True)
)
```

### Runtime Override

```python
# Override history turns at runtime
response = await agent.run(
    message="Hello",
    thread_id=thread_id,
    user_id=user_id,
    history_turns=20  # Override default
)
```

### Environment Configuration

Set defaults in `.env`:
```bash
CLI_HISTORY_TURNS=10  # Default for CLI chat mode
```

## Best Practices

### Use Conversational Mode For:
- ✅ Chatbots and virtual assistants
- ✅ Customer support agents
- ✅ Multi-turn problem solving
- ✅ Personalized interactions

### Use Stateless Mode For:
- ✅ Classification tasks (sentiment, spam, phishing)
- ✅ Data extraction and transformation
- ✅ One-shot analysis tasks
- ✅ High-throughput processing
- ✅ Independent request handling

## Examples

### Example 1: Conversational Chat Assistant

```python
import asyncio
import uuid
from broadie.factory import create_agent

async def main():
    agent = create_agent(
        name='helper',
        instruction='You are a helpful assistant.',
        conversational=True,
        history_turns=10
    )

    # Initialize
    await agent.init_checkpointer()
    await agent.init_store()

    thread_id = str(uuid.uuid4())

    # First message
    r1 = await agent.run('My name is Alice', thread_id=thread_id)
    print(r1)  # "Nice to meet you, Alice!"

    # Second message - agent remembers!
    r2 = await agent.run('What is my name?', thread_id=thread_id)
    print(r2)  # "Your name is Alice."

asyncio.run(main())
```

### Example 2: Stateless Email Classifier

```python
import asyncio
from broadie.factory import create_agent

async def main():
    classifier = create_agent(
        name='spam_detector',
        instruction='Classify emails as spam or legitimate.',
        conversational=False  # Stateless!
    )

    await classifier.init_checkpointer()

    # Each request processed independently
    r1 = await classifier.run('Buy now! Limited offer!')
    print(r1)  # "Classification: SPAM"

    r2 = await classifier.run('Meeting agenda for tomorrow')
    print(r2)  # "Classification: LEGITIMATE"

asyncio.run(main())
```

## Database Storage

All messages are stored in the `messages` table regardless of conversational mode:

| Field | Description |
|-------|-------------|
| `id` | Unique message ID |
| `namespace` | Thread ID |
| `key` | Message key |
| `value` | Message content (JSON) |
| `created_at` | Timestamp |
| `updated_at` | Timestamp |

**Conversational agents** retrieve and use this data for context.
**Stateless agents** only write to this table for audit purposes.

## Migration Guide

### Existing Agents (Backward Compatible)

All existing agents default to `conversational=True`, so behavior is unchanged:

```python
# Before (implicit conversational=True)
agent = create_agent(name='helper', instruction='...')

# After (explicit, same behavior)
agent = create_agent(
    name='helper',
    instruction='...',
    conversational=True  # Explicitly stated
)
```

### Converting to Stateless

For classification/analysis agents, simply add `conversational=False`:

```python
# Old: Unnecessary history overhead
phishing_agent = create_agent(
    name='phish_guardian',
    instruction='Analyze emails for phishing.',
    output_schema=PhishingOutput
)

# New: Optimized stateless mode
phishing_agent = create_agent(
    name='phish_guardian',
    instruction='Analyze emails for phishing.',
    output_schema=PhishingOutput,
    conversational=False  # ✅ Faster, no history overhead
)
```

## Logging

Watch the logs to see which mode is active:

```
INFO - [Conversational Mode] Retrieved 4 history items for thread abc12345...
INFO -   Adding to context: [user] My name is Alice...
INFO -   Adding to context: [assistant] Nice to meet you, Alice!...
```

or

```
INFO - [Stateless Mode] Skipping conversation history for thread abc12345...
INFO - Additional instructions: Process this request independently without conversation history.
```

## Summary

The conversational flag gives you fine-grained control over agent behavior:

- **Conversational agents** (`conversational=True`) maintain context and memory
- **Stateless agents** (`conversational=False`) process independently for speed
- Smart content extraction ensures clean history for structured outputs
- Full SQLAlchemy-backed persistence for both modes
- Backward compatible with existing code
