---
name: agentbyte-ai-driven-orchestration
description: Guide for creating AI-Driven multi-agent orchestration in Agentbyte (Chapter 7.4). Use this when you need LLM-based adaptive agent selection instead of fixed turn sequences.
license: MIT
---

Use this skill when implementing multi-agent collaboration where an LLM intelligently selects which agent should respond next based on conversation context and agent capabilities.

## Inputs to confirm
- Which agents are available and how clearly their descriptions express specialization
- What should stop the orchestration
- Whether the selector should see a compact summary or a richer selector context
- Whether worker agents should receive full history or a narrower execution history

## Pattern: Basic AI-Driven Orchestration

```python
import asyncio
from agentbyte import (
    AIOrchestrator,
    Agent,
    AgentHistoryPolicy,
    HistoryContextPolicy,
    SelectorContextPolicy,
    UserMessage,
)
from agentbyte.llm import OpenAIChatCompletionClient
from agentbyte.termination import (
    ConsecutiveAgentTermination,
    MaxMessageTermination,
    PredicateTermination,
    SourceTermination,
    TextMentionTermination,
)

async def main():
    # 1. Create model client
    client = OpenAIChatCompletionClient.from_api_key(model="gpt-4o-mini")

    # 2. Create agents with clear descriptions (LLM uses these for selection)
    researcher = Agent(
        name="researcher",
        description="Research specialist for factual and contextual information",
        instructions=(
            "You are a research specialist. Provide concise factual insights "
            "and clarify missing context when needed."
        ),
        model_client=client
    )

    writer = Agent(
        name="writer",
        description="Content specialist for polished user-facing writing",
        instructions=(
            "You are a writer. Synthesize available information into concise, "
            "high-quality prose. End with TERMINATE when complete."
        ),
        model_client=client
    )

    # 3. Define termination conditions
    # ConsecutiveAgentTermination guards against the selector routing to the same
    # agent repeatedly — the most common failure mode in AI-driven orchestration
    termination = (
        ConsecutiveAgentTermination(max_consecutive=3)
        | TextMentionTermination("TERMINATE")
        | MaxMessageTermination(8)
    )

    # 4. Initialize AI orchestrator (requires model_client for selection)
    orchestrator = AIOrchestrator(
        agents=[researcher, writer],
        termination=termination,
        model_client=client,  # Required: LLM for agent selection
        max_iterations=6,
        history_policy=HistoryContextPolicy(
            default=AgentHistoryPolicy(window="full", format="text")
        ),
        selector_policy=SelectorContextPolicy(
            window="recent",
            message_limit=12,
            format="summary",
        ),
    )

    # 5. Run the collaboration
    task = UserMessage(
        content="Write a short note about the benefits of solar energy",
        source="user"
    )
    
    result = await orchestrator.run(task)

    print(f"Stop reason: {result.stop_message.content}")
    print(f"Iterations: {result.pattern_metadata.get('iterations_completed')}")
    print(f"Selector calls: {result.pattern_metadata.get('selector_calls')}")
    print(f"Total tokens: {result.usage.total_tokens}")

asyncio.run(main())
```

## Pattern: Streaming AI-Driven Orchestration

```python
async for item in orchestrator.run_stream(task, verbose=True):
    if isinstance(item, Message):
        print(f"[{item.source}] {item.content}")
    elif isinstance(item, OrchestrationEvent):
        # AgentSelectionEvent includes AI reasoning
        if hasattr(item, 'reason'):
            print(f"Selection reason: {item.reason}")
    elif isinstance(item, OrchestrationResponse):
        print(f"Complete: {item.stop_message.content}")
```

## How AI Selection Works

1. **Structured Output:** The orchestrator uses LLM with structured output (`AgentSelection` schema) to select the next agent
2. **Context Awareness:** The LLM sees conversation history and all agent descriptions
3. **Reasoning Included:** Selection events include the LLM's reasoning for why it chose that agent
4. **Fallback Safety:** If LLM selection fails or returns invalid agent name, falls back to round-robin

## Policy Surface

```python
orchestrator = AIOrchestrator(
    agents=[agent1, agent2, agent3],
    termination=termination_condition,
    model_client=selection_model_client,
    max_iterations=10,
    history_policy=HistoryContextPolicy(
        default=AgentHistoryPolicy(window="full", format="text")
    ),
    selector_policy=SelectorContextPolicy(
        window="recent",
        message_limit=12,
        format="summary",
    ),
)
```

Use the two policies for different jobs:
- `HistoryContextPolicy`: contains a default `AgentHistoryPolicy` plus optional per-agent `AgentHistoryPolicy` overrides for worker execution
- `SelectorContextPolicy`: controls what the selector model sees when deciding which agent should go next

`AgentHistoryPolicy` is the per-agent rule that actually defines:
- `window`
- `message_limit`
- `format`
- `include_turn_prompt`

Selector formatting affects routing quality:
- `summary` is compact and best for many coordination tasks
- `markdown` can help readability on long transcripts
- `json` can help when explicit role/source/content structure matters

