# Thread Management Reference

Patterns for managing conversation state and multi-turn interactions.

## Overview

`AgentThread` links agent execution to server-side conversation state, enabling:
- Multi-turn conversations with context
- Conversation persistence and resumption
- Thread-based message history

---

## Creating and Using Threads

### Basic Multi-Turn Conversation

```python
from agent_framework.azure import AzureAIAgentsProvider
from azure.identity.aio import AzureCliCredential

async with (
    AzureCliCredential() as credential,
    AzureAIAgentsProvider(credential=credential) as provider,
):
    agent = await provider.create_agent(
        name="ChatAgent",
        instructions="You are a helpful assistant.",
    )
    
    # Create a new thread for the conversation
    thread = agent.get_new_thread()
    
    # First turn
    result1 = await agent.run("My name is Alice", thread=thread)
    print(f"Agent: {result1.text}")
    
    # Second turn - agent remembers the name
    result2 = await agent.run("What's my name?", thread=thread)
    print(f"Agent: {result2.text}")
    
    # Third turn - context continues
    result3 = await agent.run("Tell me a joke about my name", thread=thread)
    print(f"Agent: {result3.text}")
```

### Accessing Thread Information

```python
thread = agent.get_new_thread()

# Run a conversation
await agent.run("Hello!", thread=thread)

# Access the conversation ID for persistence
print(f"Conversation ID: {thread.conversation_id}")

# Thread also tracks service-side thread ID (for Azure AI agents)
print(f"Service Thread ID: {thread.service_thread_id}")
```

---

## Conversation Persistence

### Saving Thread ID

```python
import json

async def save_conversation(thread, filepath: str):
    """Save thread ID for later resumption."""
    data = {
        "conversation_id": thread.conversation_id,
        "service_thread_id": thread.service_thread_id,
    }
    with open(filepath, "w") as f:
        json.dump(data, f)

# Usage
thread = agent.get_new_thread()
await agent.run("Start a conversation", thread=thread)
await save_conversation(thread, "conversation.json")
```

### Resuming Conversations

For Azure AI agents with persistent server-side threads, you can resume conversations:

```python
import json
from agent_framework import AgentThread

async def load_and_resume(provider, agent_id: str, filepath: str):
    """Resume a previous conversation."""
    with open(filepath) as f:
        data = json.load(f)
    
    # Get the existing agent
    agent = await provider.get_agent(agent_id=agent_id)
    
    # Create thread with existing service thread ID
    thread = AgentThread(service_thread_id=data["service_thread_id"])
    
    # Continue the conversation
    result = await agent.run("Continue our conversation", thread=thread)
    return result
```

---

## Thread with Streaming

Threads work the same way with streaming responses:

```python
thread = agent.get_new_thread()

# First turn - streaming
print("Agent: ", end="", flush=True)
async for chunk in agent.run_stream("Tell me about Python", thread=thread):
    if chunk.text:
        print(chunk.text, end="", flush=True)
print()

# Second turn - non-streaming (context maintained)
result = await agent.run("What was that language again?", thread=thread)
print(f"Agent: {result.text}")

# Third turn - streaming again
print("Agent: ", end="", flush=True)
async for chunk in agent.run_stream("Give me a code example", thread=thread):
    if chunk.text:
        print(chunk.text, end="", flush=True)
print()
```

---

## Thread with Tools

Tools work seamlessly within threaded conversations:

```python
from typing import Annotated
from pydantic import Field

def search_database(
    query: Annotated[str, Field(description="Search query")]
) -> str:
    """Search the database for information."""
    return f"Results for '{query}': Item A, Item B, Item C"

def get_item_details(
    item_name: Annotated[str, Field(description="Name of the item")]
) -> str:
    """Get details for a specific item."""
    return f"Details for {item_name}: Price $99, In Stock: Yes"

async with (
    AzureCliCredential() as credential,
    AzureAIAgentsProvider(credential=credential) as provider,
):
    agent = await provider.create_agent(
        name="ShoppingAgent",
        instructions="Help users find and learn about products.",
        tools=[search_database, get_item_details],
    )
    
    thread = agent.get_new_thread()
    
    # Turn 1: Search
    result1 = await agent.run("Search for laptops", thread=thread)
    print(f"Agent: {result1.text}")
    
    # Turn 2: Follow-up (context aware)
    result2 = await agent.run("Tell me more about Item A", thread=thread)
    print(f"Agent: {result2.text}")
    
    # Turn 3: Another follow-up
    result3 = await agent.run("Is it available?", thread=thread)
    print(f"Agent: {result3.text}")
```

---

## Multiple Parallel Conversations

Handle multiple users/conversations simultaneously:

```python
async def handle_user_session(provider, user_id: str, messages: list[str]):
    """Handle a single user's conversation."""
    agent = await provider.create_agent(
        name=f"Agent-{user_id}",
        instructions="You are a helpful assistant.",
    )
    
    # Each user gets their own thread
    thread = agent.get_new_thread()
    
    for message in messages:
        result = await agent.run(message, thread=thread)
        print(f"[{user_id}] User: {message}")
        print(f"[{user_id}] Agent: {result.text}")

# Handle multiple users concurrently
import asyncio

async def main():
    async with (
        AzureCliCredential() as credential,
        AzureAIAgentsProvider(credential=credential) as provider,
    ):
        await asyncio.gather(
            handle_user_session(provider, "user1", ["Hello", "What's 2+2?"]),
            handle_user_session(provider, "user2", ["Hi there", "Tell me a joke"]),
            handle_user_session(provider, "user3", ["Good morning", "Weather today?"]),
        )
```

---

## Thread Best Practices

### Do's

```python
# ✅ Create a new thread for each logical conversation
thread = agent.get_new_thread()

# ✅ Pass the same thread to maintain context
await agent.run("Message 1", thread=thread)
await agent.run("Message 2", thread=thread)

# ✅ Save thread IDs for conversations that need resumption
conversation_id = thread.conversation_id
```

### Don'ts

```python
# ❌ Don't create a new thread for each message (loses context)
for msg in messages:
    thread = agent.get_new_thread()  # Wrong!
    await agent.run(msg, thread=thread)

# ❌ Don't share threads between different agents
agent1_thread = agent1.get_new_thread()
await agent2.run("Hello", thread=agent1_thread)  # May cause issues

# ❌ Don't forget to pass the thread (single-turn only)
await agent.run("Message 1")  # No thread - no context saved
await agent.run("Message 2")  # Can't reference previous message
```

---

## Thread Lifecycle

```
1. agent.get_new_thread()
   └── Creates new AgentThread object
   └── Server-side thread created on first run

2. agent.run(..., thread=thread)
   └── Message added to thread
   └── Agent response added to thread
   └── Context accumulated

3. (Optional) Save thread.conversation_id
   └── For later resumption

4. (Optional) Resume with AgentThread(service_thread_id=...)
   └── Continues existing conversation
```

---

## Stateless vs Stateful Patterns

### Stateless (No Thread)

Each call is independent:

```python
# Good for one-shot queries
result = await agent.run("What is 2+2?")
```

### Stateful (With Thread)

Context persists across calls:

```python
# Good for conversations
thread = agent.get_new_thread()
result1 = await agent.run("My favorite color is blue", thread=thread)
result2 = await agent.run("What's my favorite color?", thread=thread)  # Knows it's blue
```
