Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
ag2ai avatar

Ag2 Network Discussion

  • 27 installs
  • 8 repo stars
  • Updated July 27, 2026
  • ag2ai/ag2-skills

ag2-network-discussion is a Claude Code skill that opens an AG2 network discussion channel for fixed-order N-party round-robin turn-taking among agents.

About

This skill opens an AG2 network discussion channel, an N-party round-robin where each participant speaks in fixed order, cycling until an explicit close or TTL. A developer uses it for a brainstorm with a fixed cast, a panel discussion, or round-robin reviewers. It documents the ordering knob, the expected_next_speaker rotation, and the can_send probe pattern that lets agents skip wasted LLM calls when it is not their turn.

  • Opens an N-party round-robin discussion channel on the AG2 network
  • Participants speak in fixed order, cycling until explicit close or TTL
  • Covers the can_send probe so agents skip LLM calls out of turn

Ag2 Network Discussion by the numbers

  • 27 all-time installs (skills.sh)
  • Ranked #9,601 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
  • Data as of Aug 1, 2026 (Skillselion catalog sync)
At a glance

ag2-network-discussion capabilities & compatibility

Free skill; requires an LLM provider API key to run the agents.

Capabilities
multi agent network · round robin turntaking · agent orchestration
Use cases
orchestration
Pricing
Bring your own API key
From the docs

What ag2-network-discussion says it does

`discussion` is an N-party round-robin channel. Participants speak in a fixed order, cycling indefinitely until you close it.
SKILL.md
Any fixed-order N-party turn-taking where each participant gets a slot per cycle
SKILL.md
npx skills add https://github.com/ag2ai/ag2-skills --skill ag2-network-discussion

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs27
repo stars8
Last updatedJuly 27, 2026
Repositoryag2ai/ag2-skills

What it does

Run a fixed-order N-party round-robin among AG2 agents, such as a panel discussion or round-robin reviewers.

Who is it for?

Developers who want a brainstorm, panel, or round-robin reviewers with a fixed cast of AG2 agents.

Skip if: Conditional handoffs or one-shot pipelines, which use ag2-network-workflow instead.

When should I use this skill?

The user wants a brainstorm with a fixed cast, a panel discussion, or round-robin reviewers.

What you get

Agents speak in a fixed round-robin order and skip LLM calls when it is not their turn.

By the numbers

  • Participants 2+
  • default view NamedWindowedSummary(recent_n=N*2)

Files

SKILL.mdMarkdownGitHub ↗

AG2 Network — Discussion Adapter

discussion is an N-party round-robin channel. Participants speak in a fixed order, cycling indefinitely until you close it. The adapter enforces "wait your turn" via validate_send; the hub's can_send probe lets each agent's default handler skip wasted LLM calls when it isn't that agent's turn.

Prerequisite: read ag2-network-quickstart first. This skill assumes you already know Hub.open, HubClient.register, the channel lifecycle, and Envelope basics.

When to use

  • "Three agents debating in turn"
  • "A panel discussion / brainstorm with a fixed cast"
  • "Round-robin reviewers commenting on a draft"
  • Any fixed-order N-party turn-taking where each participant gets a slot per cycle

Don't use `discussion` for:

  • Conditional handoffs ("if alice mentions security, hand to the security expert") → use ag2-network-workflow.
  • Pipelines where each step happens once → use ag2-network-workflow with TransitionGraph.sequence([...]).
  • Two participants with no fixed order → use conversation (covered in ag2-network-quickstart).
  • Strict 1Q1R → use consulting (covered in ag2-network-quickstart).

Shape

Participants2+
Turn orderRound-robin (creator first, then participants in registration order)
Auto-closeNo
TerminationExplicit channel.close() or TTL (see "Closing" below)
Default viewNamedWindowedSummary(recent_n=N*2) where N is the participant count
Default expectationsturn_within(120s, warn), turn_within(600s, hide)
Knob{"ordering": ORDERING_ROUND_ROBIN} (only ordering shipped today)

The view recent-window is sized to N*2 so each agent's projection covers roughly the last two full cycles — enough context for a coherent reply without ballooning the prompt as the discussion grows.

Discussion agents, the NetworkPlugin, and turn-taking

HubClient.register(...) attaches NetworkPlugin by default — that adds peers / channels / tasks / context / delegate, the identity-level verbs. DiscussionAdapter.tools_for(...) returns []: the discussion adapter offers no channel-specific tools (no say). A participant contributes its turn purely by returning a reply — the default handler posts the round-end EV_TEXT(reply.body) as that turn's contribution.

