ag2-network-quickstart
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseAG2 Network — Quickstart
AG2 Network — 快速入门
The network turns a collection of instances into a coordinated multi-agent system. A central holds authoritative state (registry, audit log, write-ahead logs per channel); each agent lives behind a thin that connects through a . Every send goes through the hub, so every interaction is replayable and observable.
AgentHubAgentClientHubClientThe network is opt-in. Bare continues to work standalone — the network only activates when you import from .
Agentag2.network该网络可将多个实例转化为协同工作的多Agent系统。中央存储权威状态(注册表、审计日志、每个通道的预写日志);每个Agent都由一个轻量的封装,通过连接到Hub。所有消息发送都经过Hub,因此每一次交互都可重放和监控。
AgentHubAgentClientHubClient该网络为可选启用。独立的可继续单独运行——只有当你从导入相关模块时,网络功能才会激活。
Agentag2.networkWhen to use
使用场景
Load this skill whenever the user wants two or more agents to interact. Concrete trigger phrases:
- "Have two agents talk to each other"
- "Set up a multi-agent system / multi-agent chat / agent network"
- "Agents that can call each other"
- "Replace the classic /
GroupChat"ConversableAgent.handoffs - "Add a registry / audit trail / shared inbox for my agents"
Not a network task: if the user is asking one agent to spawn its own sub-tasks (recursive task fan-out, parallel sub-task execution), use instead. The network is for distinct, registered agents collaborating.
ag2-subagent-delegation当用户需要让两个或更多Agent进行交互时,加载此技能。具体触发场景:
- "让两个Agent互相对话"
- "搭建多Agent系统/多Agent聊天/Agent网络"
- "可互相调用的Agent"
- "替换传统的/
GroupChat"ConversableAgent.handoffs - "为我的Agent添加注册表/审计追踪/共享收件箱"
**不属于网络任务的场景:**如果用户要求单个Agent生成自己的子任务(递归任务分发、并行子任务执行),请使用。本网络适用于独立注册的Agent之间的协作。
ag2-subagent-delegationMental model
心智模型
┌────────────────┐
│ Hub │ ←── audit log, registry, channels,
│ ── adapters ── │ WAL, sweepers, expectation
│ ── channels ── │ evaluators
│ ── audit log ──│
└────────┬───────┘
│ in-process duplex (LocalLink)
┌──────────────────┼──────────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│AgentClient│ │AgentClient│ │AgentClient│
│ alice │ │ bob │ │ carol │
│ Agent │ │ Agent │ │ Agent │
└──────────┘ └──────────┘ └──────────┘| Concept | Lives in | Purpose |
|---|---|---|
| One per network | Authoritative state — registry, audit log, channel table, WAL, sweepers |
| Hub registry | Stable identity ( |
| Hub registry | Capability claims plus hub-mutated |
| One per process | Per-process broker; manages registration |
| One per registered Agent | Wraps the Agent; sends envelopes, runs the notify handler |
| The wire format | Hub-stamped record of one event in one channel |
| Created by | A bounded multi-party exchange governed by an adapter |
The hub assigns the at registration. Use it () for routing rather than the human-readable name.
agent_idalice.agent_id ┌────────────────┐
│ Hub │ ←── 审计日志、注册表、通道、
│ ── adapters ── │ WAL、清理器、预期
│ ── channels ── │ 评估器
│ ── audit log ──│
└────────┬───────┘
│ 进程内双向通信(LocalLink)
┌──────────────────┼──────────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│AgentClient│ │AgentClient│ │AgentClient│
│ alice │ │ bob │ │ carol │
│ Agent │ │ Agent │ │ Agent │
└──────────┘ └──────────┘ └──────────┘| 概念 | 所属位置 | 用途 |
|---|---|---|
| 每个网络一个 | 存储权威状态——注册表、审计日志、通道表、WAL、清理器 |
| Hub注册表 | 稳定身份标识( |
| Hub注册表 | 能力声明加上Hub更新的 |
| 每个进程一个 | 进程级代理;管理注册流程 |
| 每个已注册Agent一个 | 封装Agent;发送消息包,运行通知处理器 |
| 有线格式 | Hub标记的单个通道内的事件记录 |
| 通过 | 由适配器管控的多方交互边界 |
Hub会在注册时分配。请使用该ID(如)进行路由,而非人类可读的名称。
agent_idalice.agent_idThe smallest possible network
最简网络示例
A single-process hub, two agents, a channel (strict 1Q1R, auto-closes after the reply):
consultingpython
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,
HubClient,
LocalLink,
Passport,
Resume,
)
async def main() -> None:
config = AnthropicConfig(model="claude-sonnet-4-6")
# 1. Boot the hub. MemoryKnowledgeStore for in-process; DiskKnowledgeStore(path)
# for durability across restarts.
hub = await Hub.open(MemoryKnowledgeStore(), ttl_sweep_interval=0)
link = LocalLink(hub) # in-process duplex transport
# 2. One HubClient per process boundary; here we use two for clarity.
alice_hc = HubClient(link, hub=hub)
bob_hc = HubClient(link, hub=hub)
# 3. Register each Agent. The hub stamps the agent_id and returns an AgentClient.
# attach_plugin=False gives a bare agent (no peers/channels/tasks/context/
# delegate tools) — fine here since alice & bob just send/reply. The `say`
# tool is offered per-turn by the adapter, not the plugin (see below).
alice = await alice_hc.register(
Agent("alice", prompt="Ask one focused question and stop.", config=config),
Passport(name="alice"),
Resume(),
attach_plugin=False,
)
bob = await bob_hc.register(
Agent("bob", prompt="Answer in one short sentence.", config=config),
Passport(name="bob"),
Resume(),
attach_plugin=False,
)
# 4. Open a consulting channel. The adapter posts EV_CHANNEL_INVITE to bob,
# bob's default handler auto-acks, channel goes ACTIVE.
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],
)
# 5. Bob's default handler probes can_send, runs Agent.ask on the inbound
# text, sends the reply. ConsultingAdapter sees both flags set and
# auto-closes with reason "consulting_complete".
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}")
# 6. Replay the conversation from the hub's WAL.
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 alice_hc.close()
await bob_hc.close()
await hub.close()
asyncio.run(main())That's the entire mandatory surface. Everything else below is variation.
单进程Hub、两个Agent、一个通道(严格一问一答,回复后自动关闭):
consultingpython
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,
HubClient,
LocalLink,
Passport,
Resume,
)
async def main() -> None:
config = AnthropicConfig(model="claude-sonnet-4-6")
# 1. 启动Hub。使用MemoryKnowledgeStore实现进程内存储;如需跨重启持久化,使用DiskKnowledgeStore(path)
hub = await Hub.open(MemoryKnowledgeStore(), ttl_sweep_interval=0)
link = LocalLink(hub) # 进程内双向传输
# 2. 每个进程边界对应一个HubClient;此处为清晰起见使用两个
alice_hc = HubClient(link, hub=hub)
bob_hc = HubClient(link, hub=hub)
# 3. 注册每个Agent。Hub会标记agent_id并返回AgentClient。
# attach_plugin=False表示使用裸Agent(无节点/通道/任务/上下文/
# 委托工具)——此处适用,因为alice和bob仅需发送/回复消息。`say`
# 工具由适配器按轮次提供,而非插件(见下文)。
alice = await alice_hc.register(
Agent("alice", prompt="提出一个明确的问题后停止。", config=config),
Passport(name="alice"),
Resume(),
attach_plugin=False,
)
bob = await bob_hc.register(
Agent("bob", prompt="用一句话简短回答。", config=config),
Passport(name="bob"),
Resume(),
attach_plugin=False,
)
# 4. 打开一个consulting通道。适配器会向bob发送EV_CHANNEL_INVITE事件,
# bob的默认处理器会自动确认,通道进入ACTIVE状态。
channel = await alice.open(type="consulting", target="bob")
await channel.send(
"分布式系统最重要的单一特性是什么?",
audience=[bob.agent_id],
)
# 5. Bob的默认处理器会检查can_send,对入站文本运行Agent.ask,发送回复。
# ConsultingAdapter检测到两个标记都已设置,会自动关闭通道,原因是"consulting_complete"。
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}")
# 6. 从Hub的WAL中重放对话内容。
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 alice_hc.close()
await bob_hc.close()
await hub.close()
asyncio.run(main())以上是所有必备内容。以下所有内容均为扩展变体。
Picking a channel adapter
选择通道适配器
Every channel is governed by an adapter that defines participants, turn order, and termination. Four adapters ship; pick one when you call .
agent_client.open(type=..., target=...)| Adapter | Participants | Turn order | Termination | Default view |
|---|---|---|---|---|
| Exactly 2 | Strict 1Q1R (initiator → respondent) | Auto-closes after respondent's reply | |
| Exactly 2 | Free-form (either side, any time) | Explicit | |
| 2+ | Round-robin via | Explicit close or TTL | |
| 2+ | Declarative | Graph terminates ( | |
This skill covers and (the 2-party adapters). For the others:
consultingconversation- N-party round-robin → load .
ag2-network-discussion - Declarative orchestration with , conditional handoffs, or migrating from classic
TransitionGraph→ loadGroupChat.ag2-network-workflow
每个通道都由一个适配器管控,适配器定义了参与者、轮次顺序和终止规则。内置四种适配器;调用时选择其一。
agent_client.open(type=..., target=...)| 适配器 | 参与者数量 | 轮次顺序 | 终止方式 | 默认视图 |
|---|---|---|---|---|
| 恰好2个 | 严格一问一答(发起方 → 回应方) | 回应方回复后自动关闭 | |
| 恰好2个 | 自由形式(任意一方可随时发言) | 显式调用 | |
| 2个及以上 | 通过 | 显式关闭或超时 | |
| 2个及以上 | 声明式 | 图终止( | |
**本技能涵盖和(双方适配器)。**其他适配器请参考:
consultingconversation- 多方轮询 → 加载****。
ag2-network-discussion - 使用进行声明式编排、条件切换,或从传统
TransitionGraph迁移 → 加载**GroupChat**。ag2-network-workflow
Plugin tools vs. adapter tools — attach_plugin
and say
attach_pluginsay插件工具 vs 适配器工具 — attach_plugin
和say
attach_pluginsayHubClient.register(...)NetworkPluginpeerschannelstaskscontextdelegatesayadapter.tools_for(...)attach_plugin| Adapter | Offers |
|---|---|
| only to the participant whose turn it is in the 1Q1R |
| always (no turn order) |
| only to |
| never — routing is your |
So no longer means "no " — it means "no / / / / ". Pass it for a bare agent (tests, pure pipeline workers); keep the default if the agent needs those cross-cutting verbs.
attach_plugin=FalsesaydelegatepeerschannelstaskscontextYou normally don't need to think about at all: the default handler always posts the round-end envelope from the agent's plain reply, so an agent that just answers is fine. is for the rare cases of posting an extra message in a turn, or posting into a different channel the agent is also in. (Edge case to avoid: an agent on a channel that calls and also returns a non-empty reply double-sends — the second envelope trips on the strict 1Q1R adapter. If you don't want the agent calling , just don't prompt it to.) Full detail: → "Adapter-owned tools ()".
saysayconsultingsay(...)channel is closedsayag2-network-tools-and-viewstools_forHubClient.register(...)NetworkPluginpeerschannelstaskscontextdelegatesayadapter.tools_for(...)attach_plugin| 适配器 | 是否提供 |
|---|---|
| 仅轮到发言的参与者可用 |
| 始终可用(无轮次限制) |
| 仅 |
| 不可用——路由由你的 |
因此不再意味着"无工具"——而是"无////工具"。对于裸Agent(测试、纯流水线工作者)可设置该参数;如果Agent需要这些跨域工具,请保持默认值。
attach_plugin=Falsesaydelegatepeerschannelstaskscontext通常你无需关注工具:默认处理器总会在轮次结束时发送Agent纯回复对应的消息包,因此仅需回复的Agent可正常工作。工具适用于少数场景:在一轮中发送额外消息,或向Agent所在的其他通道发送消息。(需避免的边缘情况:通道中的Agent同时调用并返回非空回复会导致重复发送——第二个消息包会触发严格一问一答适配器的"channel is closed"错误。如果不希望Agent调用,只需在提示词中明确说明即可。)详细内容: → "适配器专属工具()"。
saysayconsultingsay(...)sayag2-network-tools-and-viewstools_forconsulting
— strict 1Q1R
consultingconsulting
— 严格一问一答
consultingThe example above. Initiator sends exactly one substantive envelope; respondent sends exactly one reply; the adapter auto-closes with .
event_data["reason"] == "consulting_complete"State on the hub side:
python
@dataclass(slots=True)
class ConsultingState:
initiator_sent: bool = False
respondent_replied: bool = FalseConsultingAdapter.validate_send- Out-of-order sends (respondent speaking before initiator's first envelope).
- Any send after both flags are set — raises , propagated back to the caller's
ProtocolError.channel.send(...)
Default expectations are strict: and . If the respondent doesn't ack the invite within 30s, or doesn't reply within 10 minutes, the hub auto-closes with or . Tune these via .
acks_within(30s, auto_close)reply_within(600s, auto_close)"expectation_violated:acks_within""expectation_violated:reply_within"ag2-network-governance如上述示例。发起方发送恰好一条实质性消息包;回应方发送恰好一条回复;适配器自动关闭通道,原因是。
event_data["reason"] == "consulting_complete"Hub端的状态:
python
@dataclass(slots=True)
class ConsultingState:
initiator_sent: bool = False
respondent_replied: bool = FalseConsultingAdapter.validate_send- 顺序错误的发送(发起方发送第一条消息包前,回应方发言)。
- 两个标记都已设置后的任何发送——会抛出,并传播回调用者的
ProtocolError。channel.send(...)
默认预期规则严格:和。如果回应方未在30秒内确认邀请,或未在10分钟内回复,Hub会自动关闭通道,原因是或。可通过调整这些规则。
acks_within(30s, auto_close)reply_within(600s, auto_close)"expectation_violated:acks_within""expectation_violated:reply_within"ag2-network-governanceconversation
— free-form 2-party
conversationconversation
— 自由双方对话
conversationEither side sends at any time, any order. The adapter never auto-closes; you halt via or rely on TTL.
channel.close()python
channel = await alice.open(type="conversation", target="bob")
await channel.send("Hi bob, what's a good first ML concept to learn?")Both default handlers run on every inbound , so once started the conversation auto-drives. Two halt patterns:
Agent.askEV_TEXT- Application-side cap — poll the WAL until you've seen N text envelopes, then .
await channel.close() - Empty reply as halt signal — the default handler treats an empty body as "don't send", so an LLM that replies with ends the chain. Pair with a prompt like
""."reply with empty string when you have nothing useful to add"
ConversationAdapter.validate_sendDefault expectation is lenient: (logs to the audit kind but does not close).
max_silence(3600s, audit)AUDIT_KIND_EXPECTATION_VIOLATEDUse when: two specialists genuinely converse without a fixed order (analyst ↔ critic); chat UIs where your application controls the stop signal.
conversationDon't use for: strict 1Q1R (use ); multi-participant chats (use or ).
conversationconsultingdiscussionworkflow任意一方可随时发送消息,无顺序限制。适配器从不自动关闭;你需通过或依赖超时来终止对话。
channel.close()python
channel = await alice.open(type="conversation", target="bob")
await channel.send("嗨Bob,机器学习的第一个核心概念应该学什么?")两个默认处理器都会对每个入站事件运行,因此对话启动后会自动进行。两种终止模式:
EV_TEXTAgent.ask- 应用端限制——轮询WAL直到看到N条文本消息包,然后调用。
await channel.close() - 空回复作为终止信号——默认处理器会将空内容视为"不发送",因此返回的LLM会终止对话链。可搭配提示词如
""。"当你没有有用内容可补充时,回复空字符串"
ConversationAdapter.validate_send默认预期规则宽松:(记录到审计类型但不关闭通道)。
max_silence(3600s, audit)AUDIT_KIND_EXPECTATION_VIOLATED**适用场景:**两个专家无需固定顺序的真实对话(分析师 ↔ 评论员);由应用控制停止信号的聊天UI。
**不适用场景:**严格一问一答(使用);多参与者聊天(使用或)。
consultingdiscussionworkflowA non-LLM participant — HumanClient
HumanClient非LLM参与者 — HumanClient
HumanClientNot every participant is an . is a network member with no LLM, no , no assembly policies — a person at a UI, a bridge to another system, or a scripted "user" that seeds a channel. Register it with (not — that path now rejects and points you here):
AgentHumanClientNetworkPluginregister_humanregisterkind="human"python
from ag2.network import HumanClient, Passport
user_hc = HubClient(link, hub=hub)
user = await user_hc.register_human(Passport(name="user", kind="human")) # resume=, rule=, auto_ack_invites= optionalIt implements the same surface as for outbound — , , (the escape hatch for adapter-shaped envelopes like a workflow ) — and gives you two ways to consume inbound envelopes:
NetworkClientAgentClientuser.open(type=..., target=...)user.send(channel_id, text, audience=...)user.post_envelope(env)EV_PACKET- Push — ; the coroutine fires once per inbound envelope. Multiple callbacks compose; a raising callback is logged, never propagated.
user.on_envelope(callback) - Pull — blocks for the next match;
await user.next_envelope(predicate=..., timeout=...)streams everything untilasync for env in user.envelopes(): ....user.disconnect()
It auto-acks channel invites ( default) so the hub's join handshake completes without UI round-trips; pass to gate joins yourself. Discover humans via .
auto_ack_invites=Trueauto_ack_invites=Falsehub.list_agents(kind="human")Common uses: the kickoff seeder for a channel ( — see ), the human leg of a Q&A, or a participant in a round-robin. The agent-side surface (custom handlers, headless workers, gateways) is in .
workflowFromSpeaker(user) → AgentTarget(first_agent)ag2-network-workflowconsultingdiscussionag2-network-tools-and-views并非所有参与者都是。是网络成员,无LLM、无、无组装策略——可代表UI端的用户、与其他系统的桥接、或为通道提供初始内容的脚本化"用户"。使用注册(而非——该路径现在会拒绝并引导你使用此方法):
AgentHumanClientNetworkPluginregister_humanregisterkind="human"python
from ag2.network import HumanClient, Passport
user_hc = HubClient(link, hub=hub)
user = await user_hc.register_human(Passport(name="user", kind="human")) # resume=, rule=, auto_ack_invites= 为可选参数它实现了与相同的出站接口——、、(用于发送适配器格式的消息包,如工作流)——并提供两种方式处理入站消息包:
AgentClientNetworkClientuser.open(type=..., target=...)user.send(channel_id, text, audience=...)user.post_envelope(env)EV_PACKET- 推送——;每个入站消息包触发一次协程。多个回调可组合;抛出异常的回调会被记录,但不会传播。
user.on_envelope(callback) - 拉取——会阻塞直到匹配到下一个消息包;
await user.next_envelope(predicate=..., timeout=...)会流式传输所有消息包直到async for env in user.envelopes(): ...。user.disconnect()
它会自动确认通道邀请(默认),因此Hub的加入握手无需UI往返;可传入来自行管控加入流程。通过发现人类参与者。
auto_ack_invites=Trueauto_ack_invites=Falsehub.list_agents(kind="human")常见用途:通道的启动种子(——见)、问答中的人类端、或轮询中的参与者。Agent端相关内容(自定义处理器、无头工作者、网关)请参考。
workflowFromSpeaker(user) → AgentTarget(first_agent)ag2-network-workflowconsultingdiscussionag2-network-tools-and-viewsIdentity — Passport
and Resume
PassportResume身份标识 — Passport
和Resume
PassportResumeThree dataclasses describe an agent on the network. Tenant supplies most fields; the hub stamps the rest.
python
from ag2.network import Passport, Resume, ResumeExample
passport = Passport(
name="alice", # required, unique within the hub
owner="acme", # optional, tenant id for multi-tenant deployments
model="claude-sonnet-4-6", # optional, surfaces on peer-lookup results
kind="agent", # optional: "agent" (default / None) | "human" | "remote_agent"
)
resume = Resume(
claimed_capabilities=["analysis", "policy"],
domains=["finance"],
summary="Senior policy analyst — scenario synthesis and rebuttal review.",
examples=[ResumeExample(title="Q3 risk brief", note="…")],
)The hub stamps and at registration. (type alias ) discriminates participant types — / for the usual LLM-backed , for a (use , not ), reserved for A2A/federation. filters by it. The field (per-capability counts) is hub-mutated as the agent runs capability-tagged tasks — see for how to wire that up.
Passport.agent_idPassport.created_atPassport.kindPassportKindNone"agent"AgentClient"human"HumanClientregister_humanregister"remote_agent"hub.list_agents(kind=...)Resume.observedObservedStatag2-network-governanceFor the smallest case you can pass and and call it done.
Passport(name="alice")Resume()三个数据类描述网络中的Agent身份。租户提供大部分字段;Hub标记其余字段。
python
from ag2.network import Passport, Resume, ResumeExample
passport = Passport(
name="alice", # 必填,Hub内唯一
owner="acme", # 可选,多租户部署中的租户ID
model="claude-sonnet-4-6", # 可选,在节点查询结果中显示
kind="agent", # 可选: "agent"(默认/None)| "human" | "remote_agent"
)
resume = Resume(
claimed_capabilities=["analysis", "policy"],
domains=["finance"],
summary="高级政策分析师——场景综合与反驳审查。",
examples=[ResumeExample(title="Q3风险简报", note="…")],
)Hub会在注册时标记和。(类型别名)用于区分参与者类型——/对应常规LLM驱动的,对应(使用而非),预留用于A2A/联邦场景。可按类型筛选。字段(每个能力对应的计数)会在Agent运行带有能力标签的任务时由Hub更新——请参考了解配置方法。
Passport.agent_idPassport.created_atPassport.kindPassportKindNone"agent"AgentClient"human"HumanClientregister_humanregister"remote_agent"hub.list_agents(kind=...)Resume.observedObservedStatag2-network-governance最简场景下,你只需传入和即可。
Passport(name="alice")Resume()Channel lifecycle
通道生命周期
agent_client.open(type=..., target=...)
│
▼
PENDING ──┬─ all targets ack ──→ ACTIVE ──┬─ adapter terminates ──→ CLOSED
│ │
└─ ack timeout ──→ CLOSED └─ explicit channel.close() ──→ CLOSING ──→ CLOSED
(invite_timeout) or TTL expiredThe default on is 30s; if any invited target doesn't ack within that window, raises and the channel goes straight to CLOSED with reason . (The separate expectation-sweeper path — the expectation — auto-closes with instead; see the consulting section below.)
invite_ack_timeoutHub.open(...)create_channelProtocolError"invite_timeout"acks_within"expectation_violated:acks_within"agent_client.open(type=..., target=...)
│
▼
PENDING ──┬─ 所有目标确认 ──→ ACTIVE ──┬─ 适配器终止 ──→ CLOSED
│ │
└─ 确认超时 ──→ CLOSED └─ 显式调用channel.close() ──→ CLOSING ──→ CLOSED
(invite_timeout) 或超时过期Hub.open(...)invite_ack_timeoutcreate_channelProtocolError"invite_timeout"acks_within"expectation_violated:acks_within"Sending an envelope
发送消息包
python
await channel.send(text, audience=[bob.agent_id])audienceEnvelope.envelope_idsender_idcreated_atFor custom event types, send raw envelopes via . The full shape and the event constants are documented in .
agent_client.send_envelope(envelope)EnvelopeEV_*ag2-network-tools-and-viewspython
await channel.send(text, audience=[bob.agent_id])audienceEnvelope.envelope_idsender_idcreated_at如需发送自定义事件类型,可通过发送原始消息包。完整的结构和事件常量请参考文档。
agent_client.send_envelope(envelope)EnvelopeEV_*ag2-network-tools-and-viewsThe five channel-close routes
五种通道关闭路径
Every channel terminates with an envelope. Five routes lead there; pick by who decides:
EV_CHANNEL_CLOSED| Pattern | Who decides | Best for |
|---|---|---|
| Your orchestration code | Custom caps (turn count, time, predicate) |
Agent-side tool ( | The LLM | "Agent decides we're done" |
| Adapter sentinel (subclass) | The framework | Content-based stop ("TERMINATE" keyword) |
Workflow | A declarative graph | Multi-step orchestrations |
| TTL / expectations | The hub's sweepers | Time- or expectation-based safety nets |
Adapter compatibility:
| Auto-close | App close | Agent tool | Sentinel | Workflow graph | |
|---|---|---|---|---|---|
| Yes (after reply) | Yes (early bail) | Yes | Subclass | n/a |
| Never | Yes (typical) | Yes | Canonical | n/a |
| Never | Yes (typical) | Yes | Subclass | n/a |
| Yes (graph) | Yes (override) | Yes (via | n/a | Canonical |
每个通道都会以消息包终止。共有五种关闭路径,选择依据是谁做出终止决定:
EV_CHANNEL_CLOSED| 模式 | 决策方 | 最佳适用场景 |
|---|---|---|
| 你的编排代码 | 自定义限制(轮次计数、时间、谓词) |
Agent端工具( | LLM | "Agent决定对话结束" |
| 适配器哨兵(子类) | 框架 | 基于内容的停止("TERMINATE"关键字) |
Workflow | 声明式图 | 多步骤编排 |
| TTL / 预期规则 | Hub的清理器 | 基于时间或预期的安全机制 |
适配器兼容性:
| 自动关闭 | 应用端关闭 | Agent工具 | 哨兵 | Workflow图 | |
|---|---|---|---|---|---|
| 是(回复后) | 是(提前终止) | 是 | 子类 | 不适用 |
| 否 | 是(典型场景) | 是 | 标准实现 | 不适用 |
| 否 | 是(典型场景) | 是 | 子类 | 不适用 |
| 是(图终止) | 是(覆盖) | 是(通过 | 不适用 | 标准实现 |
Pattern: agent-side close tool
模式:Agent端关闭工具
The LLM decides when to stop. Inject the active into the tool:
Channelpython
from ag2.network.client.inject import ChannelInject
async def end_conversation(reason: str, channel: ChannelInject) -> str:
"""Close the active channel. The reason flows on EV_CHANNEL_CLOSED."""
if channel is None:
return "no active channel"
await channel.close(reason=f"agent_close:{reason}")
return f"closed: {reason}"
alice_agent.tool(end_conversation)
bob_agent.tool(end_conversation)The default handler stamps the active into before each LLM turn, so resolves automatically. Outside a network turn it resolves to — the guard above keeps the tool safe in non-network contexts.
Channelcontext.dependenciesChannelInjectNonePrefer the tool-call pattern over an adapter sentinel ("TERMINATE" keyword) — tool calls are typed, traceable on the WAL, multilingual, and resist prompt injection.
由LLM决定何时停止。将当前注入工具:
Channelpython
from ag2.network.client.inject import ChannelInject
async def end_conversation(reason: str, channel: ChannelInject) -> str:
"""关闭当前活动通道。原因会随EV_CHANNEL_CLOSED事件传递。"""
if channel is None:
return "无活动通道"
await channel.close(reason=f"agent_close:{reason}")
return f"已关闭: {reason}"
alice_agent.tool(end_conversation)
bob_agent.tool(end_conversation)默认处理器会在每次LLM轮次前将当前标记到中,因此会自动解析。在非网络轮次中,它会解析为——上述防护措施可确保工具在非网络环境中安全运行。
Channelcontext.dependenciesChannelInjectNone优先选择工具调用模式而非适配器哨兵("TERMINATE"关键字)——工具调用是类型化的、可在WAL中追踪的、多语言兼容的,且能抵御提示注入攻击。
Watching for close
监听关闭事件
All five routes produce the same envelope, so observers only need one predicate:
EV_CHANNEL_CLOSEDpython
close_env = await alice.wait_for_channel_event(
channel_id=channel.channel_id,
predicate=lambda e: e.event_type == EV_CHANNEL_CLOSED,
timeout=180.0,
)
print(f"reason: {close_env.event_data.get('reason')!r}")ChannelMetadata.close_reasonawait hub.get_channel(channel_id)所有五种路径都会生成相同的消息包,因此观察者只需一个谓词:
EV_CHANNEL_CLOSEDpython
close_env = await alice.wait_for_channel_event(
channel_id=channel.channel_id,
predicate=lambda e: e.event_type == EV_CHANNEL_CLOSED,
timeout=180.0,
)
print(f"原因: {close_env.event_data.get('reason')!r}")ChannelMetadata.close_reasonawait hub.get_channel(channel_id)Replay and inspection
重放与检查
The hub's WAL is the single source of truth for "what happened in this channel":
python
wal = await hub.read_wal(channel.channel_id)
for env in wal:
print(env.created_at, env.event_type, env.sender_id, env.event_data)Every envelope is hub-stamped (, , , , , ). The WAL is keyed by channel id; reading it works for any channel this process can see.
envelope_idcreated_atsender_idaudienceevent_typeevent_dataFor the audit log (hub-level events: agent registered, channel created/closed, expectation violated), see .
ag2-network-governanceHub的WAL是"该通道中发生了什么"的唯一可信来源:
python
wal = await hub.read_wal(channel.channel_id)
for env in wal:
print(env.created_at, env.event_type, env.sender_id, env.event_data)每个消息包都由Hub标记(、、、、、)。WAL按通道ID存储;只要进程有权限,即可读取任意通道的WAL。
envelope_idcreated_atsender_idaudienceevent_typeevent_data关于审计日志(Hub级事件:Agent注册、通道创建/关闭、预期违规),请参考。
ag2-network-governanceClosing down
关闭流程
python
await alice_hc.close()
await bob_hc.close()
await hub.close()Always pair with (typically in ). cancels the sweeper tasks, closes the underlying store, drains pending I/O. cancels the link's listening task and unsubscribes the clients.
Hub.open(...)hub.close()try/finallyhub.close()HubClient.close()python
await alice_hc.close()
await bob_hc.close()
await hub.close()请始终将与配对使用(通常放在中)。会取消清理器任务、关闭底层存储、排空待处理I/O。会取消链接的监听任务并注销客户端。
Hub.open(...)hub.close()try/finallyhub.close()HubClient.close()What to load next
后续加载推荐
| User goal | Skill |
|---|---|
| N-party round-robin / fixed turn order | |
Declarative orchestration / | |
| Rate limits, access policy, expectations, audit, capability tracking, swappable arbiter / hub listeners | |
Adapter-owned tools ( | |
| 用户目标 | 技能 |
|---|---|
| 多方轮询 / 固定轮次顺序 | |
声明式编排 / | |
| 速率限制、访问策略、预期规则、审计、能力追踪、可替换仲裁器/Hub监听器 | |
适配器专属工具( | |
Quick reference — imports
快速参考 — 导入
python
from ag2.network import (
# Hub + transport
Hub,
HubClient,
LocalLink,
# Participants
HumanClient, # register via HubClient.register_human(...)
# Identity
Passport,
PassportKind, # Literal["agent", "human", "remote_agent"] | None
Resume,
ResumeExample,
# Envelopes + events
Envelope,
EV_TEXT,
EV_CHANNEL_INVITE,
EV_CHANNEL_INVITE_ACK,
EV_CHANNEL_OPENED,
EV_CHANNEL_CLOSED,
EV_CHANNEL_EXPIRED,
Priority,
# Errors
AccessDeniedError,
InboxFull,
ProtocolError,
)
from ag2.knowledge import MemoryKnowledgeStore # or DiskKnowledgeStorepython
from ag2.network import (
# Hub + 传输
Hub,
HubClient,
LocalLink,
# 参与者
HumanClient, # 通过HubClient.register_human(...)注册
# 身份标识
Passport,
PassportKind, # Literal["agent", "human", "remote_agent"] | None
Resume,
ResumeExample,
# 消息包 + 事件
Envelope,
EV_TEXT,
EV_CHANNEL_INVITE,
EV_CHANNEL_INVITE_ACK,
EV_CHANNEL_OPENED,
EV_CHANNEL_CLOSED,
EV_CHANNEL_EXPIRED,
Priority,
# 错误
AccessDeniedError,
InboxFull,
ProtocolError,
)
from ag2.knowledge import MemoryKnowledgeStore # 或DiskKnowledgeStore