## Pattern Metadata

After completion, `result.pattern_metadata` contains:
- `iterations_completed`: Total agent executions
- `selector_calls`: Number of LLM selection calls made
- `selector_model`: Model used for routing
- `selector_policy`: Serialized selector-context configuration

## Usage Tracking

The orchestrator tracks token usage from:
- All agent executions
- All selector LLM calls (agent selection)

Final `result.usage` includes comprehensive totals across all LLM interactions.

## Agent Description Best Practices

The LLM uses agent descriptions to make selection decisions. Write clear, specific descriptions:

✅ **Good:**
```python
description="Research specialist for factual and contextual information"
description="Content specialist for polished user-facing writing"
description="Code reviewer that checks for bugs and style issues"
```

❌ **Avoid:**
```python
description="A helpful agent"  # Too vague
description="Agent 1"  # No capability info
```

## Termination Conditions

| Termination Type | Purpose | AI-Driven relevance |
|-----------------|---------|---------------------|
| `ConsecutiveAgentTermination(n)` | Stop when same agent selected n times in a row | **Primary loop guard** — always include |
| `MaxMessageTermination(n)` | Stop after n messages | Hard safety cap |
| `TextMentionTermination("X")` | Stop when any agent says X | Semantic completion signal |
| `SourceTermination("name")` | Stop when a specific agent speaks | Phase-transition stop (e.g. reviewer responded) |
| `PredicateTermination(fn)` | Stop on any custom callable condition | Custom quality or content checks |
| `TimeoutTermination(sec)` | Stop after elapsed time | Latency-sensitive systems |
| `TokenUsageTermination(n)` | Stop after n tokens | Cost control |

### Recommended baseline for AI-driven orchestration

```python
termination = (
    ConsecutiveAgentTermination(max_consecutive=3)  # loop guard
    | TextMentionTermination("TERMINATE")            # semantic stop
    | MaxMessageTermination(12)                      # hard cap
)
```

### When the workflow ends when a specific agent has responded

```python
# Stop as soon as the reviewer speaks
termination = SourceTermination("reviewer") | MaxMessageTermination(10)

# Stop only when the reviewer approves in the same message
termination = (
    PredicateTermination(
        lambda msgs: any(
            getattr(m, "source", None) == "reviewer"
            and "APPROVED" in str(getattr(m, "content", ""))
            for m in msgs
        ),
        reason="Reviewer approved",
    )
    | ConsecutiveAgentTermination(max_consecutive=3)
    | MaxMessageTermination(12)
)
```

## Guardrails

- **Model Client Required:** `AIOrchestrator` needs a `model_client` parameter for agent selection (unlike `RoundRobinOrchestrator`).
- **Clear Descriptions:** Write specific agent descriptions—the LLM uses these to decide who should respond.
- **Two Context Surfaces:** Do not confuse `history_policy` with `selector_policy`; one shapes execution, the other shapes routing.
- **Termination Required:** Always provide termination conditions to prevent infinite loops. Include `ConsecutiveAgentTermination` — repeated same-agent selection is the most common AI-driven failure mode.
- **Safety Fallback:** If LLM selection fails, the orchestrator falls back to round-robin selection to preserve forward progress.
- **Usage Accounting:** Final usage includes both agent tokens AND selector tokens.
- **Case Insensitive:** Agent name matching is case-insensitive for robustness.
- **Event Observability:** Use `verbose=True` with `run_stream()` to see AI selection reasoning.

## When to Use AI-Driven vs Round-Robin

| Use AI-Driven When | Use Round-Robin When |
|--------------------|----------------------|
| Agent roles are specialized | Agents always alternate (poet/critic) |
| Order should adapt to context | Fixed workflow is desired |
| You have 3+ agents | You have exactly 2 agents |
| Complexity requires smart routing | Simple back-and-forth suffices |
| You want LLM to decide flow | You want deterministic order |

## Common Use Cases

1. **Research → Write → Review:** LLM decides when research is sufficient, when to write, when to review
2. **Multi-Specialist Teams:** Route to the right expert (data analyst, writer, coder, reviewer)
3. **Adaptive Workflows:** Let LLM determine optimal agent sequence based on task complexity
4. **Dynamic Collaboration:** Agents with overlapping capabilities—LLM picks best fit
5. **Question Routing:** Route user questions to the most appropriate agent based on content

## OpenTelemetry Integration

For observability with Jaeger:

```python
import os
# Set BEFORE importing agentbyte
os.environ["AGENTBYTE_ENABLE_OTEL"] = "true"
os.environ["OTEL_EXPORTER_OTLP_ENDPOINT"] = "http://localhost:4318"
os.environ["OTEL_SERVICE_NAME"] = "my-ai-orchestration"

from agentbyte import AIOrchestrator
# ... rest of code
```

Traces will show:
- Orchestration span (parent)
- Selector LLM calls (for agent selection)
- Individual agent execution spans
- Token usage aggregated at orchestration level