Because there's no say, there's no "double-send" hazard specific to discussion. Turn-taking is enforced entirely by validate_send: a send from anyone other than state.expected_next_speaker is rejected with ProtocolError (expects <other> to speak). The can_send probe (below) lets each handler skip its LLM call when it isn't its turn, so out-of-turn sends shouldn't happen in normal operation.

attach_plugin=False only removes the five NetworkPlugin verbs (delegate / peers / channels / tasks / context); it has no effect on adapter tools (discussion has none). Use it for a bare participant.

# Bare discussion participant (no plugin verbs):
alice = await alice_hc.register(alice_agent, Passport(name="alice"), Resume(), attach_plugin=False)

The examples below pass attach_plugin=False for compactness; drop it if your participants need delegate / peers / etc. Full detail: ag2-network-tools-and-views → "Adapter-owned tools (tools_for)".

Smallest example

import asyncio

from autogen.beta import Agent
from autogen.beta.config import AnthropicConfig
from autogen.beta.knowledge import MemoryKnowledgeStore
from autogen.beta.network import (
    EV_CHANNEL_CLOSED,
    EV_TEXT,
    ORDERING_ROUND_ROBIN,
    Hub,
    HubClient,
    LocalLink,
    Passport,
    Resume,
)


async def main() -> None:
    config = AnthropicConfig(model="claude-sonnet-4-6")
    hub = await Hub.open(MemoryKnowledgeStore(), ttl_sweep_interval=0)
    link = LocalLink(hub)

    alice_hc, bob_hc, carol_hc = (HubClient(link, hub=hub) for _ in range(3))

    alice = await alice_hc.register(
        Agent("alice", prompt="The optimist. One short sentence.", config=config),
        Passport(name="alice"), Resume(), attach_plugin=False,
    )
    bob = await bob_hc.register(
        Agent("bob", prompt="The realist. One short sentence.", config=config),
        Passport(name="bob"), Resume(), attach_plugin=False,
    )
    carol = await carol_hc.register(
        Agent("carol", prompt="The skeptic. One short sentence.", config=config),
        Passport(name="carol"), Resume(), attach_plugin=False,
    )

    channel = await alice.open(
        type="discussion",
        target=[bob.agent_id, carol.agent_id],
        knobs={"ordering": ORDERING_ROUND_ROBIN},
    )
    await channel.send("Topic: should every developer learn Rust?")

    # Halt after 6 EV_TEXT envelopes (= 2 full alice→bob→carol cycles).
    await wait_for_text_count(hub, channel.channel_id, expected=6)
    await channel.close(reason="cap_reached")

    for hc in (alice_hc, bob_hc, carol_hc):
        await hc.close()
    await hub.close()


async def wait_for_text_count(hub, channel_id, expected, *, timeout=180.0):
    deadline = asyncio.get_event_loop().time() + timeout
    while asyncio.get_event_loop().time() < deadline:
        wal = await hub.read_wal(channel_id)
        if sum(1 for e in wal if e.event_type == EV_TEXT) >= expected:
            return
        await asyncio.sleep(0.05)
    raise asyncio.TimeoutError("did not reach expected count")


asyncio.run(main())

The order in target=[bob.agent_id, carol.agent_id] is significant — it becomes the rotation order after the creator. The cycle is therefore alice → bob → carol → alice → bob → carol → … until the cap fires.

How turn skipping works (the can_send probe)

When alice sends her first envelope, the hub fans EV_TEXT out to bob and carol. Both default handlers fire in parallel:

  • bob's handlerawait hc.can_send(channel_id, bob.agent_id). The adapter says "yes, bob is expected_next_speaker." Handler runs Agent.ask, sends bob's reply.
  • carol's handlerawait hc.can_send(channel_id, carol.agent_id). The adapter says "no, expected_next_speaker is bob." Handler returns without engaging the LLM.

When bob's reply lands, the same fan-out repeats. Now expected_next_speaker = carol, so carol's handler engages and bob's skips. No wasted LLM calls anywhere.

If you write a custom handler for a discussion channel, mirror this pattern — and delegate non-text envelopes to `default_handler` so you keep the auto-ack of EV_CHANNEL_INVITE (otherwise the channel sits in PENDING until invite_ack_timeout and the hub closes it on you):

from autogen.beta.network import Envelope, EV_TEXT, default_handler


async def my_handler(envelope: Envelope) -> None:
    if envelope.event_type != EV_TEXT:
        await default_handler(envelope, client)   # invite-ack + lifecycle bookkeeping
        return
    if not await hc.can_send(envelope.channel_id, my_agent_id):    # async — must await
        return  # not our turn — skip the LLM call
    # ... read WAL, project view, run Agent.ask, send reply ...

A HumanClient in the round-robin

