Skip to content

Quick Start

The smallest possible end-to-end network scenario: one in-process hub, two agents, a consulting channel that auto-closes after a single Q-and-A.

import asyncio

from ag2 import Agent
from ag2.config import AnthropicConfig
from ag2.knowledge import MemoryKnowledgeStore
from ag2.network import (
    EV_CHANNEL_CLOSED,
    EV_TEXT,
    Hub,
)

async def main() -> None:
    config = AnthropicConfig(model="claude-sonnet-4-6")

    # Hub: registry + WAL + audit log + adapters live here.
    hub = await Hub.open(MemoryKnowledgeStore(), ttl_sweep_interval=0)

    # register() attaches each agent directly — the hub owns the connection.
    alice = await hub.register(
        Agent("alice", prompt="Ask one focused question and stop.", config=config),
    )
    bob = await hub.register(
        Agent("bob", prompt="Answer in one short sentence.", config=config),
    )

    # Strict 1Q1R; the adapter auto-closes on bob's reply.
    channel = await alice.open(type="consulting", target="bob")
    await channel.send(
        "What's the single most important property of a distributed system?",
        audience=[bob.agent_id],
    )

    # Bob's default handler runs Agent.ask on the inbound EV_TEXT, sends the
    # reply, and ConsultingAdapter posts EV_CHANNEL_CLOSED.
    close_env = await alice.wait_for_channel_event(
        channel_id=channel.channel_id,
        predicate=lambda e: e.event_type == EV_CHANNEL_CLOSED,
        timeout=60.0,
    )
    print(f"closed: {close_env.event_data.get('reason')!r}")

    # Replay the conversation from the hub's write-ahead log.
    wal = await hub.read_wal(channel.channel_id)
    for env in wal:
        if env.event_type == EV_TEXT:
            speaker = "alice" if env.sender_id == alice.agent_id else "bob"
            print(f"{speaker}: {env.event_data['text']}")

    await hub.close()

asyncio.run(main())

Expected output (Sonnet's exact words will differ on each run):

closed: 'consulting_complete'
alice: What's the single most important property of a distributed system?
bob: Fault tolerance — because a system that can't survive partial failures defeats its entire purpose.

What Just Happened#

In order:

  1. Hub.open(MemoryKnowledgeStore()) — boots an in-process hub. The KnowledgeStore is where the hub persists its audit log, registry, and write-ahead logs (here in memory).
  2. hub.register(agent, passport, resume) — attaches an Agent directly to the hub and returns an AgentClient whose agent_id is hub-stamped. The hub owns each agent's connection; agent_client.close() and hub.close() tear them down.
  3. alice.open(type="consulting", target="bob") — alice creates a consulting channel with bob as respondent. Internally: hub posts EV_CHANNEL_INVITE to bob → bob's default handler auto-acks → hub posts EV_CHANNEL_OPENED and alice.open(...) returns with channel.state == ACTIVE.
  4. channel.send(text, audience=...) — alice sends an EV_TEXT envelope.
  5. Bob's default handler — receives the EV_TEXT, probes whether the adapter would accept a reply right now (it would — bob hasn't replied yet), runs Agent.ask(text), and sends bob's reply back through bob's own channel handle.
  6. ConsultingAdapter — sees both initiator_sent and respondent_replied are true, returns AdapterResult(next_state=CLOSED, auto_close_reason="consulting_complete"). Hub posts EV_CHANNEL_CLOSED.
  7. alice.wait_for_channel_event(...) — alice's loop wakes when she receives the close envelope.
  8. hub.read_wal(channel_id) — replays every envelope the hub recorded for the channel. Each envelope is hub-stamped (id, timestamp, sender, audience, event_type, event_data).

Distributed deployments use an explicit transport

hub.register(agent) is the in-process convenience — it owns a HubClient over a LocalLink for you. To run agents in their own processes or hosts, construct a HubClient over a transport explicitly (HubClient(WsLink(url)).register(...)). See Distributed Deployment and Agent Clients.

Mental Hooks#

  • The Hub is the only authoritative state. Every send goes through it; every observer reads from it. Clients are thin.
  • A channel_id is the unit of conversation. The hub's WAL is keyed by channel id; expectation evaluators evaluate per channel; views project per channel.
  • Each AgentClient carries a default_handler that auto-acks invites and runs Agent.ask on inbound text. You can replace it with agent_client.on_envelope(callback) when you need custom logic.
  • The hub assigns the agent_id at registration. Use it (alice.agent_id) for routing rather than the human-readable name. The name may not be unique under a multi-tenant deployment.

Where to Next#

  • Hub & Identity — the registry side: Hub.open, Passport, Resume, Rule, auth.
  • Agent Clients — the agent side: HubClient.register, default handler, custom handlers.
  • Channel Adapters — pick the right one: free-form, 1Q1R, round-robin, or graph-driven.