> ## Documentation Index
> Fetch the complete documentation index at: https://praison.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Workflow Discussion (N-Agent Conversation Loop)

> N agents take turns on the same thread with round-robin, custom speaker selection, and optional early stop

Discussion runs N agents in turn on a shared thread until a criterion is met — round-robin by default, with an optional stop condition.

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents import Agent, AgentFlow, Discussion

critic = Agent(name="critic", instructions="Critique the draft rigorously")
author = Agent(name="author", instructions="Revise the draft")

flow = AgentFlow(
    agents=[critic, author],
    steps=[
        Discussion(
            [critic, author],
            rounds=3,
            until=lambda ctx: "AGREED" in (ctx.previous_result or ""),
        )
    ],
)
flow.start("Draft: Renewable energy is the only path forward.")
```

The user hands in a draft; the two agents take turns refining it until they agree.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph LR
    Request[📋 User Request] --> Discussion[💬 Discussion]

    subgraph Discussion["💬 Discussion"]
        A1[🤖 Agent 1] --> A2[🤖 Agent 2]
        A2 --> A3[🤖 Agent 3]
        A3 -.next round.-> A1
    end

    Discussion --> Output[✅ Final Output]

    classDef input fill:#6366F1,stroke:#7C90A0,color:#fff
    classDef agent fill:#8B0000,stroke:#7C90A0,color:#fff
    classDef output fill:#10B981,stroke:#7C90A0,color:#fff

    class Request input
    class A1,A2,A3 agent
    class Output output
```

## Quick Start

**1. Two agents, two rounds**

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents import Agent, AgentFlow, Discussion

critic = Agent(name="critic", instructions="Critique the draft rigorously")
author = Agent(name="author", instructions="Revise the draft")

flow = AgentFlow(
    agents=[critic, author],
    steps=[Discussion([critic, author], rounds=2)],
)
flow.start("Draft: Renewable energy is the only path forward.")
```

Turn order: `['critic', 'author', 'critic', 'author']` — two full passes.

**2. Add an early stop**

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents import Agent, AgentFlow, Discussion

critic = Agent(name="critic", instructions="Critique the draft rigorously")
author = Agent(name="author", instructions="Revise the draft; say AGREED when satisfied")

flow = AgentFlow(
    agents=[critic, author],
    steps=[
        Discussion(
            [critic, author],
            rounds=3,
            until=lambda ctx: "AGREED" in (ctx.previous_result or ""),
        )
    ],
)
flow.start("Draft: Renewable energy is the only path forward.")
```

The loop exits as soon as any turn returns text containing `AGREED`.

***

## How It Works

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
sequenceDiagram
    participant User
    participant AgentFlow
    participant Discussion
    participant Speaker1 as Speaker 1
    participant Speaker2 as Speaker 2

    User->>AgentFlow: start("draft")
    AgentFlow->>Discussion: Begin discussion
    loop Each round (up to rounds)
        Discussion->>Speaker1: Turn (previous_result = prior output)
        Speaker1-->>Discussion: Output → append to transcript
        Discussion->>Discussion: until()? stop if True
        Discussion->>Speaker2: Turn (previous_result = Speaker 1 output)
        Speaker2-->>Discussion: Output → append to transcript
        Discussion->>Discussion: until()? stop if True
    end
    Discussion-->>AgentFlow: Final output + discussion_transcript
    AgentFlow-->>User: Result
```

| Phase | What happens |
| - | - |
| 1. Compose | You list the speakers and set `rounds` |
| 2. Turn | The next speaker runs; its `previous_result` is the prior speaker's output |
| 3. Transcript | The turn is appended to `discussion_transcript` as `"{speaker}: {output}"` |
| 4. Evaluate | `until()` is checked after every turn; `True` stops immediately |
| 5. Return | Final output and the full transcript flow to the next step |

***

## API Reference

### Discussion

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
Discussion(
    agents=None,                                          # Speakers who take turns (required, non-empty)
    rounds: int = 3,                                       # Full passes over the speaker list (>= 1)
    until=None,                                            # Early-stop predicate, checked after every turn
    select=None,                                           # Custom speaker selector: (turn_index, ctx) -> agent
    name: str = "discussion",                              # Label used in step names and verbose output
)
```