A discussion participant doesn't have to be an Agent. Register a HumanClient (hc.register_human(Passport(name="user", kind="human")) — see ag2-network-quickstart → "HumanClient") and add it to the participant order like any other. It has no notify handler that auto-responds, so the application drives its turn: subscribe with on_envelope(...) (or pull with next_envelope(...)) and, when an envelope lands, check await hc.can_send(channel_id, user.agent_id) (async — must await) — if it's the human's turn, prompt the person and await user.send(channel_id, text); otherwise wait. (The framework's own discussion tests do exactly this — a human gating on adapter state for its slot in the rotation.)

State object

@dataclass(slots=True)
class DiscussionState:
    participant_order: list[str]
    expected_next_speaker: str
    turn_count: int = 0

participant_order is fixed at create time by sorting participants on Participant.order. Round-robin advances by (current_index + 1) % len(participant_order) after every accepted EV_TEXT. Read state for inspection or testing via hub._adapter_states[channel_id] (the underscore signals "operator API" — not for production agent logic).

Validation rules

DiscussionAdapter.validate_send rejects:

  • EV_TEXT from anyone other than state.expected_next_speaker — raises ProtocolError.
  • Sends from non-participants.
  • Sends to a closed channel.

Protocol envelopes (EV_CHANNEL_*, ag2.task.*) bypass the turn check — they're bookkeeping, not turn-taking.

Customising the ordering

Today only ORDERING_ROUND_ROBIN ships:

from autogen.beta.network import ORDERING_ROUND_ROBIN

channel = await alice.open(
    type="discussion",
    target=[bob.agent_id, carol.agent_id],
    knobs={"ordering": ORDERING_ROUND_ROBIN},  # equivalent to "round_robin"
)

Passing any other ordering raises at create time. Future orderings (dynamic, weighted, priority) plug in here without breaking the round-robin contract.

If you need conditional turn-taking now, switch adapters to workflowTransitionGraph.round_robin(participants, max_turns=N) gives you the same shape with the option to add conditions later. See ag2-network-workflow.

Closing

discussion never auto-closes. Four patterns work; pick by who decides:

PatternWho decidesWhen
App-side cap → channel.close()Your orchestration codeTurn count, time, or any external predicate
Agent-side tool (via ChannelInject)The LLMA participant decides "we're done"
Custom adapter subclassThe frameworkContent-based stop (rare for discussions)
TTL / turn_within expectationsThe hub's sweepersSafety net only

App-side cap (canonical)

await wait_for_text_count(hub, channel.channel_id, expected=6)
await channel.close(reason="cap_reached")

The reason flows on EV_CHANNEL_CLOSED.event_data["reason"] — pick something descriptive so observers can distinguish a clean cap from a TTL or expectation violation.

Agent-side tool

Same pattern as the other adapters — inject the active Channel, call close():

from autogen.beta.network.client.inject import ChannelInject


async def end_discussion(reason: str, channel: ChannelInject) -> str:
    if channel is None:
        return "no active channel"
    await channel.close(reason=f"agent_close:{reason}")
    return f"closed: {reason}"


for agent in (alice_agent, bob_agent, carol_agent):
    agent.tool(end_discussion)

Any participant can wrap up. See ag2-network-quickstart's "Five close routes" for the full picture.

When discussion isn't enough

The moment you need conditional turn-taking ("if bob mentions deadlines, jump to deadlines_expert"), discussion is the wrong adapter. Migrate to workflow — same N-party shape, but turn-taking lives in a TransitionGraph with FromSpeaker, ToolCalled, and ContextEquals conditions. See ag2-network-workflow for the migration recipe.

Default expectations

ExpectationThresholdHandlerWhat it does
turn_within120swarnLogs AUDIT_KIND_EXPECTATION_VIOLATED. Channel stays open.
turn_within600shideMarks the slow speaker as "skip" in the view projection for one cycle. Channel stays open.

Discussion deliberately never auto-closes from expectations — a slow speaker shouldn't kill the whole panel. If you want the channel to terminate on a stalled cycle, override the expectations to auto_close via ag2-network-governance.

Quick reference — imports

from autogen.beta.network import (
    DISCUSSION_TYPE,           # = "discussion"
    ORDERING_ROUND_ROBIN,      # = "round_robin"
    DiscussionAdapter,         # the adapter class itself (rarely instantiated directly)
    DiscussionState,           # state dataclass
    HumanClient,               # a non-LLM participant in the rotation — HubClient.register_human(...)
)

The string forms type="discussion" and ordering="round_robin" work too — the constants are equivalent and exist for type-safety in larger codebases.

Related skills

FAQ

How many participants does a discussion channel support?

Two or more, speaking round-robin in registration order.

When should I not use discussion?

For conditional handoffs or one-shot pipelines, use ag2-network-workflow instead.

AI & Agent Buildingagentsautomation

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.