# From Zero to Mastery: Complete Learning Path for A2A and Synq

**Duration:** 4-6 weeks for mastery (2-3 hours daily)  
**Prerequisites:** Basic Python knowledge (async/await, functions, classes)  
**Goal:** Build sophisticated multi-agent systems from first principles

---

## 📚 How to Use This Guide

1. **Don't skip phases** - Each builds on the previous
2. **Code everything yourself** - Type every example manually
3. **Experiment freely** - Break things, fix them, understand why
4. **Build the exercises** - Theory without practice is useless
5. **Document your learning** - Keep a journal of "aha!" moments

---

# Phase 0: Mental Models (Day 1)

Before touching code, understand the fundamental concepts.

## 0.1 What is an AI Agent?

**Traditional Software:**
```
Input → Code → Output
```
Deterministic, predictable, follows exact instructions.

**AI Agent:**
```
Goal → Agent (with LLM) → Actions → Result
```
Non-deterministic, autonomous, reasons about what to do.

**Key Properties of Agents:**
- **Autonomy**: Makes decisions independently
- **Reactivity**: Responds to environment changes
- **Proactivity**: Takes initiative to achieve goals
- **Social Ability**: Communicates with other agents

**Example:**
- Traditional: `calculate_tax(income)` - does one thing
- Agent: "Optimize my taxes" - figures out what steps to take

## 0.2 The Problem: Agent Silos

Imagine you have:
- A weather agent (knows weather)
- A recipe agent (knows recipes)
- A shopping agent (knows products)

**Without A2A:**
```
User → "Plan dinner based on tonight's weather"
       ↓
    Dead end (no agent can do this alone)
```

Each agent speaks its own language, can't collaborate.

**With A2A:**
```
User → Orchestrator Agent
       ├→ Weather Agent: "What's the weather?"
       ├→ Recipe Agent: "Suggest dinner for cold weather"
       └→ Shopping Agent: "List ingredients"
       ↓
    Complete solution delivered to user
```

## 0.3 Why Not Just Use APIs?

**APIs are stateless tools:**
```python
result = weather_api.get_weather("London")
# Returns data, no context, no negotiation
```

**Agents are stateful collaborators:**
```python
task = await weather_agent.send_message(
    "What's the weather in London? I need it for planning a picnic."
)
# Agent understands context, might ask clarifying questions,
# maintains conversation state, adapts response to your goal
```

**Key Differences:**

| Aspect | API Call | Agent Interaction |
|--------|----------|-------------------|
| **State** | Stateless | Stateful (remembers conversation) |
| **Turns** | Single request/response | Multi-turn dialogue |
| **Negotiation** | None | Can ask for clarification |
| **Intelligence** | Dumb (returns raw data) | Smart (understands intent) |
| **Format** | Fixed schema | Flexible (text, files, structured data) |

## 0.4 The A2A Mental Model

Think of A2A as **HTTP for AI agents**:

- **HTTP** lets websites talk to each other
- **A2A** lets AI agents talk to each other

```
┌─────────────────────────────────────────┐
│  HTTP is for Documents                   │
│  A2A is for Intelligence                │
└─────────────────────────────────────────┘
```

**Core Principle:** 
Agents should collaborate **as agents**, not be reduced to dumb tools.

## 0.5 The Three-Layer Stack

```
┌──────────────────────────────────────┐
│     Your Application Layer           │  ← What you build
│  (Multi-agent orchestration logic)   │
├──────────────────────────────────────┤
│          Synq Layer                  │  ← Simplified wrapper
│  (Developer-friendly abstractions)   │
├──────────────────────────────────────┤
│           A2A Layer                  │  ← Standard protocol
│  (Communication protocol)            │
└──────────────────────────────────────┘
```

**Analogy:**
- **A2A** = TCP/IP (the protocol)
- **Synq** = Flask/FastAPI (the framework)
- **Your App** = Your website (the product)

---

# Phase 1: Understanding Agent Communication (Days 2-4)

## 1.1 Core Concepts Deep Dive

### Concept 1: The Agent Card

Think of it as a **business card** or **LinkedIn profile** for an agent.

**What it contains:**
```json
{
  "name": "Recipe Agent",
  "description": "I find and recommend recipes",
  "url": "https://recipe-agent.com",
  "skills": [
    {
      "id": "recipe_search",
      "name": "Recipe Search",
      "description": "Find recipes by ingredients or cuisine"
    }
  ],
  "capabilities": {
    "streaming": true,
    "push_notifications": false
  }
}
```

**Why it matters:**
- Agents discover each other through cards
- Clients know what an agent can do
- Contains authentication requirements

### Concept 2: Tasks

A **Task** is a unit of work with a lifecycle.

```
User Request → Task Created → Task Processing → Task Completed
                    ↓              ↓                 ↓
                Task ID      Status Updates    Final Result
```

**Key Properties:**
- `taskId`: Unique identifier (like a tracking number)
- `state`: pending → working → completed/failed/canceled
- `artifacts`: The actual outputs produced

**Real-world analogy:**
Like ordering food delivery:
- Place order (create task)
- Get order ID (task ID)
- Track status (status updates)
- Receive food (artifacts)

### Concept 3: Messages

A **Message** is a single turn in a conversation.

```
Role: "user"
├─ TextPart: "Find me a recipe"
└─ DataPart: {"cuisine": "Italian"}

Role: "agent"  
├─ TextPart: "Here's a pasta recipe..."
└─ FilePart: recipe_image.jpg
```

**Types of Parts:**
1. **TextPart**: Plain text content
2. **FilePart**: Binary data (images, PDFs, etc.)
3. **DataPart**: Structured JSON data

### Concept 4: Artifacts

**Artifacts** are the tangible outputs of a task.

**Difference from Messages:**
- **Messages**: Communication during work
  - "I'm searching..."
  - "Found 3 recipes..."
- **Artifacts**: The actual deliverables
  - The recipe document
  - Shopping list PDF

```python
# Message (transient communication)
"I'm generating your report..."

# Artifact (permanent output)
report.pdf (delivered to you)
```

## 1.2 The Request Lifecycle

Let's trace a complete interaction step-by-step:

### Step 1: Agent Discovery

```
Client: "Where can I find agents?"
Discovery Service: "Check /.well-known/agent-card"

Client → GET https://agent.com/.well-known/agent-card
Agent → Returns Agent Card (JSON)
```

**What happens:**
- Client discovers what the agent can do
- Learns the agent's endpoint URL
- Sees authentication requirements

### Step 2: Authentication

```
Client: Reads agent card's securitySchemes
       ↓
If OAuth required:
    Client → Auth Server: "Give me a token"
    Auth Server → Client: JWT token
       ↓
Client stores token for future requests
```

### Step 3: Send Message (Create Task)

```
Client → POST /sendMessage
         {
           "message": {
             "role": "user",
             "parts": [{"text": "Find pasta recipes"}]
           }
         }
         Headers: Authorization: Bearer <token>

Agent → Creates Task
     → Returns Task object with taskId
```

### Step 4: Get Results