### Parameters

| Parameter | Type | Default | Description |
| - | - | - | - |
| `agents` | `list` | required (non-empty) | Speakers who take turns. Each element can be an `Agent`, a `Task`, or a callable step handler. An empty list is refused at construction with `ValueError("Discussion needs at least one speaker...")`. |
| `rounds` | `int` | `3` | Number of full passes over the speaker list. Must be `>= 1` — an unbounded debate is explicitly refused as "a bill, not a feature". Zero or negative raises `ValueError("Discussion rounds must be >= 1.")`. |
| `until` | `Callable[[WorkflowContext], bool]` \| `None` | `None` | Early-stop predicate. Called **after every turn**. Returning `True` stops the discussion immediately. If `until()` raises, the exception is **not** swallowed — the discussion halts with `ValueError("Discussion until() raised ...")`. |
| `select` | `Callable[[int, WorkflowContext], Agent]` \| `None` | `None` | Custom speaker selector. Signature: `(turn_index, context) -> agent`. When set, consulted for **every turn** — a custom selector can repeat, skip, or reorder speakers. When `None`, round-robin is used (the default). |
| `name` | `str` | `"discussion"` | Label used in step names and verbose output. |

### Result Variables

| Variable | Type | Description |
| - | - | - |
| `discussion_transcript` | `str` | Full running transcript, `"{speaker}: {output}"` lines joined by `\n`. Access via `result.get("variables")["discussion_transcript"]` on the run result, or as `{{discussion_transcript}}` in a later step's prompt/action. |

> [!NOTE]
>
> The transcript lands on the **run result's** `variables` (i.e. `result.get("variables")` from `flow.run(...)`) — **not** on `flow.variables`, which stays the declared initial state a second run would start from.

***

## Common Patterns

### Round-robin debate

Default behavior — position `i` in round `r` runs `agents[i]`, no selector needed.

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents import Agent, AgentFlow, Discussion

optimist = Agent(name="optimist", instructions="Argue for the proposal")
skeptic = Agent(name="skeptic", instructions="Argue against the proposal")

flow = AgentFlow(
    agents=[optimist, skeptic],
    steps=[Discussion([optimist, skeptic], rounds=3)],
)
flow.start("Should we adopt a four-day work week?")
```

### Custom speaker selection

Set `select=` to choose the next speaker per turn. The signature is `(turn_index, context) -> agent`.

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents import Agent, AgentFlow, Discussion

expert = Agent(name="expert", instructions="Answer with authority")
scribe = Agent(name="scribe", instructions="Summarize the last answer")

# Alternate, but let the expert always open each pair of turns
flow = AgentFlow(
    agents=[expert, scribe],
    steps=[
        Discussion(
            [expert, scribe],
            rounds=2,
            select=lambda turn, ctx: expert if turn % 2 == 0 else scribe,
        )
    ],
)
flow.start("Explain vector databases")
```

### Early stop with `until=`

Stop as soon as a consensus keyword appears in the latest turn.

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
until=lambda ctx: "AGREED" in (ctx.previous_result or "")
```

### Using the transcript downstream

Follow the discussion with a summarizer whose prompt references `{{discussion_transcript}}`.

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents import Agent, AgentFlow, Discussion

critic = Agent(name="critic", instructions="Critique the draft")
author = Agent(name="author", instructions="Revise the draft")
summarizer = Agent(
    name="summarizer",
    instructions="Summarize this debate:\n{{discussion_transcript}}",
)

flow = AgentFlow(
    agents=[critic, author, summarizer],
    steps=[
        Discussion([critic, author], rounds=2),
        summarizer,
    ],
)
flow.start("Draft: Renewable energy is the only path forward.")
```

***

## Refusals / Guardrails

Discussion is opinionated — it fails loudly rather than quietly wasting spend.

