# AG2 v0.9 Release: Introducing the New Group Chat

## TL;DR

- AG2 [`v0.9`](https://pypi.org/project/ag2/0.9/) unifies the previous Group Chat and Swarm into a single, powerful, new Group Chat.
- This unification provides improved flexibility, control, and better foundations for scalability.
- Features include pre-built orchestration patterns, enhanced workflow control, and robust context variables.
- Includes all capabilities of the previous Group Chat and Swarm.
- Swarm is now deprecated but still available. We recommend migrating to the new Group Chat pattern.

---

We're excited to announce the release of AG2 [`v0.9`](https://pypi.org/project/ag2/0.9/), featuring a significant enhancement to multi-agent orchestration. This release brings together our widely used Group Chat and Swarm pattern into a unified, more powerful Group Chat that combines the best aspects of both approaches.

## A New Era for Multi-Agent Orchestration

AG2 has always provided multiple ways to coordinate agents, from simple two-agent conversations to more complex multi-agent orchestration. With [`v0.9`](https://pypi.org/project/ag2/0.9/), we've completely reimagined agent orchestration by merging these approaches into a single, unified Group Chat architecture designed to be **extensible**, **controllable**, and **scalable**.

## Why We Made This Change

Before diving into the details, let's understand why this unification matters:

- **Simplified API** - One coherent way to orchestrate multi-agent workflows.
- **Enhanced Flexibility** - Structured handoffs from Swarm with more diverse hand off targets.
- **Improved State Management** - More robust context sharing and state tracking across agents.
- **Future-proof Design** - A foundation that can grow with evolving multi-agent orchestration needs.

## Key Features of the New Group Chat

### Pre-built Patterns for Different Needs

The new Group Chat implements several orchestration patterns to suit different use cases:

- **[AutoPattern](https://docs.ag2.ai/0.10.4/docs/user-guide/advanced-concepts/orchestration/group-chat/patterns/#autopattern-llm-powered-speaker-selection)**: Uses an LLM to intelligently select the next speaker based on conversation context.
- **[RoundRobinPattern](https://docs.ag2.ai/0.10.4/docs/user-guide/advanced-concepts/orchestration/group-chat/patterns/#roundrobinpattern-sequential-speaker-rotation)**: Creates a sequential rotation where agents speak in a predefined order.
- **[RandomPattern](https://docs.ag2.ai/0.10.4/docs/user-guide/advanced-concepts/orchestration/group-chat/patterns/#randompattern-non-deterministic-speaker-selection)**: Randomly selects the next agent to speak (excluding the current speaker).
- **[ManualPattern](https://docs.ag2.ai/0.10.4/docs/user-guide/advanced-concepts/orchestration/group-chat/patterns/#manualpattern-human-directed-speaker-selection)**: Puts humans in control by allowing manual selection of the next speaker.
- **[DefaultPattern](https://docs.ag2.ai/0.10.4/docs/user-guide/advanced-concepts/orchestration/group-chat/patterns/#defaultpattern-explicit-handoff-control)**: Provides complete control of transitions.

### Dynamic Workflow Control with Handoffs

The handoff system has been enhanced to provide powerful control over agent transitions:

- **[LLM-based conditions](https://docs.ag2.ai/0.10.4/docs/user-guide/advanced-concepts/orchestration/group-chat/handoffs/#llm-based-conditions)**: Use the LLM to determine when to transfer control based on message content.

```python
# Define LLM-based handoffs
customer_service.handoffs.add_llm_conditions([
    OnCondition(
        target=AgentTarget(billing_specialist),
        condition=StringLLMCondition(prompt="When the query is about billing or payments."),
        available=AvailableContextExpression(expression=ContextExpression("${account_status} == 'active'")) # PLEASE CHECK
    )
])
```

- **[Context-based conditions](https://docs.ag2.ai/0.10.4/docs/user-guide/advanced-concepts/orchestration/group-chat/handoffs/#context-based-conditions)**: Use shared context variables to make transition decisions.

```python
# Define context-based handoffs
# This will route to the escalation agent when the issue priority is high
support_agent.handoffs.add_context_condition(
    OnContextCondition(
        target=AgentTarget(agent=escalation_agent),
        condition=ExpressionContextCondition(
            expression=ContextExpression("${issue_priority} == 'high'")
        )
    )
)

# This will route to a supervisor when retry count exceeds 3
support_agent.handoffs.add_context_condition(
    OnContextCondition(
        target=AgentTarget(agent=supervisor_agent),
        condition=ExpressionContextCondition(
            expression=ContextExpression("${retry_count} > 3")
        )
    )
)
```

- **[After-work behavior](https://docs.ag2.ai/0.10.4/docs/user-guide/advanced-concepts/orchestration/group-chat/handoffs/#after-work-behavior)**: Define default behavior for when an agent completes its work.

```python
# Define what happens after an agent completes its work
billing_specialist.handoffs.set_after_work(RevertToUserTarget())
```

### Enhanced Transition Targets

The new Group Chat pattern introduces powerful **[Transition Targets](https://docs.ag2.ai/0.10.4/docs/user-guide/advanced-concepts/orchestration/group-chat/handoffs/#transition-targets)** - which define where control should go after an agent completes its work. This system goes beyond simply transferring to another agent:

- **AgentTarget/AgentNameTarget**: Transfer to a specific agent directly or by name.
- **RevertToUserTarget**: Return control to the human user.
- **TerminateTarget**: End the conversation entirely.
- **StayTarget**: Keep control with the current agent.
- **RandomAgentTarget**: Randomly select from a list of possible agents.
- **AskUserTarget**: You decide who the next agent is.
- **NestedChatTarget**: Transfer to a nested conversation.
- **GroupChatTarget**: Transfer to a new group chat (nested group chats!).
- **GroupManagerTarget**: Transfer to the group chat manager who will select the next speaker.

This flexible targeting system enables complex workflows where control might need to move between different levels of conversation or to specialized processes.

### Shared Context Management

The new `ContextVariables` class provides a structured way to share and maintain state across agents. This shared memory enables agents to:

- Maintain consistent information throughout a conversation.
- Make decisions based on the current state.
- Pass structured data between agents.
- Control workflow based on context values.

Here's how you can create and use context variables:

```python
# Initialize context variables
context = ContextVariables(data={
    "customer_id": "C12345",
    "subscription_tier": "premium",
    "support_history": []
})

# Update context in a tool function
def log_customer_interaction(
    issue_type: str,
    resolution: str,
    context_variables: ContextVariables
) -> ReplyResult:
    """Log a customer interaction in the context."""

# Add this interaction to history
    support_history = context_variables.get("support_history", [])
    support_history.append({
        "timestamp": datetime.now().isoformat(),
        "issue_type": issue_type,
        "resolution": resolution
    })

context_variables["support_history"] = support_history
    context_variables["last_issue_type"] = issue_type

return ReplyResult(
        message="Interaction logged successfully",
        # Passing it into ReplyResult updates the shared context
        context_variables=context_variables
    )
```

## Getting Started with the New Group Chat

Let's explore a practical example of how the new Group Chat pattern can transform travel planning workflows. We'll implement a personalized travel itinerary system where specialized agents collaborate to assist users based on their membership status.

### The Use Case

In this scenario, we want to create a travel planning system that:

- Verifies user membership and collects travel information.
- Routes users to different advisors based on membership status.
- Provides personalized itineraries using customer preferences.
- Facilitates collaboration between advisors and destination specialists.
- Tracks itinerary status from proposal to approval.

### Implementing the workflow with the new GroupChat

```python
from typing import Annotated
from autogen import ConversableAgent, LLMConfig
from autogen.agentchat import initiate_group_chat
from autogen.agentchat.group.patterns import DefaultPattern
from autogen.agentchat.group import (
    ReplyResult,
    AgentTarget,
    AgentNameTarget,
    ContextVariables,
    RevertToUserTarget,
    OnCondition,
    StringLLMCondition,
    TerminateTarget
)

# Mock user database
MEMBER_DATABASE = {
    "P12345": {
        "name": "Alex Johnson",
        "membership": "premium",
        "preferences": [
            "5-star hotels",
            "fine dining",
            "private tours",
            "exclusive experiences",
        ],
    },
    "P67890": {
        "name": "Taylor Williams",
        "membership": "premium",
        "preferences": [
            "boutique hotels",
            "local cuisine",
            "cultural experiences",
            "adventure activities",
        ],
    },
    "S12345": {
        "name": "Jordan Smith",
        "membership": "standard",
        "preferences": ["budget-friendly", "popular attractions"],
    },
    "S67890": {
        "name": "Casey Brown",
        "membership": "standard",
        "preferences": ["family-friendly", "group tours"],
    },
}

# Initialize context variables
travel_context = ContextVariables(
    data={
        "user_info_collected": False,
        "membership_status": None,
        "name": None,
        "member_id": None,
        "destination": None,
        "travel_days": None,
        "preferences": [],
        "final_itinerary": None,
    }
)

# Concierge function to collect user information
def collect_user_info(
    member_id: Annotated[str, "User's membership ID"],
    destination: Annotated[str, "User's travel destination"],
    travel_days: Annotated[int, "Number of travel days"],
    context_variables: ContextVariables,
) -> ReplyResult:
    """Collect and verify user information against the database."""

# Check if all required information is provided, else ask for it
    if not member_id:
        return ReplyResult(
            message="Please provide your membership ID.",
            target=RevertToUserTarget(),
            context_variables=context_variables,
        )
    if not destination:
        return ReplyResult(
            message="Please provide your travel destination.",
            target=RevertToUserTarget(),
            context_variables=context_variables,
        )
    if not travel_days:
        return ReplyResult(
            message="Please provide the number of travel days.",
            target=RevertToUserTarget(),
            context_variables=context_variables,
        )

# Check if member exists in database
    if member_id in MEMBER_DATABASE:
        user_data = MEMBER_DATABASE[member_id]

# Store user information in context
        context_variables["name"] = user_data["name"]
        context_variables["member_id"] = member_id
        context_variables["destination"] = destination
        context_variables["travel_days"] = travel_days
        context_variables["membership_status"] = user_data["membership"]
        context_variables["preferences"] = user_data["preferences"]
        context_variables["user_info_collected"] = True

if user_data["membership"] == "premium":
            message = f"Welcome back, {user_data['name']}! I see you're a premium member with preferences for {', '.join(user_data['preferences'])}. I'll connect you with our Premium Travel Advisor for your {travel_days}-day trip to {destination}."
            target = AgentNameTarget("premium_advisor")
        else:
            message = f"Hello {user_data['name']}. I'll connect you with our Standard Travel Advisor to help plan your {travel_days}-day trip to {destination}."
            target = AgentNameTarget("standard_advisor")
    else:
        message = f"I couldn't find your member ID in our system. Please check your ID and try again, or register as a new member."
        target = RevertToUserTarget()

return ReplyResult(
        message=message, target=target, context_variables=context_variables
    )

# Function for destination specialist to provide information
def provide_destination_info(
    destination_info: Annotated[str, "Detailed information about the destination"],
    context_variables: ContextVariables,
) -> ReplyResult:
    """Provide destination information and return to the requesting agent."""

context_variables["destination_info"] = destination_info
    target_agent_name  = "premium_advisor" if context_variables["membership_status"] == "premium" else "standard_advisor"

return ReplyResult(
        message=destination_info,
        target=AgentNameTarget(target_agent_name),
        context_variables=context_variables,
    )

# Get more destination information

def get_more_destination_info(
    more_info_request: Annotated[str, "What additional information is needed"],
    context_variables: ContextVariables,
) -> ReplyResult:
    """Get more information from the destination agent."""

message = f"Could you please provide more destination details based on the member's request: {more_info_request}?"
    return ReplyResult(
        message=message,
        target=AgentTarget(destination_specialist),
        context_variables=context_variables,
    )

# Function to track itinerary status, when proposed set the status to "proposed" and when approved by the user set it to "approved"
def track_itinerary_status(
    detailed_itinerary_info: Annotated[str, "Full itinerary in markdown format"],
    itinerary_status: Annotated[str, "Status of the itinerary (proposed/approved)"],
    context_variables: ContextVariables,
) -> ReplyResult:
    """Track the status of the itinerary and return to the user for feedback."""

context_variables["itinerary_status"] = itinerary_status

if itinerary_status == "approved":
        context_variables["final_itinerary"] = detailed_itinerary_info
        message = "Thank you. Enjoy your trip!"
        target = TerminateTarget()
    else:
        message = detailed_itinerary_info
        target = RevertToUserTarget()

return ReplyResult(
        message=message, target=target, context_variables=context_variables
    )

# Configure LLM
llm_config = LLMConfig(config_list={"api_type": "openai", "model": "gpt-5-nano", "parallel_tool_calls": False})

# Create agents
concierge = ConversableAgent(
    name="concierge",
    system_message="""You are a travel concierge who verifies membership and collects travel information.\n    Ask for the user for the below information without fail if not already provided. You can ask only one question at a time:\n        - users's member ID,\n        - destination they wish to travel,\n        - number of travel days\n    Do not proceed unless you have all the above information.\n    Use the collect_user_info function to verify their membership and route them appropriately.""",
    functions=[collect_user_info],
    llm_config=llm_config,
)

premium_advisor = ConversableAgent(
    name="premium_advisor",
    system_message="""You are a premium travel advisor who creates luxury itineraries.\n    Check the user's preferences in the context variables and customize their itinerary accordingly.\n    When you need specific destination information, consult the destination specialist.\n    For premium members, ALWAYS include:\n    - Their preferred accommodation type (usually 5-star or boutique hotels)\n    - Their dining preferences (often fine dining or local cuisine)\n    - Exclusive experiences not available to standard members\n    - Private transportation options\n    Propose the itinerary to the user and track the status using the track_itinerary_status function.\n    If the user requires more information about a destination, use the get_more_destination_info function.\n    If the user approves or accepts the itinerary, set the status to "approved".\n    """,
    functions=[track_itinerary_status, get_more_destination_info],
    llm_config=llm_config,
)

standard_advisor = ConversableAgent(
    name="standard_advisor",
    system_message="""You are a standard travel advisor who creates basic itineraries.\n    Check the user's preferences in the context variables.\n    When you need specific destination information, consult the destination specialist.\n    For standard members, include:\n    - Affordable accommodation options\n    - Popular restaurants at various price points\n    - Standard tourist attractions\n    - Public transportation or group tour options\n    Propose the itinerary to the user and track the status using the track_itinerary_status function.\n    If the user requires more information about a destination, use the get_more_destination_info function.\n    If the user approves or accepts the itinerary, set the status to "approved".\n    """,
    functions=[track_itinerary_status, get_more_destination_info],
)

destination_specialist = ConversableAgent(
    name="destination_specialist",
    system_message="""You are a destination specialist with detailed knowledge about travel locations.\n    When asked by an advisor, provide comprehensive information about the requested destination.\n    Include details about attractions, local customs, best times to visit, and insider tips.\n    Use the provide_destination_info function to respond.""",
    functions=[provide_destination_info],
    llm_config=llm_config,
)

# Create user agent
user = ConversableAgent(name="user", human_input_mode="ALWAYS")

# Set the after work for the concierge
concierge.handoffs.set_after_work(RevertToUserTarget())

# Define handoffs using LLM-based conditions for advisors to consult
# the destination specialist for destination info
premium_advisor.handoffs.add_llm_conditions([
    OnCondition(
        target=AgentTarget(destination_specialist),
        condition=StringLLMCondition(
            prompt="Consult the destination specialist if you need specific information about the destination. Add a note to the destination specialist to return to the 'premium_advisor' after gathering the information."
        ),
    ),
])

standard_advisor.handoffs.add_llm_conditions([
    OnCondition(
        target=AgentTarget(destination_specialist),
        condition=StringLLMCondition(
            prompt="Consult the destination specialist if you need specific information about the destination. Add a note to the destination specialist to return to the 'standard_advisor' after gathering the information."
        ),
    )
])

# After providing the itinerary, return to the user for feedback
premium_advisor.handoffs.set_after_work(RevertToUserTarget())
standard_advisor.handoffs.set_after_work(RevertToUserTarget())

# After providing information, destination specialist returns to the
# requesting advisor via the provide_destination_info tool call
# If they don't call the tool, get more info from the user
destination_specialist.handoffs.set_after_work(RevertToUserTarget())

# Create pattern for orchestration
# Use the DefaultPattern as we want to be in full control of the flow
pattern = DefaultPattern(
    initial_agent=concierge,
    agents=[concierge, premium_advisor, standard_advisor, destination_specialist],
    user_agent=user,
    context_variables=travel_context,
)

# Start the group chat
result, final_context, last_agent = initiate_group_chat(
    pattern=pattern,
    messages="Hello, I'd like to plan a vacation.",
    max_rounds=30
)

# Print out the approved itinerary
if final_context["itinerary_status"] == "approved":
    print(f"Approved Itinerary:\n{final_context['final_itinerary']}")
```

### Running This Example

To run this example yourself:

1. **Save the code** to a file (e.g., `customer_service_demo.py`)
2. **Set up your OpenAI API key** - This example uses the `gpt-5-nano` model from OpenAI:
   
```bash 
export OPENAI_API_KEY=<your_api_key_here>
```

```bash 
set OPENAI_API_KEY=<your_api_key_here>
```

3. **Install the required packages**:

```
pip install ag2[openai]
```

4. **Run the script**:

```
python customer_service_demo.py
```

## Migrating from Swarm to the New Group Chat

If you're currently using Swarm, we strongly recommend migrating to the new Group Chat pattern. While Swarm functionality is still available in [`v0.9`](https://pypi.org/project/ag2/0.9/), it's now deprecated and will be removed in a future release.

At a high level, the migration process involves:

1. **Updating imports** to use the new group chat modules instead of swarm modules
2. **Converting context variables** from dictionary-based to the new `ContextVariables` class
3. **Replacing SwarmResult with ReplyResult** in your agent functions
4. **Updating handoff definitions** to use the new agent-based handoff system
5. **Using patterns** to configure and initiate group chats

For detailed migration instructions and examples, please refer to our [Migration Guide](https://docs.ag2.ai/0.10.4/docs/user-guide/advanced-concepts/orchestration/swarm/deprecation).

## Towards Scalable Agent Orchestration

This unified architecture represents the first step toward more scalable agent orchestration by building upon a serializable framework of classes. Additionally, base classes for Targets and Patterns have been designed to allow you to create your own targets and patterns.

## Get Started Today!

The new Group Chat pattern is available now in AG2 [`v0.9`](https://pypi.org/project/ag2/0.9/) and we encourage all users to explore it and build their own multi-agent applications.

To learn more, check out our documentation:

- [Group Chat Overview](https://docs.ag2.ai/0.10.4/docs/user-guide/advanced-concepts/orchestration/orchestrations)
- [Swarm Migration Guide](https://docs.ag2.ai/0.10.4/docs/user-guide/advanced-concepts/orchestration/swarm/deprecation)

We're excited to see what you build with this powerful new framework!