**Option A: Polling (Request/Response)**
```
Client → GET /getTask?taskId=abc123
Agent → Returns task with current status

(repeat until task.state === "completed")
```

**Option B: Streaming (SSE)**
```
Client → POST /sendMessageStream (keeps connection open)
Agent → Stream: TaskSubmitted
     → Stream: TaskStatusUpdate (working)
     → Stream: TaskArtifactUpdate (partial results)
     → Stream: TaskArtifactUpdate (more results)
     → Stream: TaskStatusUpdate (completed)
```

## 1.3 Hands-On Exercise: Trace the Flow

**Goal:** Understand the flow viscerally.

Create a file called `trace_flow.py`:

```python
"""
Mental simulation of A2A flow.
This isn't real code - it's a thinking tool.
"""

def trace_agent_interaction():
    """
    Trace a complete agent interaction step-by-step.
    """
    
    # Step 1: Discovery
    print("=" * 60)
    print("STEP 1: AGENT DISCOVERY")
    print("=" * 60)
    print("Client: I need to find an agent...")
    print("Client: GET /.well-known/agent-card")
    print()
    
    agent_card = {
        "name": "Recipe Agent",
        "url": "http://localhost:8000",
        "skills": ["recipe_search", "meal_planning"]
    }
    print(f"Agent: Here's my card: {agent_card}")
    print()
    
    # Step 2: Authentication
    print("=" * 60)
    print("STEP 2: AUTHENTICATION")
    print("=" * 60)
    print("Client: Card says I need a token...")
    print("Client: (Gets token from auth server)")
    token = "jwt_abc123xyz"
    print(f"Client: Got token: {token[:10]}...")
    print()
    
    # Step 3: Create Task
    print("=" * 60)
    print("STEP 3: SEND MESSAGE (CREATE TASK)")
    print("=" * 60)
    message = {
        "role": "user",
        "parts": [{"text": "Find me Italian pasta recipes"}]
    }
    print(f"Client: POST /sendMessage")
    print(f"        Message: {message}")
    print()
    
    task = {
        "taskId": "task_001",
        "state": "submitted",
        "artifacts": []
    }
    print(f"Agent: Created task: {task}")
    print()
    
    # Step 4: Processing
    print("=" * 60)
    print("STEP 4: TASK PROCESSING")
    print("=" * 60)
    print("Agent: (internal) Analyzing request...")
    print("Agent: (internal) Searching recipe database...")
    print("Agent: (internal) Found 5 recipes...")
    print()
    
    # Step 5: Return Results
    print("=" * 60)
    print("STEP 5: RETURN RESULTS")
    print("=" * 60)
    task["state"] = "completed"
    task["artifacts"] = [
        {
            "artifactId": "artifact_001",
            "name": "Recipe List",
            "parts": [{"text": "1. Carbonara\n2. Cacio e Pepe\n..."}]
        }
    ]
    print(f"Agent: Task complete! Here are your artifacts:")
    print(f"       {task['artifacts']}")
    print()
    
    print("=" * 60)
    print("FLOW COMPLETE")
    print("=" * 60)

if __name__ == "__main__":
    trace_agent_interaction()
```

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

**Exercise:**
1. Read through the output carefully
2. Draw the flow on paper
3. Modify it to add a second interaction
4. Add error handling (what if agent is busy?)

## 1.4 Key Insights

Before moving to code, internalize these insights:

1. **Agents are black boxes**
   - Clients don't know internal logic
   - They just see: send message → get result
   
2. **Tasks enable async work**
   - Long-running operations don't block
   - Client can do other things while waiting
   
3. **Discovery enables flexibility**
   - Don't hardcode agent capabilities
   - Read the card dynamically
   
4. **Protocol ≠ Implementation**
   - A2A defines how to communicate
   - Not how to build the agent's brain

---

# Phase 2: A2A Protocol Fundamentals (Days 5-8)

## 2.1 Read the Core Documentation

Read these in order (in `/Users/dorsa/Desktop/PROJECTS/synq_2/A2A/docs/`):

