# Build Group Chat

> Build an AG2 handoff-driven workflow with DefaultPattern, agent handoffs, context variables, and routing. Use when the user wants customer service routing, state-machine workflows, or explicit agent-to-agent transitions.

- Skill: `ag2ai/build-group-chat-2` (Agent Skill)
- Install (CLI): `npx skillmds@latest add ag2ai/build-group-chat-2`
- Raw SKILL.md: https://api.skillmd.com/api/skills/ag2ai/build-group-chat-2/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: ag2ai (https://skillmd.com/u/ag2ai)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/ag2ai/build-group-chat-2

---


# Build AG2 Handoff-Driven Workflow

You are an expert at building AG2 handoff-driven workflows using `run_group_chat` with `DefaultPattern`. When the user wants to build a routing/handoff workflow:

## 1. Understand the Workflow

Ask the user:
- What agents are needed and what are their roles?
- What conditions determine routing between agents?
- Is there shared state that agents need to read/update?
- What happens when an agent finishes (terminate, escalate, loop)?

## 2. Generate the Code

```python
import os
from typing import Annotated
from autogen import ConversableAgent, LLMConfig
from autogen.agentchat import run_group_chat
from autogen.agentchat.group import (
    OnCondition, AgentTarget, TerminateTarget, ContextVariables, StringLLMCondition,
)
from autogen.agentchat.group.patterns import DefaultPattern

llm_config = LLMConfig(
    {"model": "gpt-4o-mini", "api_key": os.environ["OPENAI_API_KEY"]}
)

# Shared state across all agents
ctx = ContextVariables(data={"status": "new", "category": ""})

# 1. Define agents
triage = ConversableAgent(
    name="triage",
    system_message="You are a customer service triage agent. Determine the customer's issue category and route appropriately.",
    llm_config=llm_config,
    human_input_mode="NEVER",
)

billing = ConversableAgent(
    name="billing",
    system_message="You handle billing inquiries: refunds, charges, subscription changes.",
    llm_config=llm_config,
    human_input_mode="NEVER",
)

support = ConversableAgent(
    name="support",
    system_message="You handle technical support issues.",
    llm_config=llm_config,
    human_input_mode="NEVER",
)

user = ConversableAgent(name="user", llm_config=False, human_input_mode="NEVER")

# 2. Define handoffs — OnCondition is evaluated by the LLM
triage.handoffs.add_llm_conditions([
    OnCondition(
        target=AgentTarget(billing),
        condition=StringLLMCondition(prompt="Customer has a billing or payment question"),
    ),
    OnCondition(
        target=AgentTarget(support),
        condition=StringLLMCondition(prompt="Customer has a technical issue or bug report"),
    ),
])

# 3. Define after-work behavior
billing.handoffs.set_after_work(TerminateTarget())
support.handoffs.set_after_work(TerminateTarget())

# 4. Register tools (optional)
@billing.register_for_execution()
@billing.register_for_llm(description="Look up a customer's recent charges")
def lookup_charges(
    customer_id: Annotated[str, "The customer ID to look up"],
) -> str:
    # Replace with actual DB lookup
    return "Last charge: $29.99 on 2025-03-01 (Monthly subscription)"

# 5. Run the group chat with DefaultPattern
result = run_group_chat(
    pattern=DefaultPattern(
        initial_agent=triage,
        agents=[triage, billing, support],
        user_agent=user,
        context_variables=ctx,
    ),
    messages="I was charged twice this month and need a refund",
    max_rounds=15,
)
result.process()
print(result.summary)
```

## 3. Advanced: Multi-Level Routing

For complex workflows with escalation:

```python
escalation = ConversableAgent(
    name="escalation",
    system_message="You handle escalated issues that other agents could not resolve.",
    llm_config=llm_config,
    human_input_mode="NEVER",
)

# Billing can escalate
billing.handoffs.add_llm_condition(
    OnCondition(
        target=AgentTarget(escalation),
        condition=StringLLMCondition(prompt="Issue requires manager approval or is beyond standard policy"),
    )
)

# Support can escalate
support.handoffs.add_llm_condition(
    OnCondition(
        target=AgentTarget(escalation),
        condition=StringLLMCondition(prompt="Issue is a critical bug or requires engineering team"),
    )
)

# After-work with RevertToUserTarget
from autogen.agentchat.group import RevertToUserTarget
escalation.handoffs.set_after_work(RevertToUserTarget())
```

## 4. Context-Aware Routing

Use `OnContextCondition` for deterministic routing based on shared state (faster, no LLM cost):

```python
from autogen.agentchat.group import (
    OnContextCondition, ExpressionContextCondition, ContextExpression, ReplyResult,
)

# Tool that updates context and hands off
@triage.register_for_execution()
@triage.register_for_llm(description="Classify the customer issue")
def classify_issue(
    issue_type: Annotated[str, "billing or support"],
    context_variables: ContextVariables,
) -> ReplyResult:
    context_variables["issue_type"] = issue_type
    target = AgentTarget(billing) if issue_type == "billing" else AgentTarget(support)
    return ReplyResult(
        message=f"Classified as {issue_type}",
        context_variables=context_variables,
        target=target,
    )
```

## 5. Rules

- Use `run_group_chat` with `DefaultPattern`, NOT deprecated `run_swarm`
- Use `ConversableAgent` for all agents
- Import handoff classes from `autogen.agentchat.group`
- Always set `max_rounds` to prevent infinite loops
- Use `TerminateTarget()` as the default after-work to avoid hanging conversations
- Write clear, specific condition strings — the LLM evaluates them to decide routing
- Keep the number of conditions per agent small (2-4) for reliable routing
- Prefer `OnContextCondition` over `OnCondition` for simple state checks (cheaper, deterministic)

