# 🎓 Qualtrics Sales Agentification - Zero to Mastery Guide

**Your complete learning path for multi-agent AI systems with Synqed**

---

## 📚 Table of Contents

1. [Fundamentals - What You Need to Know First](#1-fundamentals)
2. [Core Concepts](#2-core-concepts)
3. [API Reference - New Features](#3-api-reference)
4. [File Structure Walkthrough](#4-file-structure-walkthrough)
5. [Hands-On Learning Path](#5-hands-on-learning-path)
6. [Advanced Topics](#6-advanced-topics)
7. [Troubleshooting & Best Practices](#7-troubleshooting)

---

## 1. Fundamentals

### 🤔 What is A2A (Agent-to-Agent)?

**A2A** is a standardized protocol that allows AI agents to:
- **Discover** each other's capabilities
- **Communicate** via HTTP endpoints
- **Delegate** tasks to specialists
- **Collaborate** on complex problems

Think of it like REST APIs, but specifically designed for AI agents to talk to each other.

**Key A2A Concepts:**
```
Agent Card (/.well-known/agent-card.json)
  ↓
Describes what the agent can do (skills, capabilities)

Task Delegation (POST /)
  ↓
One agent sends work to another agent

Response
  ↓
Agent returns completed work
```

### 🧰 What is Synqed?

**Synqed** is a Python library that makes A2A easy. It provides:
- Simple agent creation
- Automatic server setup
- Intelligent orchestration
- Collaborative workspaces

**Without Synqed (raw A2A):**
```python
# Manually create HTTP endpoints
# Handle agent card JSON
# Parse A2A protocol messages
# Coordinate agent communication
# 100+ lines of boilerplate code
```

**With Synqed:**
```python
agent = synqed.Agent(name="MyAgent", executor=my_function)
server = synqed.AgentServer(agent, port=8000)
await server.start()
# Done! 🎉
```

---

## 2. Core Concepts

### 🤖 The Agent

An **Agent** is an AI worker with:
- **Name**: Identifier (e.g., "ResearchAgent")
- **Description**: What it does
- **Skills**: Specific capabilities it has
- **Executor**: The actual logic (your Python function)

**Anatomy of an Agent:**

```python
async def my_agent_brain(context):
    """This is the executor - the agent's 'brain'"""
    user_input = context.get_user_input()  # Get the task
    
    # Do something smart (call LLM, query database, etc.)
    result = await call_openai(user_input)
    
    return result  # Return the result

agent = synqed.Agent(
    name="ResearchAgent",  # Who is this?
    description="Conducts research on any topic",  # What does it do?
    skills=[  # What can it do specifically?
        {
            "skill_id": "web_research",
            "name": "Web Research",
            "description": "Searches and analyzes web content",
            "tags": ["research", "web", "analysis"]
        }
    ],
    executor=my_agent_brain  # HOW does it work?
)
```

### 🌐 The Agent Server

An **AgentServer** makes your agent available over HTTP:

```python
server = synqed.AgentServer(agent, port=8001)
await server.start_background()  # Runs in background
# Now agent is accessible at http://localhost:8001
```

**What happens when you start a server:**
1. HTTP server starts on the port
2. Agent card becomes available at `/.well-known/agent-card.json`
3. Task endpoint becomes available at `/`
4. Other agents can now discover and talk to this agent

### 🎯 The Orchestrator

An **Orchestrator** is the "manager" that:
- **Analyzes** complex tasks
- **Breaks them down** into subtasks
- **Selects** the best agents for each subtask
- **Coordinates** execution
- **Synthesizes** final results

```python
orchestrator = synqed.Orchestrator(
    provider=synqed.LLMProvider.OPENAI,
    api_key=os.getenv("OPENAI_API_KEY"),
    model="gpt-4o",  # Needs powerful model for planning
    temperature=0.3  # Lower = more focused
)
```

**Orchestrator uses an LLM to make intelligent decisions!** It's AI coordinating other AIs.

### 🏗️ The Orchestrated Workspace

An **OrchestratedWorkspace** is where the magic happens:
- Registers multiple agents
- Receives complex tasks
- Lets orchestrator plan execution
- Enables agent collaboration
- Tracks all messages and results

```python
workspace = synqed.OrchestratedWorkspace(
    orchestrator=orchestrator,
    enable_agent_discussion=True,  # Agents can talk to each other
    workspace_persistence=True,     # Save conversation history
    message_callback=my_callback    # Real-time message display
)

# Register agents
workspace.register_agent(research_agent)
workspace.register_agent(content_agent)

# Execute complex task
result = await workspace.execute_task(
    task="Research AI trends and create a report",
    workspace_name="AI Research Project"
)
```

---

## 3. API Reference - New Features

### 🆕 New APIs in Qualtrics Example

#### 1. **Agent Creation with Skills**

**NEW**: Skills are now structured dictionaries:

```python
agent = synqed.Agent(
    name="ResearchAgent",
    description="Brief description",
    skills=[
        {
            "skill_id": "unique_id",        # Unique identifier
            "name": "Display Name",          # Human-readable name
            "description": "What it does",   # Detailed description
            "tags": ["tag1", "tag2"]        # Searchable tags
        }
    ],
    executor=async_function
)
```

**Why skills matter:**
- Orchestrator uses them to match tasks to agents
- Tags help with intelligent routing
- Descriptions inform task decomposition

#### 2. **Agent Server Background Mode**

**NEW**: Start servers without blocking:

```python
server = synqed.AgentServer(agent, port=8001)
await server.start_background()  # Returns immediately, runs in background
print(f"Agent running at {agent.url}")

# Continue with other code...

# Later, stop it
await server.stop()
```

**Old way:**
```python
await server.start()  # Blocks forever ❌
```

#### 3. **OrchestratedWorkspace with Callbacks**

**NEW**: Real-time message monitoring:

```python
def message_callback(msg_dict):
    """Called every time agents communicate"""
    sender = msg_dict.get('sender_name', 'Unknown')
    content = msg_dict.get('content', '')
    print(f"{sender}: {content}")

workspace = synqed.OrchestratedWorkspace(
    orchestrator=orchestrator,
    enable_agent_discussion=True,
    message_callback=message_callback  # NEW!
)
```

**Message dict structure:**
```python
{
    'sender_name': 'ResearchAgent',
    'content': 'Completed research on topic...',
    'message_type': 'task_result',
    'timestamp': '2025-11-18T10:30:00',
    'metadata': {...}
}
```

#### 4. **Execution Result Object**

**NEW**: Rich result object with complete execution details:

```python
result = await workspace.execute_task(task="...")

# Access different parts
result.success                 # bool: Did it succeed?
result.final_result           # str: The synthesized answer
result.workspace_id           # str: Workspace identifier
result.workspace_messages     # list: ALL messages exchanged
result.plan                   # ExecutionPlan: How it was executed
result.subtask_results        # dict: Individual agent outputs
result.metadata               # dict: Execution statistics
```

**ExecutionPlan structure:**
```python
result.plan.subtasks          # List[Subtask]: All subtasks created
result.plan.selected_agents   # List[str]: Which agents were used
result.plan.execution_order   # List[List[str]]: Parallel execution stages
result.plan.reasoning         # str: Why orchestrator chose this plan
```

**Subtask structure:**
```python
subtask.subtask_id            # str: Unique ID
subtask.description           # str: What needs to be done
subtask.assigned_agent_name   # str: Which agent does it
subtask.status               # str: "pending", "in_progress", "completed"
subtask.order                # int: Execution order
subtask.dependencies         # List[str]: Which subtasks must finish first
```

#### 5. **Colored Display Utilities**

**NEW**: Beautiful colored output:

```python
colored_printer = synqed.create_colored_printer()

# Automatic color assignment by agent name
colored_printer.print_message(
    sender="ResearchAgent",
    content="Found relevant data..."
)
```

**Colors are automatically assigned:**
- Blue: Orchestrator
- Green: Research agents
- Yellow: Content agents
- Magenta: Admin agents
- Cyan: Pipeline agents
- Red: Deal agents

#### 6. **Workspace Persistence**

**NEW**: Keep conversation history:

```python
workspace = synqed.OrchestratedWorkspace(
    orchestrator=orchestrator,
    workspace_persistence=True  # NEW!
)

# After execution, access full history
result = await workspace.execute_task(task)
print(f"Total messages: {len(result.workspace_messages)}")

# Replay the conversation
for msg in result.workspace_messages:
    print(f"{msg['sender_name']}: {msg['content']}")
```

---

## 4. File Structure Walkthrough

Let's walk through the Qualtrics example step by step:

### 📋 Section 1: Setup (Lines 1-38)

```python
import asyncio
import os
from dotenv import load_dotenv
import synqed

load_dotenv()  # Load OPENAI_API_KEY from .env file
```

**What's happening:**
- Importing necessary libraries
- Loading environment variables (API keys)
- Setting up for async execution

### 🏗️ Section 2: Agent Creation (Lines 74-395)

**Pattern used for each agent:**

```python
# 1. Define executor function (the agent's brain)
async def create_agent_executor(system_prompt: str, temperature: float):
    async def executor(context):
        user_message = context.get_user_input()
        # Call OpenAI with the system prompt
        response = await client.chat.completions.create(...)
        return response.choices[0].message.content
    return executor

# 2. Create the agent
research_agent = synqed.Agent(
    name="ResearchAgent",
    description="What it does",
    skills=[...],  # List of specific capabilities
    executor=await create_agent_executor(system_prompt, temp)
)
```

**5 Specialized Agents Created:**

1. **ResearchAgent** (Port 8001)
   - Skills: internal_research, external_research, opportunity_identification
   - Purpose: Gathers customer data and market intelligence

2. **ContentAgent** (Port 8002)
   - Skills: presentation_creation, proposal_writing, demo_scripting
   - Purpose: Creates customer-facing materials

3. **AdminAgent** (Port 8003)
   - Skills: calendar_management, note_taking, crm_updates, follow_ups
   - Purpose: Handles administrative tasks

4. **PipelineAgent** (Port 8004)
   - Skills: prioritization, risk_detection, opportunity_tracking
   - Purpose: Analyzes deals and priorities

5. **DealAgent** (Port 8005)
   - Skills: quote_generation, qr_calculation, partner_identification, deal_optimization
   - Purpose: Creates and optimizes quotes

### 🚀 Section 3: Starting Servers (Lines 404-434)

```python
# Create server for each agent
research_server = synqed.AgentServer(research_agent, port=8001)
content_server = synqed.AgentServer(content_agent, port=8002)
# ... etc

# Start them in background
await research_server.start_background()
await content_server.start_background()
# ... etc
```

**What's happening:**
- Each agent gets its own HTTP server
- Servers run on different ports (8001-8005)
- All run in background (non-blocking)
- Agents can now be discovered via A2A protocol

### 🎯 Section 4: Orchestrator Creation (Lines 438-452)

```python
orchestrator = synqed.Orchestrator(
    provider=synqed.LLMProvider.OPENAI,
    api_key=api_key,
    model="gpt-4o",  # Powerful model for planning
    temperature=0.3
)
```

**Why GPT-4o?**
- Needs to understand complex tasks
- Breaks them into logical subtasks
- Selects appropriate agents
- More powerful = better planning

### 🏗️ Section 5: Workspace Creation (Lines 456-499)

```python
orchestrated_workspace = synqed.OrchestratedWorkspace(
    orchestrator=orchestrator,
    enable_agent_discussion=True,  # Agents can collaborate
    workspace_persistence=True,    # Save message history
    message_callback=message_callback  # Real-time display
)

# Register all agents
orchestrated_workspace.register_agent(research_agent)
orchestrated_workspace.register_agent(content_agent)
# ... etc
```

**What registration does:**
- Tells orchestrator about available agents
- Orchestrator learns their skills and capabilities
- Can now intelligently route tasks to them

### 🎬 Section 6: Scenario Execution (Lines 503-838)

**Scenario 1: Complex Multi-Agent Task**

```python
sales_request = """
I have a meeting tomorrow with TechCorp Inc...
1. Understand their current usage
2. Research their company
3. Create a pitch deck
4. Get a quote
5. Schedule follow-up
"""

result = await orchestrated_workspace.execute_task(
    task=sales_request,
    workspace_name="TechCorp Upsell Preparation"
)
```

**What happens internally:**

1. **Analysis Phase**
   - Orchestrator reads the complex request
   - Identifies 5 distinct subtasks

2. **Planning Phase**
   - Breaks request into subtasks
   - Determines dependencies (what must happen first)
   - Creates execution plan

3. **Agent Selection**
   - Matches each subtask to best agent
   - Example: "Research company" → ResearchAgent
   - Example: "Create pitch" → ContentAgent

4. **Discovery Phase** (A2A Protocol)
   - Orchestrator calls `GET /.well-known/agent-card.json` on each agent
   - Learns their exact capabilities

5. **Delegation Phase** (A2A Protocol)
   - Orchestrator sends `POST /` to each agent with subtask
   - Agents receive tasks programmatically

6. **Execution Phase**
   - Agents work in parallel where possible
   - Sequential when dependencies exist
   - Example: Research must complete before Content creates pitch

7. **Synthesis Phase**
   - Orchestrator collects all results
   - Uses LLM to synthesize coherent final answer
   - Returns comprehensive result object

**Scenario 2: Sequential Workflow (Lines 840-933)**

Demonstrates two agents with dependency:
- ResearchAgent → ContentAgent
- Content waits for research to complete

**Scenario 3: Parallel Execution (Lines 936-1040)**

Demonstrates true parallelism:
- PipelineAgent ∥ DealAgent
- Both work simultaneously (no dependencies)

### 📊 Section 7: Results Display (Lines 600-837)

```python
if result.success:
    # Show plan
    print(f"Total Subtasks: {len(result.plan.subtasks)}")
    
    # Show each subtask
    for subtask in result.plan.subtasks:
        print(f"{subtask.assigned_agent_name}: {subtask.description}")
    
    # Show workspace messages (full conversation)
    for msg in result.workspace_messages:
        print(f"{msg['sender_name']}: {msg['content']}")
    
    # Show final synthesized result
    print(result.final_result)
```

### 🧹 Section 8: Cleanup (Lines 1080-1104)

```python
# Gracefully stop all servers
await research_server.stop()
await content_server.stop()
# ... etc
```

**Important:** Always stop servers to free up ports!

---

## 5. Hands-On Learning Path

### 🌱 Level 1: Beginner - Single Agent (30 minutes)

**Goal:** Create and run your first agent

**Exercise 1: Hello World Agent**

```python
import asyncio
import synqed

async def simple_executor(context):
    """The simplest possible executor"""
    user_input = context.get_user_input()
    return f"Echo: {user_input}"

async def main():
    # Create agent
    agent = synqed.Agent(
        name="EchoAgent",
        description="Echoes back what you say",
        executor=simple_executor
    )
    
    # Start server
    server = synqed.AgentServer(agent, port=8000)
    print(f"Agent running at {agent.url}")
    await server.start()

asyncio.run(main())
```

**Run it:**
```bash
python hello_agent.py
```

**Test it (in another terminal):**
```python
import asyncio
import synqed

async def test():
    async with synqed.Client("http://localhost:8000") as client:
        response = await client.ask("Hello!")
        print(response)  # Should see: "Echo: Hello!"

asyncio.run(test())
```

**✅ Level 1 Complete when you can:**
- Create a basic agent
- Start a server
- Connect with a client
- Send and receive messages

### 🌿 Level 2: Intermediate - LLM-Powered Agent (1 hour)

**Goal:** Create an agent that uses an actual LLM

**Exercise 2: Smart Assistant Agent**

```python
import asyncio
import os
import synqed
from openai import AsyncOpenAI

async def llm_executor(context):
    """Agent that uses OpenAI"""
    user_input = context.get_user_input()
    
    client = AsyncOpenAI(api_key=os.getenv("OPENAI_API_KEY"))
    response = await client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[
            {"role": "system", "content": "You are a helpful assistant."},
            {"role": "user", "content": user_input}
        ]
    )
    
    return response.choices[0].message.content

async def main():
    agent = synqed.Agent(
        name="SmartAssistant",
        description="AI-powered assistant using GPT-4",
        skills=[
            {
                "skill_id": "general_qa",
                "name": "General Q&A",
                "description": "Answers general questions",
                "tags": ["questions", "information", "help"]
            }
        ],
        executor=llm_executor
    )
    
    server = synqed.AgentServer(agent, port=8000)
    print(f"Agent running at {agent.url}")
    await server.start()

asyncio.run(main())
```

**✅ Level 2 Complete when you can:**
- Integrate OpenAI (or other LLM)
- Define skills properly
- Handle API keys securely
- Create specialized agents

### 🌳 Level 3: Advanced - Two Agent Collaboration (2 hours)

**Goal:** Make two agents work together

**Exercise 3: Research + Writer Team**

```python
import asyncio
import os
import synqed
from openai import AsyncOpenAI

# Create executor factory
async def create_executor(system_prompt):
    async def executor(context):
        user_input = context.get_user_input()
        client = AsyncOpenAI(api_key=os.getenv("OPENAI_API_KEY"))
        response = await client.chat.completions.create(
            model="gpt-4o-mini",
            messages=[
                {"role": "system", "content": system_prompt},
                {"role": "user", "content": user_input}
            ],
            temperature=0.7
        )
        return response.choices[0].message.content
    return executor

async def main():
    # Agent 1: Researcher
    researcher = synqed.Agent(
        name="Researcher",
        description="Conducts research on topics",
        skills=[
            {
                "skill_id": "research",
                "name": "Research",
                "description": "Researches any topic",
                "tags": ["research", "information"]
            }
        ],
        executor=await create_executor(
            "You are a research specialist. Provide factual, detailed research."
        )
    )
    
    # Agent 2: Writer
    writer = synqed.Agent(
        name="Writer",
        description="Creates written content",
        skills=[
            {
                "skill_id": "writing",
                "name": "Content Writing",
                "description": "Writes articles and content",
                "tags": ["writing", "content", "articles"]
            }
        ],
        executor=await create_executor(
            "You are a content writer. Create engaging, well-structured content."
        )
    )
    
    # Start both servers
    research_server = synqed.AgentServer(researcher, port=8001)
    writer_server = synqed.AgentServer(writer, port=8002)
    
    await research_server.start_background()
    await writer_server.start_background()
    
    print(f"Researcher: {researcher.url}")
    print(f"Writer: {writer.url}")
    
    # Create orchestrator
    orchestrator = synqed.Orchestrator(
        provider=synqed.LLMProvider.OPENAI,
        api_key=os.getenv("OPENAI_API_KEY"),
        model="gpt-4o"
    )
    
    # Create workspace
    workspace = synqed.OrchestratedWorkspace(
        orchestrator=orchestrator,
        enable_agent_discussion=True
    )
    
    workspace.register_agent(researcher)
    workspace.register_agent(writer)
    
    # Execute task
    result = await workspace.execute_task(
        task="Research quantum computing and write a brief article about it",
        workspace_name="Quantum Computing Article"
    )
    
    print("\n=== RESULT ===")
    print(result.final_result)
    
    # Cleanup
    await research_server.stop()
    await writer_server.stop()

asyncio.run(main())
```

**✅ Level 3 Complete when you can:**
- Run multiple agents simultaneously
- Create an orchestrator
- Use OrchestratedWorkspace
- See agents collaborating automatically

### 🌲 Level 4: Expert - Full Multi-Agent System (3+ hours)

**Goal:** Build a complete 3+ agent system with all features

**Exercise 4: Build Your Own Sales Assistant**

Create a simplified version of the Qualtrics example:

1. **3 Agents:**
   - ResearchAgent: Company research
   - ContentAgent: Pitch creation
   - AdminAgent: Email drafting

2. **Features to implement:**
   - Real-time message display
   - Colored output
   - Multiple scenarios
   - Proper cleanup

**Template structure:**

```python
# 1. Create executor factory
# 2. Create 3 specialized agents with skills
# 3. Start all servers in background
# 4. Create orchestrator
# 5. Create workspace with callbacks
# 6. Register all agents
# 7. Execute multiple scenarios
# 8. Display results
# 9. Clean up servers
```

**✅ Level 4 Complete when you can:**
- Build a 3+ agent system from scratch
- Implement real-time callbacks
- Handle multiple scenarios
- Display formatted results
- Properly manage server lifecycle

### 🏆 Level 5: Master - Understand the Qualtrics Example (Ongoing)

**Goal:** Fully understand and modify the Qualtrics example

**Mastery Checklist:**

- [ ] Understand every line of code
- [ ] Can explain what each agent does
- [ ] Understand the A2A protocol flow
- [ ] Can modify agent behaviors
- [ ] Can add new agents
- [ ] Can create new scenarios
- [ ] Understand parallel vs sequential execution
- [ ] Can explain the orchestration logic
- [ ] Can debug issues independently
- [ ] Can teach others how it works

**Challenge Exercises:**

1. **Add a 6th agent:**
   - Add "CustomerSuccessAgent" that tracks NPS scores
   - Give it 2-3 relevant skills
   - Register it with the workspace
   - Create a scenario that uses it

2. **Modify system prompts:**
   - Make ResearchAgent focus on technical details
   - Make ContentAgent use a more formal tone
   - See how output changes

3. **Add custom scenarios:**
   - Create a renewal scenario
   - Create a competitor analysis scenario
   - Create an onboarding scenario

4. **Implement error handling:**
   - What if an agent fails?
   - What if OpenAI is down?
   - How to retry failed subtasks?

5. **Add observability:**
   - Log all agent communications
   - Track execution times
   - Generate analytics report

---

## 6. Advanced Topics

### 🔄 Execution Patterns

**1. Sequential (Dependencies)**

```python
# Subtask B depends on Subtask A
Subtask A (ResearchAgent) → Subtask B (ContentAgent)

# Execution order:
[
    ["subtask_a"],  # Stage 1: Research first
    ["subtask_b"]   # Stage 2: Content after
]
```

**2. Parallel (No Dependencies)**

```python
# Subtasks can run simultaneously
Subtask A (ResearchAgent) ∥ Subtask B (DealAgent)

# Execution order:
[
    ["subtask_a", "subtask_b"]  # Stage 1: Both at once
]
```

**3. Mixed (Complex Dependencies)**

```python
# Some parallel, some sequential
Research → (Content ∥ Deal) → Admin

# Execution order:
[
    ["research"],           # Stage 1
    ["content", "deal"],    # Stage 2: Parallel
    ["admin"]              # Stage 3
]
```

### 🎯 Smart Agent Selection

**How orchestrator selects agents:**

1. **Analyzes the task:**
   - What needs to be done?
   - What skills are required?
   - What's the complexity?

2. **Matches to agent skills:**
   - Reads agent skills and tags
   - Scores each agent for each subtask
   - Considers multiple factors

3. **Selection criteria:**
   - Skill relevance (most important)
   - Tag matching
   - Agent availability
   - Load balancing

**Example:**

```
Task: "Research customer and create pitch"

Orchestrator thinking:
1. Subtask 1: "Research customer"
   - Needs: research, data-analysis
   - Best match: ResearchAgent (has "research" skill)
   
2. Subtask 2: "Create pitch"
   - Needs: content-creation, presentations
   - Best match: ContentAgent (has "presentation_creation" skill)
```

### 💬 Message Flow

**Complete message lifecycle:**

```
1. User submits task
   ↓
2. Orchestrator receives task
   ↓
3. Orchestrator analyzes (uses LLM)
   ↓
4. Creates execution plan
   ↓
5. For each subtask:
   a. Discovers agent (GET agent-card)
   b. Sends task (POST /)
   c. Agent executes
   d. Agent returns result
   e. Orchestrator logs message
   ↓
6. Orchestrator synthesizes results
   ↓
7. Returns final result
```

**Message types you'll see:**

- `system`: Workspace system messages
- `task_assignment`: Task sent to agent
- `task_result`: Agent's completed work
- `feedback`: Agent feedback to another agent
- `synthesis`: Final synthesized result

### 🧠 Context Object Deep Dive

**What's available in executor context:**

```python
async def my_executor(context):
    # Get user's message
    user_input = context.get_user_input()  # str
    
    # Get full task object
    task = context.get_task()  # Task object
    # task.description - str: Task description
    # task.task_id - str: Unique ID
    # task.metadata - dict: Additional data
    
    # Get message object
    message = context.get_message()  # Message object
    # message.content - str: Message content
    # message.sender_id - str: Who sent it
    # message.timestamp - datetime: When sent
    
    return "Your response"
```

### 🎨 Advanced Callback Usage

**Rich message callbacks:**

```python
def advanced_callback(msg_dict):
    """Extract all information from messages"""
    sender = msg_dict.get('sender_name')
    content = msg_dict.get('content')
    msg_type = msg_dict.get('message_type')
    timestamp = msg_dict.get('timestamp')
    metadata = msg_dict.get('metadata', {})
    
    # Custom formatting based on type
    if msg_type == 'task_assignment':
        print(f"📤 {sender} assigned task: {content[:100]}...")
    elif msg_type == 'task_result':
        print(f"✅ {sender} completed: {content[:100]}...")
    elif msg_type == 'feedback':
        print(f"💬 {sender} feedback: {content}")
    else:
        print(f"📝 {sender}: {content}")
    
    # Log to file
    with open('workspace.log', 'a') as f:
        f.write(f"{timestamp} | {sender} | {msg_type} | {content}\n")
    
    # Store in database
    # send_to_monitoring_system(msg_dict)
    # etc.
```

### 🔧 Custom Agent Behaviors

**Creating specialized executors:**

```python
# 1. Database Agent
async def database_executor(context):
    """Agent that queries a database"""
    query = context.get_user_input()
    
    # Connect to DB
    async with aiopg.create_pool(dsn) as pool:
        async with pool.acquire() as conn:
            async with conn.cursor() as cur:
                await cur.execute(query)
                results = await cur.fetchall()
    
    return format_results(results)

# 2. API Agent
async def api_executor(context):
    """Agent that calls external APIs"""
    request = context.get_user_input()
    
    async with aiohttp.ClientSession() as session:
        async with session.get(api_url, params=params) as response:
            data = await response.json()
    
    return process_data(data)

# 3. File Agent
async def file_executor(context):
    """Agent that processes files"""
    file_task = context.get_user_input()
    
    # Read file
    with open(file_path, 'r') as f:
        content = f.read()
    
    # Process
    result = process_content(content)
    
    # Write output
    with open(output_path, 'w') as f:
        f.write(result)
    
    return "File processed successfully"

# 4. Tool Agent
async def tool_executor(context):
    """Agent that uses external tools"""
    tool_request = context.get_user_input()
    
    # Parse which tool to use
    if "calculate" in tool_request:
        result = calculator.evaluate(expression)
    elif "search" in tool_request:
        result = search_engine.search(query)
    elif "translate" in tool_request:
        result = translator.translate(text, target_lang)
    
    return result
```

---

## 7. Troubleshooting & Best Practices

### ❌ Common Errors & Solutions

**1. "Address already in use" Error**

```
OSError: [Errno 48] Address already in use
```

**Solution:**
- Another process is using that port
- Change port number: `AgentServer(agent, port=8010)`
- Or kill the process: `lsof -ti:8001 | xargs kill -9`

**2. "OpenAI API Key Not Found"**

```
Error: The api_key client option must be set
```

**Solution:**
- Create `.env` file with: `OPENAI_API_KEY=your-key-here`
- Or set environment variable: `export OPENAI_API_KEY=your-key`
- Make sure to `load_dotenv()` before using the key

**3. "No response from agent"**

**Check:**
- Is the server running? (Look for "Agent running at...")
- Is the URL correct? (http://localhost:PORT)
- Is there a firewall blocking the port?
- Check server logs for errors

**4. "Orchestrator not selecting agents"**

**Check:**
- Are agents registered? (`workspace.register_agent()`)
- Do agent skills match the task?
- Is the task description clear enough?
- Try more specific agent descriptions

### ✅ Best Practices

**1. Agent Design**

```python
# ✅ GOOD: Specific, focused agent
agent = synqed.Agent(
    name="CustomerResearchAgent",
    description="Specializes in analyzing customer data from Salesforce",
    skills=[
        {
            "skill_id": "salesforce_analysis",
            "name": "Salesforce Data Analysis",
            "description": "Analyzes customer records, contracts, and usage data from Salesforce CRM",
            "tags": ["salesforce", "customers", "crm", "analysis"]
        }
    ],
    executor=specialized_executor
)

# ❌ BAD: Too generic, unclear purpose
agent = synqed.Agent(
    name="Agent1",
    description="Does stuff",
    skills=["general"],
    executor=vague_executor
)
```

**2. System Prompts**

```python
# ✅ GOOD: Detailed, role-specific prompt
system_prompt = """You are a Qualtrics sales research specialist.

Your role:
- Analyze internal customer data from Salesforce
- Research external market intelligence
- Identify upsell opportunities
- Provide actionable insights for sales conversations

Format your research with clear sections:
1. INTERNAL DATA SUMMARY
2. EXTERNAL MARKET INTELLIGENCE
3. KEY OPPORTUNITIES
4. RECOMMENDED TALKING POINTS

Be thorough, fact-based, and highlight opportunities."""

# ❌ BAD: Generic, unhelpful prompt
system_prompt = "You are a helpful assistant."
```

**3. Error Handling**

```python
# ✅ GOOD: Robust error handling
async def safe_executor(context):
    try:
        user_input = context.get_user_input()
        
        # Validate input
        if not user_input or len(user_input) < 10:
            return "Error: Please provide more detailed input."
        
        # Call LLM with timeout
        async with asyncio.timeout(30):  # 30 second timeout
            result = await call_llm(user_input)
        
        return result
        
    except asyncio.TimeoutError:
        return "Error: Request timed out. Please try again."
    except Exception as e:
        logger.error(f"Executor error: {e}")
        return f"Error: {str(e)}"

# ❌ BAD: No error handling
async def unsafe_executor(context):
    user_input = context.get_user_input()
    result = await call_llm(user_input)  # Can fail!
    return result
```

**4. Resource Management**

```python
# ✅ GOOD: Proper cleanup
async def main():
    servers = []
    
    try:
        # Start servers
        for agent in agents:
            server = synqed.AgentServer(agent, port=get_port())
            await server.start_background()
            servers.append(server)
        
        # Do work
        result = await workspace.execute_task(task)
        
    finally:
        # Always cleanup
        for server in servers:
            await server.stop()

# ❌ BAD: No cleanup (ports stay blocked)
async def main():
    server = synqed.AgentServer(agent, port=8000)
    await server.start_background()
    result = await workspace.execute_task(task)
    # Server still running! Port blocked!
```

**5. Task Descriptions**

```python
# ✅ GOOD: Clear, detailed task
task = """
I have a meeting tomorrow with TechCorp Inc., an existing customer.

Current situation:
- Company: TechCorp Inc.
- Current product: Qualtrics CX (500 licenses)
- Contract value: $250K/year
- Renewal: 60 days out
- Contact: Sarah Johnson, VP Customer Success

Objectives:
1. Research their current usage and identify upsell opportunities
2. Analyze their recent company developments
3. Create a pitch deck for Employee Experience platform
4. Generate a quote for 200 additional licenses
5. Draft a follow-up email

Deliverables needed:
- Research summary
- Pitch presentation
- Pricing quote
- Email draft
"""

# ❌ BAD: Vague, insufficient detail
task = "Help me prepare for a customer meeting."
```

**6. Workspace Configuration**

```python
# ✅ GOOD: Appropriate configuration for use case
# For production/important tasks
workspace = synqed.OrchestratedWorkspace(
    orchestrator=orchestrator,
    enable_agent_discussion=True,    # Quality over speed
    workspace_persistence=True,      # Keep history
    message_callback=log_messages    # Monitor everything
)

# For quick/simple tasks
workspace = synqed.OrchestratedWorkspace(
    orchestrator=orchestrator,
    enable_agent_discussion=False,   # Speed over quality
    workspace_persistence=False      # Don't need history
)

# ❌ BAD: One-size-fits-all configuration
workspace = synqed.OrchestratedWorkspace(orchestrator=orchestrator)
```

### 🎯 Performance Tips

**1. Choose the Right Model**

```python
# Orchestrator: Needs powerful model for planning
orchestrator = synqed.Orchestrator(
    model="gpt-4o"  # Best for complex orchestration
)

# Agents: Can use smaller models for specific tasks
agent_executor = create_executor(
    model="gpt-4o-mini"  # Faster, cheaper for focused tasks
)
```

**2. Optimize Temperature**

```python
# Low temperature (0.0-0.3): Focused, deterministic
# Good for: Research, data analysis, calculations
temperature=0.2

# Medium temperature (0.4-0.7): Balanced
# Good for: Content creation, general tasks
temperature=0.5

# High temperature (0.8-1.0): Creative, diverse
# Good for: Brainstorming, creative writing
temperature=0.9
```

**3. Parallel Execution**

```python
# Orchestrator automatically parallelizes independent subtasks
# Design agents with independent capabilities

# ✅ GOOD: Independent agents (can run in parallel)
- ResearchAgent: Data gathering
- ContentAgent: Content creation
- DealAgent: Quote generation

# ❌ BAD: Dependent agents (must run sequentially)
- Agent1: Does step 1
- Agent2: Needs output from Agent1
- Agent3: Needs output from Agent2
```

### 📊 Monitoring & Debugging

**Add comprehensive logging:**

```python
import logging

logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s | %(name)s | %(levelname)s | %(message)s',
    handlers=[
        logging.FileHandler('agent_system.log'),
        logging.StreamHandler()
    ]
)

logger = logging.getLogger(__name__)

async def logged_executor(context):
    """Executor with detailed logging"""
    user_input = context.get_user_input()
    logger.info(f"Received task: {user_input[:100]}...")
    
    try:
        result = await process_task(user_input)
        logger.info(f"Task completed successfully: {len(result)} chars")
        return result
    except Exception as e:
        logger.error(f"Task failed: {e}", exc_info=True)
        raise

# Monitor workspace activity
def monitoring_callback(msg_dict):
    """Log all workspace activity"""
    logger.info(
        f"Workspace message: {msg_dict['sender_name']} | "
        f"{msg_dict['message_type']} | "
        f"{len(msg_dict['content'])} chars"
    )
```

### 🔍 Debugging Techniques

**1. Enable verbose output:**

```python
# Print all messages in real-time
def debug_callback(msg_dict):
    print("=" * 80)
    print(f"Sender: {msg_dict['sender_name']}")
    print(f"Type: {msg_dict['message_type']}")
    print(f"Content: {msg_dict['content']}")
    print(f"Metadata: {msg_dict.get('metadata', {})}")
    print("=" * 80)
```

**2. Inspect execution plan:**

```python
result = await workspace.execute_task(task)

# Analyze the plan
print(f"Subtasks: {len(result.plan.subtasks)}")
print(f"Agents: {result.plan.selected_agents}")
print(f"Reasoning: {result.plan.reasoning}")

for subtask in result.plan.subtasks:
    print(f"\nSubtask: {subtask.subtask_id}")
    print(f"  Agent: {subtask.assigned_agent_name}")
    print(f"  Description: {subtask.description}")
    print(f"  Dependencies: {subtask.dependencies}")
    print(f"  Status: {subtask.status}")
```

**3. Test agents individually:**

```python
# Before using in workspace, test agent directly
async def test_agent():
    async with synqed.Client("http://localhost:8001") as client:
        result = await client.ask("Test task")
        print(f"Agent response: {result}")

asyncio.run(test_agent())
```

---

## 🎓 Summary & Next Steps

### What You've Learned

✅ **Fundamentals:**
- What A2A protocol is
- What Synqed provides
- How agents work

✅ **Core Concepts:**
- Agent creation and executors
- Agent servers and deployment
- Orchestrators and workspaces
- Message flow and callbacks

✅ **APIs:**
- Agent with skills
- AgentServer with background mode
- OrchestratedWorkspace with callbacks
- ExecutionResult object structure
- Colored display utilities

✅ **Practical Skills:**
- Building single agents
- Creating multi-agent systems
- Implementing collaboration
- Handling errors
- Monitoring and debugging

### Mastery Checklist

- [ ] Can explain A2A protocol in simple terms
- [ ] Can create a basic agent from scratch
- [ ] Can integrate LLMs (OpenAI, etc.)
- [ ] Can run multiple agents simultaneously
- [ ] Understand orchestration logic
- [ ] Can implement real-time callbacks
- [ ] Can debug agent communication issues
- [ ] Can optimize for performance
- [ ] Can handle errors gracefully
- [ ] Can build a complete multi-agent system
- [ ] Understand the Qualtrics example fully
- [ ] Can teach others about Synqed

### Next Steps

**1. Practice (Most Important!)**
- Build 5+ single agents
- Build 3+ multi-agent systems
- Experiment with different use cases

**2. Explore Examples**
- Study other examples in `/synqed-samples/`
- Try different protocols (Consensus, Debate, Critique)
- Experiment with WorkspaceEngine

**3. Build Real Projects**
- Customer support automation
- Content creation pipeline
- Research assistant team
- Sales enablement system
- Whatever solves YOUR problem!

**4. Deep Dive**
- Read A2A specification
- Study the source code
- Contribute to the project
- Build custom protocols

**5. Join Community**
- Share your projects
- Ask questions
- Help others learn
- Contribute improvements

### Resources

- **Documentation**: `/synqed-python/README.md`
- **Examples**: `/synqed-samples/samples/python/`
- **A2A Spec**: `/A2A/specification/`
- **This Guide**: Reference as needed

---

## 🎉 Congratulations!

You now have a complete roadmap from zero to mastery of the Synqed library and the Qualtrics sales agentification example!

**Remember:**
- Start simple (single agent)
- Build complexity gradually
- Practice consistently
- Ask questions
- Build real projects

The best way to learn is by **building**. Start with Level 1 exercises and work your way up!

---

**Questions? Stuck? Want to share your progress?**

Open an issue, start a discussion, or reach out to the Synq team!

Happy building! 🚀

---

*Last updated: November 18, 2025*
*Version: 1.0*