1. **`topics/what-is-a2a.md`** (you've seen this)
   - Problems A2A solves
   - Design principles
   - When to use A2A vs MCP

2. **`topics/key-concepts.md`**
   - Deep dive into Tasks, Messages, Parts
   - Communication patterns
   - Agent Cards structure

3. **`topics/life-of-a-task.md`**
   - Task state machine
   - Status transitions
   - Error handling

4. **`topics/streaming-and-async.md`**
   - SSE streaming
   - Push notifications
   - When to use each

5. **`topics/agent-discovery.md`**
   - How agents find each other
   - Well-known endpoints
   - Security considerations

6. **`specification.md`**
   - Full protocol specification
   - JSON-RPC methods
   - Request/response schemas

## 2.2 Study the Protocol Specification

Open `/Users/dorsa/Desktop/PROJECTS/synq_2/A2A/specification/json/a2a.json`

This is the formal specification. Key sections:

### Core Methods

```
sendMessage         - Send a message, get back a Task or Message
sendMessageStream   - Stream responses in real-time
getTask            - Get task status by ID
cancelTask         - Cancel a running task
```

### Data Structures

```
AgentCard          - Agent metadata
Task               - Unit of work
Message            - Communication turn
Part               - Content container
Artifact           - Task output
```

## 2.3 Hands-On: Explore A2A Python SDK

```bash
cd /Users/dorsa/Desktop/PROJECTS/synq_2/a2a-python
```

**Study these files:**

1. **`src/a2a/types.py`**
   - All A2A type definitions
   - Pydantic models for validation
   - Understand the data structures

2. **`src/a2a/server/`**
   - How A2A servers are built
   - Request handling
   - Task management

3. **`src/a2a/client/`**
   - How clients communicate
   - Connection management
   - Streaming support

**Exercise: Read and Understand Types**

```python
# Create understanding_types.py
from a2a.types import (
    AgentCard,
    Task,
    Message,
    TextPart,
    FilePart,
    DataPart,
    Artifact,
    TaskStatus,
    TaskState
)

def explore_types():
    """Explore A2A type system."""
    
    # 1. Create a text part
    text_part = TextPart(text="Hello, agent!")
    print(f"TextPart: {text_part}")
    print()
    
    # 2. Create a message
    message = Message(
        role="user",
        parts=[text_part]
    )
    print(f"Message: {message.model_dump_json(indent=2)}")
    print()
    
    # 3. Create a task
    task = Task(
        task_id="task_001",
        status=TaskStatus(state=TaskState.working)
    )
    print(f"Task: {task.model_dump_json(indent=2)}")
    print()
    
    # 4. Create an artifact
    artifact = Artifact(
        artifact_id="art_001",
        name="Result",
        parts=[TextPart(text="Task complete!")]
    )
    print(f"Artifact: {artifact.model_dump_json(indent=2)}")
    print()

if __name__ == "__main__":
    explore_types()
```

## 2.4 Mental Model: The Complete Stack

```
┌────────────────────────────────────────────┐
│         YOUR APPLICATION                    │
│  (Business logic, orchestration)           │
└────────────────┬───────────────────────────┘
                 │
                 ↓
┌────────────────────────────────────────────┐
│            A2A CLIENT                       │
│  - Discovers agents                        │
│  - Sends messages                          │
│  - Manages tasks                           │
└────────────────┬───────────────────────────┘
                 │
                 │ HTTP/JSON-RPC
                 │
                 ↓
┌────────────────────────────────────────────┐
│            A2A SERVER                       │
│  - Exposes agent card                      │
│  - Receives requests                       │
│  - Creates tasks                           │
└────────────────┬───────────────────────────┘
                 │
                 ↓
┌────────────────────────────────────────────┐
│         AGENT EXECUTOR                      │
│  - Your agent's brain                      │
│  - Contains the actual logic               │
│  - Produces results                        │
└────────────────────────────────────────────┘
```

## 2.5 Build a Raw A2A Agent (No Synq)

To truly understand A2A, build one agent without Synq:

```python
# raw_a2a_agent.py
"""
Build an A2A agent using only the a2a-sdk.
This teaches you what Synq abstracts away.
"""
import asyncio
from a2a.server import AgentServer as A2AServer
from a2a.server.agent_execution import AgentExecutor, RequestContext
from a2a.server.events import EventQueue
from a2a.types import AgentCard, AgentSkill, Message, TaskStatus, TaskState
from a2a.utils.message import new_agent_text_message

# Step 1: Create an executor (agent's brain)
class SimpleExecutor(AgentExecutor):
    async def execute(self, context: RequestContext, event_queue: EventQueue) -> None:
        """Execute the task."""
        # Get user's message
        user_input = context.get_user_input()
        
        # Do your agent logic
        response_text = f"Echo: {user_input}"
        
        # Create response message
        message = new_agent_text_message(
            text=response_text,
            task_id=context.task_id
        )
        
        # Send it to the event queue
        await event_queue.enqueue_event(message)
    
    async def cancel(self, context: RequestContext, event_queue: EventQueue) -> None:
        """Handle cancellation."""
        from a2a.server.events import TaskStatusUpdateEvent
        status = TaskStatus(state=TaskState.canceled)
        event = TaskStatusUpdateEvent(task_id=context.task_id, status=status)
        await event_queue.enqueue_event(event)

# Step 2: Create an agent card
def create_agent_card() -> AgentCard:
    return AgentCard(
        name="Raw A2A Agent",
        description="Built without Synq to understand the protocol",
        version="1.0.0",
        url="http://localhost:8000",
        skills=[
            AgentSkill(
                id="echo",
                name="Echo",
                description="Echoes back your message",
                tags=["echo"]
            )
        ]
    )

# Step 3: Create and run server
async def main():
    executor = SimpleExecutor()
    card = create_agent_card()
    
    server = A2AServer(
        agent_card=card,
        agent_executor=executor,
        host="0.0.0.0",
        port=8000
    )
    
    print(f"Raw A2A server starting at {card.url}")
    print("This is what Synq abstracts away!")
    print("Press Ctrl+C to stop")
    
    await server.start()

if __name__ == "__main__":
    asyncio.run(main())
```

**Run it:**
```bash
cd /Users/dorsa/Desktop/PROJECTS/synq_2/a2a-python
python raw_a2a_agent.py
```

**Exercise:**
1. Run the agent
2. Test it with curl:
   ```bash
   # Get agent card
   curl http://localhost:8000/.well-known/agent-card
   
   # Send a message
   curl -X POST http://localhost:8000/sendMessage \
     -H "Content-Type: application/json" \
     -d '{"message": {"role": "user", "parts": [{"text": "Hello"}]}}'
   ```
3. Compare this to Synq's version - notice the boilerplate?

## 2.6 Key Takeaways from Phase 2

✅ A2A is **just a protocol** - defines messages, not behavior  
✅ Building raw A2A agents requires **boilerplate**  
✅ The protocol is **transport-agnostic** (HTTP, gRPC, etc.)  
✅ Everything flows through **JSON-RPC 2.0**  
✅ Agent Cards are the **discovery mechanism**

---

# Phase 3: Understanding Synq (Days 9-12)

Now that you understand A2A, let's see how Synq simplifies it.

## 3.1 Why Synq Exists

Compare building the same agent:

**Raw A2A (50+ lines):**
```python
class MyExecutor(AgentExecutor):
    async def execute(self, context, event_queue):
        # ... lots of boilerplate ...
        message = new_agent_text_message(...)
        await event_queue.enqueue_event(message)
    
    async def cancel(self, context, event_queue):
        # ... more boilerplate ...

card = AgentCard(name=..., description=..., skills=[...])
executor = MyExecutor()
server = A2AServer(agent_card=card, agent_executor=executor, ...)
await server.start()
```

**Synq (10 lines):**
```python
async def my_logic(context):
    return "Response"

agent = Agent(name="...", skills=["..."], executor=my_logic)
server = AgentServer(agent, port=8000)
await server.start()
```

**Synq abstracts:**
- Agent card creation
- Executor wrapping
- Event queue management
- Message serialization
- Server setup

## 3.2 Synq Architecture

```
┌───────────────────────────────────────────┐
│        Synq High-Level API                │
├───────────────────────────────────────────┤
│  Agent  │ Agent wraps AgentExecutor       │
│  Server │ Server wraps A2AServer          │
│  Client │ Client wraps A2AClient          │
│  Delegator │ Multi-agent orchestration    │
└──────────────────┬────────────────────────┘
                   │
                   ↓
           ┌───────────────┐
           │  A2A Protocol  │
           └───────────────┘
```

## 3.3 Core Synq Components

### Component 1: Agent

**File:** `Synq/src/synqed/agent.py`

**What it does:**
- Wraps your agent logic
- Auto-generates agent cards
- Handles executor creation

**Key methods:**
```python
agent = Agent(
    name="My Agent",
    description="What it does",
    skills=["skill1", "skill2"],  # Simple list or detailed dicts
    executor=my_async_function     # Your logic here
)

card = agent.card                  # Auto-generated AgentCard
executor = agent.get_executor()    # Wrapped in AgentExecutor
```

### Component 2: AgentCardBuilder

**File:** `Synq/src/synqed/agent_card.py`

**What it does:**
- Builds A2A-compliant agent cards
- Provides fluent API
- Handles validation

**Usage:**
```python
from synqed import AgentCardBuilder

builder = AgentCardBuilder(
    name="My Agent",
    description="Description",
    version="1.0.0",
    url="http://localhost:8000"
)

builder.add_skill(
    skill_id="my_skill",
    name="My Skill",
    description="What it does",
    tags=["tag1"],
    examples=["Example usage"]
)

builder.set_capabilities(
    streaming=True,
    push_notifications=False
)

card = builder.build()  # Returns AgentCard
```

### Component 3: AgentServer

**File:** `Synq/src/synqed/server.py`

**What it does:**
- Wraps A2A server
- Integrates with FastAPI
- Handles routing

**Usage:**
```python
server = AgentServer(
    agent=agent,
    host="0.0.0.0",
    port=8000,
    path_prefix="/a2a/v1",
    enable_cors=True
)

# Blocking (runs until Ctrl+C)
await server.start()

# Or background
await server.start_background()
# ... do other work ...
await server.stop()
```

### Component 4: Client

**File:** `Synq/src/synqed/client.py`

**What it does:**
- Simplified A2A client
- Streaming support
- Task management

**Usage:**
```python
from synqed import Client

async with Client("http://localhost:8000") as client:
    # Stream responses
    async for chunk in client.stream("Hello!"):
        print(chunk, end="")
    
    # Or wait for complete response
    response = await client.ask("What's 2+2?")
    print(response)
    
    # Task management
    task = await client.get_task(task_id)
    await client.cancel_task(task_id)
```

### Component 5: TaskDelegator

**File:** `Synq/src/synqed/delegator.py`

**What it does:**
- Multi-agent orchestration
- Automatic task routing
- Agent discovery

**Usage:**
```python
from synqed import TaskDelegator

delegator = TaskDelegator()

# Register agents
delegator.register_agent(agent=local_agent)
delegator.register_agent(agent_url="http://remote:8000")

# Submit tasks (auto-routed)
result = await delegator.submit_task("Find a recipe")

# Submit with constraints
result = await delegator.submit_task(
    "Find a recipe",
    require_skills=["cooking"],
    preferred_agent="Recipe Agent"
)

# Broadcast to all
results = await delegator.submit_task_to_multiple("Question for all")
```

## 3.4 Hands-On: Build Your First Synq Agent

```bash
cd /Users/dorsa/Desktop/PROJECTS/synq_2/Synq
pip install -e .
```

**Exercise 1: Hello World Agent**

Create `hello_agent.py`:

```python
import asyncio
from synqed import Agent, AgentServer

async def hello_logic(context):
    """Simple agent that greets users."""
    user_message = context.get_request_message_text()
    return f"Hello! You said: '{user_message}'"

async def main():
    agent = Agent(
        name="Hello Agent",
        description="A friendly greeting agent",
        skills=["greeting", "conversation"],
        executor=hello_logic
    )
    
    server = AgentServer(agent, port=8000)
    print(f"Agent running at {server.url}")
    await server.start()

if __name__ == "__main__":
    asyncio.run(main())
```

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

**Exercise 2: Client Interaction**

In another terminal, create `hello_client.py`:

```python
import asyncio
from synqed import Client

async def main():
    async with Client("http://localhost:8000") as client:
        # Test 1: Simple message
        response = await client.ask("Hi there!")
        print(f"Response: {response}")
        
        # Test 2: Streaming
        print("\nStreaming response:")
        async for chunk in client.stream("Tell me a story"):
            print(chunk, end="", flush=True)
        print()

if __name__ == "__main__":
    asyncio.run(main())
```

Run it:
```bash
python hello_client.py
```

**Exercise 3: Inspect the Agent Card**

Create `inspect_card.py`:

```python
import asyncio
import aiohttp

async def main():
    async with aiohttp.ClientSession() as session:
        async with session.get("http://localhost:8000/.well-known/agent-card") as resp:
            card = await resp.json()
            
            print("Agent Card:")
            print("=" * 60)
            
            import json
            print(json.dumps(card, indent=2))
            
            print("\n" + "=" * 60)
            print(f"Agent Name: {card['name']}")
            print(f"Skills: {[s['id'] for s in card['skills']]}")
            print(f"Streaming: {card['capabilities']['streaming']}")

if __name__ == "__main__":
    asyncio.run(main())
```

## 3.5 Study the Examples

Go through each example in order:

### 1. Basic Agent

```bash
cd /Users/dorsa/Desktop/PROJECTS/synq_2/Synq
python examples/basic_agent.py
```

**What to learn:**
- How to structure agent logic
- Context object API
- Server lifecycle

### 2. Client Example

```bash
python examples/client_example.py
```

**What to learn:**
- Client connection management
- Sending messages
- Handling responses

### 3. Multi-Agent Delegation

```bash
python examples/multi_agent_delegation.py
```

**What to learn:**
- Creating specialized agents
- Task delegation logic
- Background servers

## 3.6 Deep Dive: How Synq Works Internally

Read these files carefully:

**1. Agent Wrapper** (`src/synqed/agent.py`)
```python
# Key insight: Agent wraps user function in AgentExecutor
def _create_custom_executor(self, handler):
    class CustomExecutor(AgentExecutor):
        async def execute(self, context, event_queue):
            # Call user's function
            result = await handler(context)
            
            # Convert to Message
            message = new_agent_text_message(text=result, ...)
            
            # Send to queue
            await event_queue.enqueue_event(message)
    
    return CustomExecutor()
```

**2. Server Wrapper** (`src/synqed/server.py`)
```python
# Key insight: Server wraps A2AServer with sensible defaults
class AgentServer:
    def __init__(self, agent, port=8000):
        # Update agent URL
        agent.set_url(f"http://localhost:{port}")
        
        # Create A2A server
        self._server = A2AServer(
            agent_card=agent.card,
            agent_executor=agent.get_executor(),
            ...
        )
```

**3. Client Wrapper** (`src/synqed/client.py`)
```python
# Key insight: Client simplifies A2A client API
class Client:
    async def ask(self, message):
        # Create A2A message
        a2a_message = self._create_message(message)
        
        # Send via A2A client
        task = await self._client.send_message(a2a_message)
        
        # Wait for completion
        while task.status.state != TaskState.completed:
            await asyncio.sleep(0.1)
            task = await self._client.get_task(task.task_id)
        
        # Extract result
        return self._extract_text(task)
```

## 3.7 Key Takeaways from Phase 3

✅ Synq is a **wrapper** around A2A, not a replacement  
✅ It reduces boilerplate by **10x**  
✅ Still 100% **A2A-compliant** under the hood  
✅ You can mix Synq and raw A2A  
✅ Understanding A2A first makes Synq obvious

---

# Phase 4: Building Real Systems (Days 13-18)

Time to build actual multi-agent systems.

## 4.1 Project 1: Weather-to-Recipe System

**Goal:** Build a 3-agent system that:
1. Gets weather for a location
2. Suggests recipe based on weather
3. Creates shopping list for ingredients

### Step 1: Design the System

```
User: "Plan dinner for tonight in London"
     ↓
Orchestrator Agent
     ├→ Weather Agent: "What's weather in London?"
     │  └→ "Cold and rainy, 8°C"
     │
     ├→ Recipe Agent: "Suggest dinner for cold weather"
     │  └→ "Hot soup! Here's a recipe..."
     │
     └→ Shopping Agent: "List ingredients for [soup recipe]"
        └→ "You need: carrots, chicken, ..."
     ↓
User: Gets complete dinner plan
```

### Step 2: Build Weather Agent

```python
# weather_agent.py
import asyncio
from synqed import Agent, AgentServer

# Simulate weather API
WEATHER_DATA = {
    "london": {"temp": 8, "condition": "rainy"},
    "paris": {"temp": 12, "condition": "cloudy"},
    "miami": {"temp": 28, "condition": "sunny"},
}

async def weather_logic(context):
    """Get weather for a location."""
    user_message = context.get_request_message_text().lower()
    
    # Extract location (simple parsing)
    location = None
    for city in WEATHER_DATA.keys():
        if city in user_message:
            location = city
            break
    
    if not location:
        return "I couldn't find a location in your message. Try: London, Paris, or Miami."
    
    weather = WEATHER_DATA[location]
    return f"Weather in {location.title()}: {weather['temp']}°C, {weather['condition']}"

async def main():
    agent = Agent(
        name="Weather Agent",
        description="Provides weather information",
        skills=["weather", "forecast", "temperature"],
        executor=weather_logic
    )
    
    server = AgentServer(agent, port=8001)
    print(f"Weather Agent running at {server.url}")
    await server.start()

if __name__ == "__main__":
    asyncio.run(main())
```

### Step 3: Build Recipe Agent

```python
# recipe_agent.py
import asyncio
from synqed import Agent, AgentServer

RECIPES = {
    "cold": {
        "name": "Hot Chicken Soup",
        "ingredients": ["chicken", "carrots", "celery", "onions", "broth"],
        "description": "A warming soup perfect for cold weather"
    },
    "warm": {
        "name": "Fresh Salad",
        "ingredients": ["lettuce", "tomatoes", "cucumber", "olive oil"],
        "description": "A light salad for warm weather"
    },
    "hot": {
        "name": "Iced Gazpacho",
        "ingredients": ["tomatoes", "cucumbers", "peppers", "olive oil"],
        "description": "Cold Spanish soup for hot days"
    }
}

async def recipe_logic(context):
    """Suggest recipe based on weather."""
    user_message = context.get_request_message_text().lower()
    
    # Determine weather category
    if any(word in user_message for word in ["cold", "rainy", "freezing"]):
        category = "cold"
    elif any(word in user_message for word in ["hot", "sunny", "warm"]):
        category = "hot"
    else:
        category = "warm"
    
    recipe = RECIPES[category]
    
    response = f"""
Recipe Suggestion: {recipe['name']}

{recipe['description']}

Ingredients needed:
{', '.join(recipe['ingredients'])}
"""
    return response.strip()

async def main():
    agent = Agent(
        name="Recipe Agent",
        description="Suggests recipes based on weather",
        skills=["recipes", "cooking", "meal_planning"],
        executor=recipe_logic
    )
    
    server = AgentServer(agent, port=8002)
    print(f"Recipe Agent running at {server.url}")
    await server.start()

if __name__ == "__main__":
    asyncio.run(main())
```

### Step 4: Build Shopping Agent

```python
# shopping_agent.py
import asyncio
from synqed import Agent, AgentServer

# Simulate product database
PRODUCTS = {
    "chicken": {"price": 5.99, "store": "Butcher Shop"},
    "carrots": {"price": 1.49, "store": "Grocery Store"},
    "celery": {"price": 2.29, "store": "Grocery Store"},
    "onions": {"price": 0.99, "store": "Grocery Store"},
    "broth": {"price": 3.49, "store": "Grocery Store"},
    "lettuce": {"price": 1.99, "store": "Grocery Store"},
    "tomatoes": {"price": 2.49, "store": "Grocery Store"},
    "cucumber": {"price": 1.29, "store": "Grocery Store"},
    "olive oil": {"price": 8.99, "store": "Grocery Store"},
    "peppers": {"price": 3.29, "store": "Grocery Store"},
}

async def shopping_logic(context):
    """Create shopping list from ingredients."""
    user_message = context.get_request_message_text().lower()
    
    # Extract ingredients (simple keyword matching)
    found_items = []
    total = 0.0
    
    for ingredient, details in PRODUCTS.items():
        if ingredient in user_message:
            found_items.append(f"- {ingredient.title()}: ${details['price']} at {details['store']}")
            total += details['price']
    
    if not found_items:
        return "I couldn't identify any ingredients. Please list them clearly."
    
    response = "Shopping List:\n\n"
    response += "\n".join(found_items)
    response += f"\n\nEstimated Total: ${total:.2f}"
    
    return response

async def main():
    agent = Agent(
        name="Shopping Agent",
        description="Creates shopping lists for ingredients",
        skills=["shopping", "products", "groceries"],
        executor=shopping_logic
    )
    
    server = AgentServer(agent, port=8003)
    print(f"Shopping Agent running at {server.url}")
    await server.start()

if __name__ == "__main__":
    asyncio.run(main())
```

### Step 5: Build Orchestrator with TaskDelegator

```python
# orchestrator.py
import asyncio
from synqed import TaskDelegator, Client

async def main():
    # Create delegator
    delegator = TaskDelegator()
    
    # Register agents (assumes they're running)
    print("Registering agents...")
    delegator.register_agent(agent_url="http://localhost:8001")  # Weather
    delegator.register_agent(agent_url="http://localhost:8002")  # Recipe
    delegator.register_agent(agent_url="http://localhost:8003")  # Shopping
    
    print(f"Registered {len(delegator.list_agents())} agents")
    print()
    
    # Orchestrate a complete dinner planning flow
    location = input("Enter location (london/paris/miami): ")
    
    print("\n" + "=" * 60)
    print("STEP 1: Getting weather...")
    print("=" * 60)
    
    weather = await delegator.submit_task(
        f"What's the weather in {location}?",
        require_skills=["weather"]
    )
    print(weather)
    
    print("\n" + "=" * 60)
    print("STEP 2: Finding recipe...")
    print("=" * 60)
    
    recipe = await delegator.submit_task(
        f"Suggest dinner for {weather}",
        require_skills=["recipes"]
    )
    print(recipe)
    
    print("\n" + "=" * 60)
    print("STEP 3: Creating shopping list...")
    print("=" * 60)
    
    shopping_list = await delegator.submit_task(
        f"Create shopping list for: {recipe}",
        require_skills=["shopping"]
    )
    print(shopping_list)
    
    print("\n" + "=" * 60)
    print("DINNER PLAN COMPLETE!")
    print("=" * 60)
    
    await delegator.close_all()

if __name__ == "__main__":
    asyncio.run(main())
```

### Step 6: Run the System

Terminal 1:
```bash
python weather_agent.py
```

Terminal 2:
```bash
python recipe_agent.py
```

Terminal 3:
```bash
python shopping_agent.py
```

Terminal 4:
```bash
python orchestrator.py
```

**Exercise:**
1. Run the complete system
2. Trace the messages between agents
3. Add logging to see the flow
4. Add error handling (what if an agent is down?)

## 4.2 Project 2: Research Assistant System

**Goal:** Build agents that collaborate on research tasks.

**Agents:**
1. **Search Agent** - Finds relevant papers/articles
2. **Summarizer Agent** - Summarizes content
3. **Writer Agent** - Compiles final report

**Your task:** Design and implement this yourself using the patterns from Project 1.

**Hints:**
- Search Agent: Simulate with a dict of "papers"
- Summarizer: Take text, return shorter version
- Writer: Combine summaries into report
- Orchestrator: Coordinate the research flow

## 4.3 Project 3: Customer Support System

**Goal:** Multi-agent customer support.

**Agents:**
1. **Triage Agent** - Categorizes support tickets
2. **Knowledge Base Agent** - Searches FAQs
3. **Escalation Agent** - Handles complex issues
4. **Email Agent** - Sends responses

**Your task:** Build this system from scratch.

## 4.4 Advanced Patterns

### Pattern 1: Agent Chains

```python
# Sequential processing through multiple agents
result1 = await agent1.process(input)
result2 = await agent2.process(result1)
result3 = await agent3.process(result2)
```

### Pattern 2: Agent Voting

```python
# Get consensus from multiple agents
results = await delegator.submit_task_to_multiple(query)
best_result = vote(results)  # Your voting logic
```

### Pattern 3: Hierarchical Agents

```python
# Supervisor agent delegates to worker agents
supervisor = Agent(name="Supervisor", ...)
workers = [Agent(name=f"Worker{i}", ...) for i in range(5)]

# Supervisor decides which worker to use
worker = supervisor.choose_worker(task)
result = await worker.process(task)
```

### Pattern 4: Feedback Loops

```python
# Agent requests clarification
result = await agent.process(input)
if agent.needs_clarification:
    clarification = await ask_user()
    result = await agent.process(clarification)
```

## 4.5 Key Takeaways from Phase 4

✅ Real systems need **orchestration**  
✅ Agents should be **specialized**, not general  
✅ Design the **flow** before coding  
✅ Error handling is **critical**  
✅ Logging helps debug **distributed** systems

---

# Phase 5: Production & Advanced Topics (Days 19-24)

## 5.1 Testing Multi-Agent Systems

### Unit Tests for Individual Agents

```python
# test_weather_agent.py
import pytest
from synqed import Agent, Client
import asyncio

@pytest.mark.asyncio
async def test_weather_agent():
    """Test weather agent in isolation."""
    # Create agent
    agent = Agent(...)
    server = AgentServer(agent, port=9000)
    
    # Start in background
    await server.start_background()
    
    try:
        # Test it
        async with Client("http://localhost:9000") as client:
            response = await client.ask("Weather in London?")
            assert "London" in response
            assert "°C" in response
    finally:
        await server.stop()
```

### Integration Tests

```python
# test_integration.py
import pytest

@pytest.mark.asyncio
async def test_complete_flow():
    """Test weather → recipe → shopping flow."""
    # Start all agents
    # ...
    
    # Run orchestrator
    # ...
    
    # Assert complete workflow
    assert "recipe" in result
    assert "shopping list" in result
```

### Testing Strategies

1. **Mock external dependencies**
2. **Test timeout handling**
3. **Test agent discovery failures**
4. **Test network errors**
5. **Test concurrent requests**

## 5.2 Error Handling

### Agent-Level Errors

```python
async def robust_agent_logic(context):
    try:
        result = await risky_operation()
        return result
    except TimeoutError:
        return "I'm taking too long. Try again later."
    except ExternalAPIError as e:
        return f"External service failed: {e}"
    except Exception as e:
        # Log error
        logger.error(f"Unexpected error: {e}")
        return "Something went wrong. Please try again."
```

### Orchestrator-Level Errors

```python
async def robust_orchestrator():
    try:
        result = await delegator.submit_task(task)
    except NoAgentAvailable:
        # Fallback to default agent
        result = await fallback_agent.process(task)
    except AgentTimeout:
        # Retry with different agent
        result = await delegator.submit_task(task, timeout=60)
    except AllAgentsFailed:
        # Give up gracefully
        return "System unavailable. Try again later."
```

### Circuit Breaker Pattern

```python
class CircuitBreaker:
    def __init__(self, threshold=5, timeout=60):
        self.failures = 0
        self.threshold = threshold
        self.timeout = timeout
        self.last_failure_time = None
        self.state = "closed"  # closed, open, half-open
    
    async def call(self, func, *args, **kwargs):
        if self.state == "open":
            if time.time() - self.last_failure_time > self.timeout:
                self.state = "half-open"
            else:
                raise CircuitBreakerOpen("Circuit breaker is open")
        
        try:
            result = await func(*args, **kwargs)
            self.failures = 0
            self.state = "closed"
            return result
        except Exception as e:
            self.failures += 1
            self.last_failure_time = time.time()
            if self.failures >= self.threshold:
                self.state = "open"
            raise

# Usage
breaker = CircuitBreaker()
result = await breaker.call(agent.process, task)
```

## 5.3 Monitoring & Observability

### Add Logging

```python
import logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

async def monitored_agent_logic(context):
    request_id = context.task_id
    logger.info(f"[{request_id}] Processing request")
    
    start_time = time.time()
    try:
        result = await process(context)
        duration = time.time() - start_time
        logger.info(f"[{request_id}] Completed in {duration:.2f}s")
        return result
    except Exception as e:
        logger.error(f"[{request_id}] Failed: {e}")
        raise
```

### Add Metrics

```python
from prometheus_client import Counter, Histogram

requests_total = Counter('agent_requests_total', 'Total requests')
request_duration = Histogram('agent_request_duration_seconds', 'Request duration')

async def instrumented_agent_logic(context):
    requests_total.inc()
    
    with request_duration.time():
        result = await process(context)
    
    return result
```

### Distributed Tracing

```python
# Using OpenTelemetry
from opentelemetry import trace

tracer = trace.get_tracer(__name__)

async def traced_agent_logic(context):
    with tracer.start_as_current_span("agent.process") as span:
        span.set_attribute("agent.name", "Weather Agent")
        span.set_attribute("task.id", context.task_id)
        
        result = await process(context)
        
        span.set_attribute("result.length", len(result))
        return result
```

## 5.4 Deployment

### Docker Compose Setup

```yaml
# docker-compose.yml
version: '3.8'

services:
  weather-agent:
    build: ./weather_agent
    ports:
      - "8001:8001"
    environment:
      - PORT=8001
    restart: always
  
  recipe-agent:
    build: ./recipe_agent
    ports:
      - "8002:8002"
    environment:
      - PORT=8002
    restart: always
  
  shopping-agent:
    build: ./shopping_agent
    ports:
      - "8003:8003"
    environment:
      - PORT=8003
    restart: always
  
  orchestrator:
    build: ./orchestrator
    ports:
      - "8000:8000"
    depends_on:
      - weather-agent
      - recipe-agent
      - shopping-agent
    environment:
      - WEATHER_URL=http://weather-agent:8001
      - RECIPE_URL=http://recipe-agent:8002
      - SHOPPING_URL=http://shopping-agent:8003
```

### Kubernetes Deployment

```yaml
# weather-agent-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: weather-agent
spec:
  replicas: 3
  selector:
    matchLabels:
      app: weather-agent
  template:
    metadata:
      labels:
        app: weather-agent
    spec:
      containers:
      - name: weather-agent
        image: weather-agent:latest
        ports:
        - containerPort: 8001
        env:
        - name: PORT
          value: "8001"
        livenessProbe:
          httpGet:
            path: /.well-known/agent-card
            port: 8001
          initialDelaySeconds: 30
          periodSeconds: 10
        resources:
          limits:
            cpu: "1"
            memory: "512Mi"
          requests:
            cpu: "0.5"
            memory: "256Mi"
---
apiVersion: v1
kind: Service
metadata:
  name: weather-agent
spec:
  selector:
    app: weather-agent
  ports:
  - port: 8001
    targetPort: 8001
  type: ClusterIP
```

## 5.5 Security

### Authentication

```python
# Add API key authentication
from synqed import Agent, AgentServer

agent = Agent(
    name="Secure Agent",
    skills=["secure"],
    executor=logic,
    security_schemes={
        "apiKey": {
            "type": "apiKey",
            "in": "header",
            "name": "X-API-Key"
        }
    }
)

# Add middleware to validate keys
async def validate_api_key(request):
    api_key = request.headers.get("X-API-Key")
    if not is_valid_key(api_key):
        raise Unauthorized()
```

### Rate Limiting

```python
from collections import defaultdict
import time

class RateLimiter:
    def __init__(self, max_requests=100, window=60):
        self.max_requests = max_requests
        self.window = window
        self.requests = defaultdict(list)
    
    def allow_request(self, client_id):
        now = time.time()
        
        # Clean old requests
        self.requests[client_id] = [
            t for t in self.requests[client_id]
            if now - t < self.window
        ]
        
        # Check limit
        if len(self.requests[client_id]) >= self.max_requests:
            return False
        
        self.requests[client_id].append(now)
        return True

limiter = RateLimiter()

async def rate_limited_logic(context):
    client_id = context.get_client_id()
    if not limiter.allow_request(client_id):
        raise RateLimitExceeded()
    
    return await process(context)
```

### Input Validation

```python
from pydantic import BaseModel, validator

class RequestSchema(BaseModel):
    query: str
    max_results: int = 10
    
    @validator('query')
    def query_not_empty(cls, v):
        if not v or not v.strip():
            raise ValueError('Query cannot be empty')
        return v
    
    @validator('max_results')
    def max_results_in_range(cls, v):
        if v < 1 or v > 100:
            raise ValueError('max_results must be between 1 and 100')
        return v

async def validated_logic(context):
    # Parse request
    try:
        data = RequestSchema(**context.get_data())
    except ValidationError as e:
        return f"Invalid request: {e}"
    
    # Process valid request
    return await process(data.query, data.max_results)
```

## 5.6 Performance Optimization

### Caching

```python
from functools import lru_cache
import asyncio

class AsyncLRU:
    def __init__(self, maxsize=128):
        self.cache = {}
        self.maxsize = maxsize
    
    def __call__(self, func):
        async def wrapper(*args, **kwargs):
            key = str(args) + str(kwargs)
            
            if key in self.cache:
                return self.cache[key]
            
            result = await func(*args, **kwargs)
            
            if len(self.cache) >= self.maxsize:
                # Remove oldest
                self.cache.pop(next(iter(self.cache)))
            
            self.cache[key] = result
            return result
        
        return wrapper

@AsyncLRU(maxsize=100)
async def expensive_operation(query):
    # Simulate expensive operation
    await asyncio.sleep(2)
    return f"Result for {query}"
```

### Connection Pooling

```python
# Reuse clients for better performance
class AgentPool:
    def __init__(self, agent_url, pool_size=10):
        self.agent_url = agent_url
        self.pool = asyncio.Queue(maxsize=pool_size)
        self.pool_size = pool_size
    
    async def initialize(self):
        for _ in range(self.pool_size):
            client = Client(self.agent_url)
            await client.connect()
            await self.pool.put(client)
    
    async def get_client(self):
        return await self.pool.get()
    
    async def return_client(self, client):
        await self.pool.put(client)
    
    async def execute(self, message):
        client = await self.get_client()
        try:
            result = await client.ask(message)
            return result
        finally:
            await self.return_client(client)

# Usage
pool = AgentPool("http://localhost:8000")
await pool.initialize()

result = await pool.execute("Hello")
```

### Parallel Processing

```python
async def process_batch(tasks):
    """Process multiple tasks in parallel."""
    results = await asyncio.gather(
        *[delegator.submit_task(task) for task in tasks],
        return_exceptions=True
    )
    
    # Filter out errors
    successful = [r for r in results if not isinstance(r, Exception)]
    failed = [r for r in results if isinstance(r, Exception)]
    
    return successful, failed
```

## 5.7 Advanced Agent Patterns

### Self-Healing Agents

```python
class SelfHealingAgent:
    def __init__(self, agent, max_retries=3):
        self.agent = agent
        self.max_retries = max_retries
        self.health_check_interval = 30
    
    async def start(self):
        """Start agent with health monitoring."""
        await self.agent.start_background()
        asyncio.create_task(self._health_monitor())
    
    async def _health_monitor(self):
        """Continuously monitor and restart if needed."""
        while True:
            await asyncio.sleep(self.health_check_interval)
            
            if not await self._is_healthy():
                logger.warning("Agent unhealthy, restarting...")
                await self._restart()
    
    async def _is_healthy(self):
        """Check if agent is responding."""
        try:
            async with Client(self.agent.url, timeout=5) as client:
                await client.ask("health check")
            return True
        except:
            return False
    
    async def _restart(self):
        """Restart the agent."""
        await self.agent.stop()
        await asyncio.sleep(5)
        await self.agent.start_background()
```

### Adaptive Agents

```python
class AdaptiveAgent:
    """Agent that adapts based on performance."""
    
    def __init__(self, agent):
        self.agent = agent
        self.success_rate = 1.0
        self.total_requests = 0
        self.successful_requests = 0
    
    async def process(self, task):
        """Process with adaptation."""
        self.total_requests += 1
        
        # Adapt timeout based on success rate
        timeout = 30 if self.success_rate > 0.8 else 60
        
        try:
            result = await asyncio.wait_for(
                self.agent.process(task),
                timeout=timeout
            )
            self.successful_requests += 1
            self._update_success_rate()
            return result
        except asyncio.TimeoutError:
            self._update_success_rate()
            raise
    
    def _update_success_rate(self):
        """Update success rate metric."""
        self.success_rate = self.successful_requests / self.total_requests
        
        # Adapt behavior based on success rate
        if self.success_rate < 0.5:
            logger.warning(f"Low success rate: {self.success_rate:.2%}")
            # Could trigger circuit breaker, alert, etc.
```

## 5.8 Key Takeaways from Phase 5

✅ Production systems need **monitoring**  
✅ Test at **multiple levels** (unit, integration, e2e)  
✅ Error handling is **not optional**  
✅ Security must be **built-in**, not added later  
✅ Performance optimization is **iterative**

---

# Phase 6: Mastery Projects (Days 25-30)

## 6.1 Build a Complete Application

Choose ONE project and build it production-ready:

### Option A: AI Research Assistant
- Paper search agent
- Summarization agent
- Citation manager agent
- Report writer agent
- Full web UI

### Option B: E-commerce Helper
- Product search agent
- Price comparison agent
- Review analyzer agent
- Recommendation agent
- Order tracking agent

### Option C: DevOps Assistant
- Log analyzer agent
- Metric monitor agent
- Incident responder agent
- Deployment agent
- Documentation agent

**Requirements:**
- ✅ 5+ specialized agents
- ✅ Complete error handling
- ✅ Monitoring & logging
- ✅ Unit + integration tests
- ✅ Docker deployment
- ✅ API documentation
- ✅ User interface

## 6.2 Contribute to Synq

**Ways to contribute:**

1. **Add new features**
   - Better error messages
   - More helper methods
   - New delegation strategies

2. **Improve documentation**
   - Add examples
   - Fix typos
   - Explain advanced patterns

3. **Write tests**
   - Increase test coverage
   - Add edge cases
   - Performance benchmarks

4. **Optimize performance**
   - Profile bottlenecks
   - Reduce latency
   - Memory optimization

## 6.3 Explore Advanced Topics

### Topic 1: LLM Integration

```python
# Integrate actual LLMs into agents
import anthropic

async def llm_agent_logic(context):
    """Agent powered by Claude."""
    user_message = context.get_request_message_text()
    
    client = anthropic.Anthropic()
    response = await client.messages.create(
        model="claude-3-5-sonnet-20241022",
        max_tokens=1024,
        messages=[{"role": "user", "content": user_message}]
    )
    
    return response.content[0].text
```

### Topic 2: Agent Memory

```python
class StatefulAgent:
    """Agent with conversation memory."""
    
    def __init__(self):
        self.conversations = {}  # user_id → messages
    
    async def process(self, context):
        user_id = context.get_user_id()
        
        # Get conversation history
        history = self.conversations.get(user_id, [])
        
        # Add new message
        user_message = context.get_request_message_text()
        history.append({"role": "user", "content": user_message})
        
        # Generate response with context
        response = await self.generate_with_history(history)
        
        # Store response
        history.append({"role": "agent", "content": response})
        self.conversations[user_id] = history
        
        return response
```

### Topic 3: Multi-Modal Agents

```python
async def multimodal_agent_logic(context):
    """Agent that handles text, images, and files."""
    parts = context.request.message.parts
    
    for part in parts:
        if isinstance(part, TextPart):
            text = part.text
            # Process text
        
        elif isinstance(part, FilePart):
            if part.mime_type.startswith("image/"):
                # Process image
                image_data = part.inline_data or await download(part.uri)
                result = await analyze_image(image_data)
            
            elif part.mime_type == "application/pdf":
                # Process PDF
                pdf_data = await download(part.uri)
                text = extract_text_from_pdf(pdf_data)
        
        elif isinstance(part, DataPart):
            # Process structured data
            data = part.data
            result = await process_structured_data(data)
    
    return compiled_response
```

### Topic 4: Agent Learning

```python
class LearningAgent:
    """Agent that improves from feedback."""
    
    def __init__(self):
        self.feedback_db = []
        self.model = None
    
    async def process(self, task):
        """Process with learned optimizations."""
        # Use learned model to optimize
        optimized_approach = self.model.predict(task)
        
        result = await execute_with_approach(task, optimized_approach)
        
        return result
    
    async def receive_feedback(self, task_id, rating, comments):
        """Learn from user feedback."""
        self.feedback_db.append({
            "task_id": task_id,
            "rating": rating,
            "comments": comments
        })
        
        # Retrain model periodically
        if len(self.feedback_db) % 100 == 0:
            await self._retrain_model()
    
    async def _retrain_model(self):
        """Retrain on accumulated feedback."""
        # Your ML training code here
        pass
```

---

# Phase 7: Ongoing Mastery (Continuous)

## 7.1 Stay Updated

- 📚 Read A2A specification updates
- 💬 Join A2A community discussions
- 🐛 Follow Synq GitHub issues
- 📝 Read agent architecture papers

## 7.2 Practice Patterns

Build small agents regularly:

**Week 1:** Build a calculator agent  
**Week 2:** Build a translator agent  
**Week 3:** Build a code reviewer agent  
**Week 4:** Build a data analyzer agent

**Goal:** Muscle memory for agent patterns.

## 7.3 Teach Others

- Write blog posts
- Create tutorials
- Answer questions
- Give talks

**Teaching is the ultimate test of mastery.**

## 7.4 Build a Portfolio

Create a showcase of agents:

```
my-agent-portfolio/
├── simple-agents/
│   ├── calculator-agent/
│   ├── translator-agent/
│   └── weather-agent/
├── multi-agent-systems/
│   ├── research-assistant/
│   ├── customer-support/
│   └── ecommerce-helper/
└── advanced-patterns/
    ├── self-healing-agent/
    ├── adaptive-agent/
    └── learning-agent/
```

## 7.5 Contribute to Ecosystem

- Build plugins/extensions
- Create integration libraries
- Write agent templates
- Build development tools

---

# Appendix: Resources

## Documentation Links

- A2A Protocol: `/Users/dorsa/Desktop/PROJECTS/synq_2/A2A/`
- A2A Python SDK: `/Users/dorsa/Desktop/PROJECTS/synq_2/a2a-python/`
- Synq Framework: `/Users/dorsa/Desktop/PROJECTS/synq_2/Synq/`

## Example Code

- Basic Agent: `/Users/dorsa/Desktop/PROJECTS/synq_2/Synq/examples/basic_agent.py`
- Client Example: `/Users/dorsa/Desktop/PROJECTS/synq_2/Synq/examples/client_example.py`
- Multi-Agent: `/Users/dorsa/Desktop/PROJECTS/synq_2/Synq/examples/multi_agent_delegation.py`

## Learning Checkpoints

### Checkpoint 1 (End of Phase 2):
- [ ] Can explain A2A protocol to someone else
- [ ] Built a raw A2A agent from scratch
- [ ] Understand Agent Cards, Tasks, Messages, Artifacts

### Checkpoint 2 (End of Phase 3):
- [ ] Built multiple Synq agents
- [ ] Understand how Synq wraps A2A
- [ ] Can use all Synq components

### Checkpoint 3 (End of Phase 4):
- [ ] Built 3+ multi-agent systems
- [ ] Implemented different orchestration patterns
- [ ] Solved real problems with agents

### Checkpoint 4 (End of Phase 5):
- [ ] Added comprehensive testing
- [ ] Implemented monitoring & logging
- [ ] Deployed agents to production

### Checkpoint 5 (End of Phase 6):
- [ ] Built a complete application
- [ ] Production-grade code quality
- [ ] Ready to build anything with agents

---

# Final Words

**Mastery is not a destination, it's a journey.**

You now have the complete roadmap from zero to mastery. The key is **consistent practice**:

1. **Code daily** - Even 30 minutes
2. **Build projects** - Theory without practice is useless
3. **Experiment freely** - Break things, learn from failures
4. **Teach others** - Ultimate test of understanding
5. **Stay curious** - Keep asking "why?" and "how?"

**Start with Phase 0. Don't skip ahead.**

Each phase builds essential understanding for the next. Rushing leads to gaps that will haunt you later.

**Good luck on your journey to mastery! 🚀**

---

*Document Version: 1.0*  
*Last Updated: 2025-01-16*  
*Author: AI Learning Path Generator*