| Refusal | Why |
| - | - |
| Empty speaker list → `ValueError` at construction | Zero speakers would run zero turns and report success — the "flattering kind of failure". |
| `rounds < 1` → `ValueError` | Unbounded debate is a bill, not a feature. |
| Broken `until()` → wrapped `ValueError`, not silently ignored | Swallowing it would turn a bounded discussion into rounds of spend, and the symptom would arrive as a bill instead of an error. |

***

## Stop Propagation

> [!NOTE]
>
> A speaker with `on_error="stop"` (the `Task` default) halts the **entire enclosing workflow**, not just the discussion — later steps do not run. This mirrors `route()`, `loop()`, `repeat()`, `parallel()`, and `if_()`. See [Workflow Error Handling](https://praison.ai/docs/features/workflow-error-handling.md).

***

## Nesting

`Discussion` is a first-class workflow step and runs correctly inside `loop`, `parallel`, `route`, `if_`, and `repeat`.

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents import Agent, AgentFlow, Discussion, loop

critic = Agent(name="critic", instructions="Critique this item")
author = Agent(name="author", instructions="Revise this item")

flow = AgentFlow(
    agents=[critic, author],
    steps=[loop(steps=[Discussion([critic, author], rounds=2)], over="items")],
    variables={"items": ["draft A", "draft B"]},
)
flow.start("Refine each draft")
```

See [Nested Workflows](https://praison.ai/docs/features/nested-workflows.md).

***

## Discussion vs. Repeat vs. AgentTeam

| Feature | `repeat()` | `Discussion` | `AgentTeam` (hierarchical) |
| - | - | - | - |
| Same agent per iteration | ✔ | ✘ (rotates by default) | manager-delegated |
| Fixed list per iteration | ✔ (`repeat([a, b])`) | ✔ | ✔ (manager decides) |
| N agents in turn | light (fixed list) | ✔ (round-robin/custom) | ✔ (manager decides) |
| Shared transcript between speakers | ✘ | ✔ | ✔ |
| Early stop condition | ✔ (`until=`) | ✔ (`until=`) | based on manager |
| Custom next-speaker selector | n/a | ✔ (`select=`) | manager-driven |

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph TB
    Q[How should agents take turns?] --> B{Same agent, iterate to improve?}
    B -->|Yes| R["repeat(step, until=...)"]
    B -->|No| L{Light back-and-forth of a small fixed list?}
    L -->|Yes| RL["repeat([a, b], max_iterations=N)"]
    L -->|No| C{First-class N-speaker conversation?}
    C -->|Yes| D["Discussion"]
    C -->|No| E["AgentTeam (manager decides)"]

    classDef decision fill:#6366F1,stroke:#7C90A0,color:#fff
    classDef option fill:#10B981,stroke:#7C90A0,color:#fff
    class Q,B,L,C decision
    class R,RL,D,E option
```

***

## Best Practices

> [accordion] Keep rounds small
>
> Start at 2–3; every additional round is a full pass over every speaker.

> [accordion] Give each speaker a distinct role
>
> A critic + an author debate is more useful than two agents with identical instructions.

> [accordion] Use until= for consensus signals
>
> A keyword like `AGREED` is more reliable than string-similarity heuristics.

> [accordion] Reference \{\{discussion\_transcript}} explicitly
>
> A summarizer that receives only `previous_result` sees just the final turn.

***

## Related

> [card] [Workflow Patterns](https://praison.ai/docs/features/workflow-patterns.md)
>
> Overview of routing, parallel, loop, and repeat

> [card] [Workflow Repeat](https://praison.ai/docs/features/workflow-repeat.md)
>
> Repeat a step or fixed list of steps until a condition is met

> [card] [Workflow Loop](https://praison.ai/docs/features/workflow-loop.md)
>
> Iterate over lists and files

> [card] [Workflow Error Handling](https://praison.ai/docs/features/workflow-error-handling.md)
>
> How stop propagates through nested patterns

> [card] [Nested Workflows](https://praison.ai/docs/features/nested-workflows.md)
>
> Compose Discussion inside loop, parallel, route, and repeat


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.