ag2-network-governance

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

AG2 Network — Governance

AG2 网络——治理

Everything hub-side: identity, per-agent rules, expectations, audit, and task observation. The hub is the single source of truth — every send goes through it, every observation reads from it, every policy is checked there.
Prerequisite: read
ag2-network-quickstart
first. This skill assumes you know
Hub.open
,
Passport
,
Resume
, the channel lifecycle, and the
agent_client.register(...)
flow.
所有中心节点侧的功能:身份标识、每个智能体的规则、预期配置、审计以及任务观测。中心节点是唯一的可信数据源——所有消息发送都需经过它,所有观测数据都从它读取,所有策略都在它这里校验。
前置要求:请先阅读
ag2-network-quickstart
。本技能默认你已了解
Hub.open
、
Passport
、
Resume
、通道生命周期以及
agent_client.register(...)
流程。

When to use

使用场景

Load this skill when the user needs to:
  • Limit who can talk to whom (
    access
    /
    AccessBlock
    )
  • Declare a token-bucket rate intent (
    limits.rate
    /
    RateBlock
    ) — stored on the rule but not enforced by the in-process hub
  • Cap inbox size to prevent flooding (
    limits.inbox
    /
    InboxBlock
    ) — and get an early-warning signal before the cap (
    on_inbox_pressure
    /
    high_water
    )
  • Set channel TTL defaults, concurrency caps, or delegation depth (
    limits
    /
    LimitsBlock
    )
  • Plug in custom access / routing logic — JWT scopes, per-tenant quotas, federation (
    HubArbiter
    /
    RuleBasedArbiter
    )
  • Authenticate agents at registration (
    AuthAdapter
    )
  • Tune the channel-close timing (
    acks_within
    ,
    reply_within
    ,
    max_silence
    ,
    turn_within
    )
  • Read or query the audit log for compliance — or stream live state changes to metrics / alerting (
    HubListener
    )
  • Add a custom periodic task to the hub (
    register_sweeper
    )
  • Build a capability track record on each agent (
    Resume.observed
    )
  • Route based on which agents have demonstrably done a task (e.g. "send to whichever researcher has the lowest
    p50_latency_ms
    ")
当用户需要以下功能时,加载本技能:
  • 限制智能体之间的通信对象(
    access
    /
    AccessBlock
    )
  • 声明令牌桶速率限制意图(
    limits.rate
    /
    RateBlock
    )——规则中会存储该配置,但进程内中心节点不强制执行
  • 限制收件箱大小以防止消息泛滥(
    limits.inbox
    /
    InboxBlock
    )——并在达到上限前触发预警信号(
    on_inbox_pressure
    /
    high_water
    )
  • 设置通道TTL默认值、并发上限或委托深度(
    limits
    /
    LimitsBlock
    )
  • 接入自定义访问/路由逻辑——JWT权限范围、租户配额、联邦机制(
    HubArbiter
    /
    RuleBasedArbiter
    )
  • 在注册时验证智能体身份(
    AuthAdapter
    )
  • 调整通道关闭时机(
    acks_within
    、
    reply_within
    、
    max_silence
    、
    turn_within
    )
  • 读取或查询审计日志以满足合规要求——或实时流式传输状态变更至指标/告警系统(
    HubListener
    )
  • 为中心节点添加自定义定时任务(
    register_sweeper
    )
  • 为每个智能体构建能力记录(
    Resume.observed
    )
  • 根据智能体已完成任务的情况进行路由(例如:"发送给
    p50_latency_ms
    最低的研究员")

Identity — what every agent carries

身份标识——每个智能体的必备信息

Three dataclasses describe an agent on the network. The 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
)

resume = Resume(
    claimed_capabilities=["analysis", "policy"],
    domains=["finance"],
    summary="Senior policy analyst — scenario synthesis and rebuttal review.",
    examples=[ResumeExample(title="Q3 risk brief", note="…")],
)
FieldSource
Passport.name
tenant (required, unique)
Passport.agent_id
hub-stamped at registration; use for routing
Passport.created_at
hub-stamped ISO-Z timestamp
Resume.claimed_capabilities
tenant (free-form strings:
"research"
,
"summarisation"
, …)
Resume.summary
tenant — indexed for peer lookup
Resume.observed
hub-mutated per-capability
ObservedStat
(n / completed / failed / expired / p50_latency_ms)
Resume.last_updated
hub-stamped ISO-Z, refreshed on mutation
The
observed
field is the agent's track record. It grows automatically as the agent runs capability-tagged tasks (see "Task observation" below).
三个数据类描述了网络中的智能体。大部分字段由租户提供,其余由中心节点标记。
python
from ag2.network import Passport, Resume, ResumeExample

passport = Passport(
    name="alice",                # 必填,中心节点内唯一
    owner="acme",                # 可选,多租户部署中的租户ID
    model="claude-sonnet-4-6",   # 可选,在peer查询结果中展示
)

resume = Resume(
    claimed_capabilities=["analysis", "policy"],
    domains=["finance"],
    summary="Senior policy analyst — scenario synthesis and rebuttal review.",
    examples=[ResumeExample(title="Q3 risk brief", note="…")],
)
字段来源
Passport.name
租户(必填,唯一)
Passport.agent_id
中心节点标记:注册时生成;用于路由
Passport.created_at
中心节点标记:ISO-Z时间戳
Resume.claimed_capabilities
租户(自由格式字符串:
"research"
、
"summarisation"
等)
Resume.summary
租户——用于peer查询的索引字段
Resume.observed
中心节点更新:每个能力对应的
ObservedStat
(n/已完成/失败/过期/p50_latency_ms)
Resume.last_updated
中心节点标记:ISO-Z时间戳,变更时刷新
observed
字段是智能体的任务记录。当智能体运行带有能力标签的任务时,该字段会自动更新(见下文"任务观测")。

Per-agent rules

每个智能体的规则

Pass a
Rule
at registration to govern an agent's behaviour on the network:
python
from ag2.network import (
    Rule, AccessBlock, LimitsBlock, RateBlock, InboxBlock, ChannelTypeAccess,
)

rule = Rule(
    access=AccessBlock(
        outbound_to=["bob", "carol"],           # whitelist of recipients (names or ids)
        channel_types=ChannelTypeAccess(
            initiate=["consulting", "discussion"],
            accept=["consulting", "discussion"],
        ),
    ),
    limits=LimitsBlock(                         # rate + inbox nest INSIDE limits
        channel_ttl_default="4h",               # default TTL for channels this agent creates
        delegation_depth=2,                     # max recursion through sub-task delegation
        rate=RateBlock(per_minute=60, burst=10),
        inbox=InboxBlock(max_pending=100),      # cap inbound queue depth
    ),
)

alice = await alice_hc.register(
    Agent("alice", config=config),
    Passport(name="alice"),
    Resume(),
    rule=rule,
)
A
Rule
has two top-level blocks:
access
(
AccessBlock
) and
limits
(
LimitsBlock
).
RateBlock
and
InboxBlock
nest inside
limits
(as
limits.rate
and
limits.inbox
).
BlockLives atControlsFailure mode
AccessBlock
access
(top-level)
Who this agent can address (
outbound_to
) / accept from (
inbound_from
); channel types it can create/join
AccessDeniedError
LimitsBlock
limits
(top-level)
TTL defaults;
delegation_depth
;
max_concurrent_channels
/
max_concurrent_tasks
AccessDeniedError
RateBlock
limits.rate
(nested)
Token-bucket values (
per_minute
,
burst
)
Stored but not enforced in-process (
per_minute=0
disables by default)
InboxBlock
limits.inbox
(nested)
Inbound queue depth (
max_pending
)
InboxFull
to the sender
When a rule check fails the hub raises the matching error from
channel.send(...)
or
hc.register(...)
; the envelope never lands on the WAL. Rule changes are audited (kind
AUDIT_KIND_RULE_SET
via
set_rule
), but a send denial is not written to the built-in audit log — it surfaces via the
on_envelope_rejected
listener fan-out (the built-in
AuditLog
implements no
on_envelope_rejected
handler). Register a
HubListener
if you need to capture rejections. The component that runs those checks — and the seam where you'd plug in something other than rule data — is the arbiter, below.
注册时传入
Rule
以管控智能体在网络中的行为:
python
from ag2.network import (
    Rule, AccessBlock, LimitsBlock, RateBlock, InboxBlock, ChannelTypeAccess,
)

rule = Rule(
    access=AccessBlock(
        outbound_to=["bob", "carol"],           # 允许发送的收件人白名单(名称或ID)
        channel_types=ChannelTypeAccess(
            initiate=["consulting", "discussion"],
            accept=["consulting", "discussion"],
        ),
    ),
    limits=LimitsBlock(                         # rate和inbox嵌套在limits内部
        channel_ttl_default="4h",               # 该智能体创建的通道默认TTL
        delegation_depth=2,                     # 子任务委托的最大递归深度
        rate=RateBlock(per_minute=60, burst=10),
        inbox=InboxBlock(max_pending=100),      # 入站队列深度上限
    ),
)

alice = await alice_hc.register(
    Agent("alice", config=config),
    Passport(name="alice"),
    Resume(),
    rule=rule,
)
Rule
包含两个顶级模块:
access
(
AccessBlock
)和
limits
(
LimitsBlock
)。
RateBlock
和
InboxBlock
嵌套在
limits
内部(作为
limits.rate
和
limits.inbox
)。
模块位置管控内容失败模式
AccessBlock
access
(顶级)
该智能体可发送的对象(
outbound_to
)/可接收的对象(
inbound_from
);可创建/加入的通道类型
AccessDeniedError
LimitsBlock
limits
(顶级)
TTL默认值;
delegation_depth
;
max_concurrent_channels
/
max_concurrent_tasks
AccessDeniedError
RateBlock
limits.rate
(嵌套)
令牌桶参数(
per_minute
、
burst
)
仅存储但进程内不强制执行(默认
per_minute=0
表示禁用)
InboxBlock
limits.inbox
(嵌套)
入站队列深度(
max_pending
)
向发送方返回
InboxFull
当规则校验失败时,中心节点会从
channel.send(...)
或
hc.register(...)
抛出对应的错误;消息信封不会写入WAL。规则变更会被审计(通过
set_rule
触发
AUDIT_KIND_RULE_SET
),但发送被拒绝的情况不会写入内置审计日志——而是通过
on_envelope_rejected
监听器广播(内置
AuditLog
未实现
on_envelope_rejected
处理程序)。如果需要捕获拒绝事件,请注册
HubListener
。执行这些校验的组件——以及你可以接入自定义逻辑的接口——是下面的仲裁器(arbiter)。

Updating a rule after registration

注册后更新规则

python
new_rule = Rule(access=AccessBlock(outbound_to=["bob"]))
await hub.set_rule(alice.agent_id, new_rule)  # emits AUDIT_KIND_RULE_SET
python
new_rule = Rule(access=AccessBlock(outbound_to=["bob"]))
await hub.set_rule(alice.agent_id, new_rule)  # 触发AUDIT_KIND_RULE_SET

Parsing duration strings

解析时长字符串

LimitsBlock.channel_ttl_default
accepts a string parsed by
parse_duration
:
python
from ag2.network import parse_duration

parse_duration("30s")  # 30
parse_duration("4h")   # 14400
parse_duration("2d")   # 172800
s
,
m
,
h
,
d
suffixes; a bare integer (or
int
) is treated as seconds; empty string returns
0
. Returns an
int
number of seconds.
LimitsBlock.channel_ttl_default
接受由
parse_duration
解析的字符串:
python
from ag2.network import parse_duration

parse_duration("30s")  # 30
parse_duration("4h")   # 14400
parse_duration("2d")   # 172800
支持
s
、
m
、
h
、
d
后缀;纯整数(或
int
类型)会被视为秒数;空字符串返回
0
。返回值为秒数的整数。

The arbiter — swappable access & routing

仲裁器——可替换的访问与路由

The hub doesn't enforce
Rule
s with inline
if
checks anymore; it delegates every access / routing decision to a
HubArbiter
— a Protocol with one method per decision point, each returning
Allow()
or
Deny(reason, error=<NetworkError subclass>)
:
MethodConsulted before…Default
Deny
error
authorize_register(passport, resume, rule)
committing a registration
AccessDeniedError
authorize_channel_open(manifest, creator, creator_rule, invitees, invitee_rules, active_creator_channels)
creating a channel (invitee
inbound_from
+ creator
max_concurrent_channels
)
AccessDeniedError
authorize_send(envelope, sender, sender_rule, recipients)
appending an envelope to the WAL (outbound access + delegation depth)
AccessDeniedError
authorize_inbox(envelope, recipient, recipient_rule, current_pending)
enqueuing into a recipient's inbox (capacity)
InboxFull
authorize_dispatch(envelope, sender, recipient, recipient_rule)
dispatching one delivery
AccessDeniedError
resolve_unknown_audience(envelope, unknown_ids)
dispatching to ids the hub doesn't know — returns
None
(drop silently — the single-hub default) or a replacement id list (federation hook)
—
The default is
RuleBasedArbiter
— it enforces the per-agent
Rule
:
access
(outbound/inbound name globs, channel types) plus the
limits
caps it actually checks (
delegation_depth
,
max_concurrent_channels
,
inbox.max_pending
).
limits.rate
is not enforced. This is exactly what the hub did inline before this seam existed. If you only use
Rule
, you never touch the arbiter.
Swap it to layer your own logic — JWT scopes, per-tenant quotas, federation routing — on top of (or instead of) the rule data.
BaseHubArbiter
returns
Allow()
for everything, so a subclass that overrides one gate would allow the rest — to keep rule enforcement, delegate to a
RuleBasedArbiter()
instance:
python
from ag2.network import HubArbiter, BaseHubArbiter, RuleBasedArbiter, Allow, Deny

class ScopedArbiter(BaseHubArbiter):
    def __init__(self, inner: HubArbiter) -> None:
        self._inner = inner                           # the rule checks
    async def authorize_send(self, envelope, sender, sender_rule, recipients):
        if not _token_has_scope(sender, "net.send"):
            return Deny("missing net.send scope")     # → AccessDeniedError back to the caller
        return await self._inner.authorize_send(envelope, sender, sender_rule, recipients)
    # authorize_register / _channel_open / _inbox / _dispatch / resolve_unknown_audience
    # all fall through to BaseHubArbiter's Allow() — explicitly re-delegate any you want enforced.

hub.register_arbiter(ScopedArbiter(RuleBasedArbiter()))   # one active arbiter; replaces the prior one
中心节点不再通过内联
if
校验来强制执行
Rule
;而是将所有访问/路由决策委托给**
HubArbiter
**——一个协议,每个决策点对应一个方法,返回
Allow()
或
Deny(reason, error=<NetworkError子类>)
:
方法触发时机默认拒绝错误
authorize_register(passport, resume, rule)
提交注册前
AccessDeniedError
authorize_channel_open(manifest, creator, creator_rule, invitees, invitee_rules, active_creator_channels)
创建通道前(校验受邀方
inbound_from
+ 创建方
max_concurrent_channels
)
AccessDeniedError
authorize_send(envelope, sender, sender_rule, recipients)
将信封写入WAL前(校验出站访问权限 + 委托深度)
AccessDeniedError
authorize_inbox(envelope, recipient, recipient_rule, current_pending)
将信封加入收件人队列前(校验容量)
InboxFull
authorize_dispatch(envelope, sender, recipient, recipient_rule)
分发单个消息前
AccessDeniedError
resolve_unknown_audience(envelope, unknown_ids)
向中心节点未知的ID分发消息时——返回
None
(静默丢弃——单中心节点默认行为)或替换后的ID列表(联邦机制钩子)
—
默认实现是**
RuleBasedArbiter
**——它强制执行每个智能体的
Rule
:
access
(出站/入站名称通配符、通道类型)以及
limits
中实际校验的上限(
delegation_depth
、
max_concurrent_channels
、
inbox.max_pending
)。
limits.rate
不强制执行。这与中心节点之前内联执行的逻辑完全一致。如果仅使用
Rule
,则无需接触仲裁器。
替换仲裁器以叠加自定义逻辑——JWT权限范围、租户配额、联邦路由——在规则数据之上(或替代规则数据)。
BaseHubArbiter
对所有请求返回
Allow()
,因此仅覆盖某个校验点的子类会允许其他所有请求——若要保留规则校验,请委托给
RuleBasedArbiter()
实例:
python
from ag2.network import HubArbiter, BaseHubArbiter, RuleBasedArbiter, Allow, Deny

class ScopedArbiter(BaseHubArbiter):
    def __init__(self, inner: HubArbiter) -> None:
        self._inner = inner                           # 规则校验逻辑
    async def authorize_send(self, envelope, sender, sender_rule, recipients):
        if not _token_has_scope(sender, "net.send"):
            return Deny("missing net.send scope")     # → 返回AccessDeniedError给调用方
        return await self._inner.authorize_send(envelope, sender, sender_rule, recipients)
    # authorize_register / _channel_open / _inbox / _dispatch / resolve_unknown_audience
    # 均默认使用BaseHubArbiter的Allow()——如需强制执行,请显式委托。

hub.register_arbiter(ScopedArbiter(RuleBasedArbiter()))   # 仅一个活跃仲裁器;替换之前的实例

hub.arbiter → the active instance (read-only; handy in tests)

hub.arbiter → 活跃实例(只读;测试时常用)


`Deny.error` picks the `NetworkError` subclass the hub raises (default `AccessDeniedError`) — `Deny(..., error=InboxFull)` etc. to control it. The arbiter is the **gatekeeper** (consulted *before* the state change); `HubListener` (later) is the **observer** (notified *after*). It's a different concern from `AuthAdapter` below — that authenticates *credentials* once at registration; the arbiter authorizes *actions* throughout the channel's life.

`Deny.error`指定中心节点抛出的`NetworkError`子类(默认`AccessDeniedError`)——使用`Deny(..., error=InboxFull)`等可控制错误类型。仲裁器是** gatekeeper**(状态变更前校验);`HubListener`(下文)是** observer**(状态变更后通知)。这与下文的`AuthAdapter`是不同的关注点——`AuthAdapter`在注册时一次性验证**凭证**;仲裁器在通道生命周期内持续授权**操作**。

Authentication

身份验证

By default the hub uses
AuthRegistry.default()
— a
NoAuth
-only registry (scheme
"none"
) that accepts every claim, so every registration succeeds without credentials. The scheme is selected per-passport via
passport.auth.scheme
(an
AuthBlock
field, defaulting to
"none"
); the credentials live in
passport.auth.claim
(a
dict
). For production:
python
from ag2.network import AuthAdapter, AuthRegistry, NoAuth, ApiKeyAuth, AuthError, Hub
from ag2.network import AuthBlock, Passport
from ag2.knowledge import MemoryKnowledgeStore

import hmac
from typing import Any


class HMACAuth:
    scheme = "hmac"                                       # class-level scheme label

    async def validate(self, passport: Passport, claim: dict[str, Any]) -> None:
        expected = self._sign(passport.name)
        token = claim.get("token", "")
        if not hmac.compare_digest(expected, token):
            raise AuthError(f"bad hmac for {passport.name}")
默认情况下,中心节点使用
AuthRegistry.default()
——仅包含
NoAuth
的注册器(方案为
"none"
),接受所有声明,因此所有注册无需凭证即可成功。每个护照通过
passport.auth.scheme
(
AuthBlock
字段,默认
"none"
)选择验证方案;凭证存储在
passport.auth.claim
(字典)中。生产环境使用示例:
python
from ag2.network import AuthAdapter, AuthRegistry, NoAuth, ApiKeyAuth, AuthError, Hub
from ag2.network import AuthBlock, Passport
from ag2.knowledge import MemoryKnowledgeStore

import hmac
from typing import Any


class HMACAuth:
    scheme = "hmac"                                       # 类级别的方案标签

    async def validate(self, passport: Passport, claim: dict[str, Any]) -> None:
        expected = self._sign(passport.name)
        token = claim.get("token", "")
        if not hmac.compare_digest(expected, token):
            raise AuthError(f"bad hmac for {passport.name}")

AuthRegistry takes a LIST of adapters at construction (keyed by .scheme).

AuthRegistry在构造时接受适配器列表(按.scheme键区分)。

registry = AuthRegistry([NoAuth(), HMACAuth()])
hub = await Hub.open(MemoryKnowledgeStore(), auth=registry)

`AuthAdapter` is a `Protocol` with a `scheme` attribute and a `validate` method:

```python
class AuthAdapter(Protocol):
    scheme: str
    async def validate(self, passport: Passport, claim: dict[str, Any]) -> None: ...
Raise
AuthError
to reject. At registration the hub looks up the adapter by
passport.auth.scheme
, calls
adapter.validate(passport, passport.auth.claim)
, and records
AUDIT_KIND_AGENT_REGISTERED
on success. Remote-agent passports skip the local auth check. The library ships
NoAuth
(accept-all, the default) and
ApiKeyAuth(keys=..., resolver=...)
(constant-time token compare against
claim["token"]
).
registry = AuthRegistry([NoAuth(), HMACAuth()])
hub = await Hub.open(MemoryKnowledgeStore(), auth=registry)

`AuthAdapter`是一个协议,包含`scheme`属性和`validate`方法:

```python
class AuthAdapter(Protocol):
    scheme: str
    async def validate(self, passport: Passport, claim: dict[str, Any]) -> None: ...
抛出
AuthError
表示拒绝。注册时,中心节点根据
passport.auth.scheme
查找适配器,调用
adapter.validate(passport, passport.auth.claim)
,成功则记录
AUDIT_KIND_AGENT_REGISTERED
。远程智能体的护照跳过本地身份验证。库内置了
NoAuth
(接受所有请求,默认)和
ApiKeyAuth(keys=..., resolver=...)
(对
claim["token"]
执行常量时间令牌比对)。

Expectations — channel-level SLAs

预期配置——通道级SLA

Every adapter ships defaults in its manifest. The expectation sweeper task evaluates them every
expectation_sweep_interval
(default 10s) and dispatches violations to handlers.
每个适配器在其清单中提供默认配置。预期清理任务每隔
expectation_sweep_interval
(默认10秒)评估一次,并将违规情况分发给处理程序。

Built-in evaluators

内置评估器

default_evaluators()
ships exactly three evaluators:
NameClassDefault
seconds
Threshold
"acks_within"
AcksWithinEvaluator
30All still-pending invitees must ack within
params["seconds"]
of channel creation (only while the channel is
PENDING
).
"reply_within"
ReplyWithinEvaluator
600A participant addressed by an
EV_TEXT
must reply within
params["seconds"]
(only while
ACTIVE
).
"max_silence"
MaxSilenceEvaluator
3600The channel has no content envelope from anyone for
params["seconds"]
(channel-wide).
The
discussion
and
workflow
adapters declare
turn_within
expectations on their manifests, but no built-in
turn_within
evaluator ships
— and
"warn"
/
"hide"
are not built-in handlers. The sweeper silently skips any expectation whose
name
has no registered evaluator or whose
on_violation
has no registered handler (
_expectation_tick
does
.get(...)
and
continue
s on
None
). To make
turn_within
/
warn
/
hide
active, register your own evaluator (
register_expectation_evaluator
) and handler (
register_violation_handler
).
default_evaluators()
提供三个评估器:
名称类默认秒数阈值规则
"acks_within"
AcksWithinEvaluator
30所有待确认的受邀方必须在通道创建后的
params["seconds"]
内确认(仅在通道处于
PENDING
状态时生效)。
"reply_within"
ReplyWithinEvaluator
600被
EV_TEXT
消息提及的参与者必须在
params["seconds"]
内回复(仅在通道处于
ACTIVE
状态时生效)。
"max_silence"
MaxSilenceEvaluator
3600通道在
params["seconds"]
内未收到任何内容信封(全通道范围)。
discussion
和
workflow
适配器在其清单中声明了
turn_within
预期配置,但没有内置的
turn_within
评估器
——且
"warn"
/
"hide"
也不是内置处理程序。清理任务会静默跳过任何
name
未注册评估器或
on_violation
未注册处理程序的预期配置(
_expectation_tick
会调用
.get(...)
,若返回
None
则继续执行)。要启用
turn_within
/
warn
/
hide
,请注册自定义评估器(
register_expectation_evaluator
)和处理程序(
register_violation_handler
)。

Default expectations per adapter

各适配器的默认预期配置

AdapterDefaults
consulting
acks_within(30s, auto_close)
,
reply_within(600s, auto_close)
conversation
max_silence(3600s, audit)
discussion
turn_within(120s, warn)
,
turn_within(600s, hide)
workflow
turn_within(120s, warn)
,
turn_within(600s, auto_close)
适配器默认配置
consulting
acks_within(30s, auto_close)
、
reply_within(600s, auto_close)
conversation
max_silence(3600s, audit)
discussion
turn_within(120s, warn)
、
turn_within(600s, hide)
workflow
turn_within(120s, warn)
、
turn_within(600s, auto_close)

Violation handlers

违规处理程序

python
from ag2.network import Expectation

Expectation(name="acks_within", on_violation="auto_close", params={"seconds": 30})
default_handlers()
ships exactly three handlers, keyed by
on_violation
:
on_violation
Handler classEffect
"audit"
AuditHandler
No-op handler. The actual audit record (
AUDIT_KIND_EXPECTATION_VIOLATED
) is written by the
AuditLog
listener via
on_expectation_fired
, which the hub fans out before invoking any handler — so
AuditHandler
itself does nothing. Channel continues.
"notify_channel"
NotifyChannelHandler
Post
EV_EXPECTATION_VIOLATED
to every channel participant. Channel continues. (Audit is still written by the
AuditLog
listener.)
"auto_close"
AutoCloseHandler
Close the channel with
reason="expectation_violated:<name>"
. (Audit is still written by the
AuditLog
listener.)
There is no built-in
"warn"
or
"hide"
handler
— those names appear only on the
discussion
/
workflow
manifests and are no-ops until you register a handler for them.
python
from ag2.network import Expectation

Expectation(name="acks_within", on_violation="auto_close", params={"seconds": 30})
default_handlers()
提供三个处理程序,按
on_violation
键区分:
on_violation
处理程序类效果
"audit"
AuditHandler
空操作处理程序。实际的审计记录(
AUDIT_KIND_EXPECTATION_VIOLATED
)由
AuditLog
监听器通过
on_expectation_fired
写入,中心节点在调用任何处理程序前会广播该事件——因此
AuditHandler
本身不执行任何操作。通道继续运行。
"notify_channel"
NotifyChannelHandler
向所有通道参与者发送
EV_EXPECTATION_VIOLATED
消息。通道继续运行。(审计记录仍由
AuditLog
监听器写入。)
"auto_close"
AutoCloseHandler
关闭通道,
reason="expectation_violated:<name>"
。(审计记录仍由
AuditLog
监听器写入。)
没有内置的
"warn"
或
"hide"
处理程序
——这些名称仅出现在
discussion
/
workflow
适配器的清单中,在注册对应的处理程序前均为无效操作。

Overriding adapter defaults

覆盖适配器默认配置

Pass
expectations
in the channel knobs to replace the adapter's defaults:
python
channel = await alice.open(
    type="conversation",
    target=bob.agent_id,
    knobs={
        "expectations": [
            {"name": "max_silence", "on_violation": "auto_close",
             "params": {"seconds": 600}},
        ],
    },
)
在通道knobs中传入
expectations
以替换适配器的默认配置:
python
channel = await alice.open(
    type="conversation",
    target=bob.agent_id,
    knobs={
        "expectations": [
            {"name": "max_silence", "on_violation": "auto_close",
             "params": {"seconds": 600}},
        ],
    },
)

Custom evaluators

自定义评估器

python
from ag2.network import EV_TEXT, Expectation
from ag2.network import ExpectationContext, Violation


class TooManyMessagesEvaluator:
    name = "too_many_messages"

    # Signature: evaluate(self, expectation, context) -> Violation | None
    def evaluate(self, expectation: Expectation, context: ExpectationContext) -> Violation | None:
        threshold = int(expectation.params["max"])
        text_count = sum(1 for e in context.wal if e.event_type == EV_TEXT)
        if text_count > threshold:
            return Violation(
                expectation=expectation,                 # the Expectation object, not a string
                violator_ids=[],                          # channel-wide
                detail={"text_count": text_count, "threshold": threshold},
            )
        return None
Violation
is
Violation(expectation: Expectation, violator_ids: list[str] = [], detail: dict = {})
—
expectation
is the
Expectation
object (it carries
name
,
on_violation
,
params
);
channel_id
is not a field (the hub already knows it). Evaluators are pure functions over channel state — no I/O, no mutation — so they're trivially testable. Register via
hub.register_expectation_evaluator(TooManyMessagesEvaluator())
.
python
from ag2.network import EV_TEXT, Expectation
from ag2.network import ExpectationContext, Violation


class TooManyMessagesEvaluator:
    name = "too_many_messages"

    # 签名:evaluate(self, expectation, context) -> Violation | None
    def evaluate(self, expectation: Expectation, context: ExpectationContext) -> Violation | None:
        threshold = int(expectation.params["max"])
        text_count = sum(1 for e in context.wal if e.event_type == EV_TEXT)
        if text_count > threshold:
            return Violation(
                expectation=expectation,                 # Expectation对象,而非字符串
                violator_ids=[],                          # 全通道范围
                detail={"text_count": text_count, "threshold": threshold},
            )
        return None
Violation
的定义为
Violation(expectation: Expectation, violator_ids: list[str] = [], detail: dict = {})
——
expectation
是
Expectation
对象(包含
name
、
on_violation
、
params
);
channel_id
不是字段(中心节点已知晓)。评估器是基于通道状态的纯函数——无I/O、无变更——因此易于测试。通过
hub.register_expectation_evaluator(TooManyMessagesEvaluator())
注册。

Deterministic testing

确定性测试

python
hub = await Hub.open(MemoryKnowledgeStore(), expectation_sweep_interval=0)
python
hub = await Hub.open(MemoryKnowledgeStore(), expectation_sweep_interval=0)

Manually advance state and tick:

手动推进状态并触发评估:

clock.advance(45) await hub._expectation_tick() # operator API (leading underscore by convention)
undefined
clock.advance(45) await hub._expectation_tick() # 操作符API(按约定以开头下划线标识)
undefined

Audit log

审计日志

The hub maintains an append-only audit log (
AuditLog
instance), exposed via the public
hub.audit_log
property (the internal attribute is
hub._audit_log
; swap the instance with
hub.replace_audit_log(...)
):
python
records = await hub.audit_log.read_all()
for r in records:
    print(r["kind"], r["at"], r)
Each record is a plain dict with at minimum
kind
and
at
(ISO-Z timestamp); kind-specific fields appear alongside.
中心节点维护一个追加式审计日志(
AuditLog
实例),通过公共属性
hub.audit_log
暴露(内部属性为
hub._audit_log
;可通过
hub.replace_audit_log(...)
替换实例):
python
records = await hub.audit_log.read_all()
for r in records:
    print(r["kind"], r["at"], r)
每条记录是一个普通字典,至少包含
kind
和
at
(ISO-Z时间戳);特定类型的字段会附加在其中。

Audit kinds

审计类型

python
from ag2.network import (
    AUDIT_KIND_AGENT_REGISTERED,
    AUDIT_KIND_AGENT_UNREGISTERED,
    AUDIT_KIND_RESUME_SET,
    AUDIT_KIND_SKILL_SET,
    AUDIT_KIND_RULE_SET,
    AUDIT_KIND_CHANNEL_CREATED,
    AUDIT_KIND_CHANNEL_CLOSED,
    AUDIT_KIND_CHANNEL_EXPIRED,
    AUDIT_KIND_TASK_TERMINATED,
    AUDIT_KIND_EXPECTATION_VIOLATED,
)
KindWhenCommon fields
AUDIT_KIND_AGENT_REGISTERED
hc.register(...)
agent_id
,
name
,
owner
AUDIT_KIND_AGENT_UNREGISTERED
hc.unregister(agent_id)
agent_id
AUDIT_KIND_RESUME_SET
hub.set_resume(...)
Source:
RESUME_SOURCE_TENANT
or
RESUME_SOURCE_OBSERVED
AUDIT_KIND_SKILL_SET
hub.set_skill(...)
Updated skill markdown
AUDIT_KIND_RULE_SET
hub.set_rule(...)
The new
Rule
AUDIT_KIND_CHANNEL_CREATED
alice.open(...)
creator_id
, manifest type/version, participants
AUDIT_KIND_CHANNEL_CLOSED
Any close route
reason
AUDIT_KIND_CHANNEL_EXPIRED
TTL sweeperTTL details
AUDIT_KIND_TASK_TERMINATED
agent.task(...)
reached terminal state via
TaskMirror
owner_id
,
capability
,
outcome
,
latency_ms
AUDIT_KIND_EXPECTATION_VIOLATED
Expectation evaluator's threshold elapsed
expectation
,
channel_id
, evaluator detail
python
from ag2.network import (
    AUDIT_KIND_AGENT_REGISTERED,
    AUDIT_KIND_AGENT_UNREGISTERED,
    AUDIT_KIND_RESUME_SET,
    AUDIT_KIND_SKILL_SET,
    AUDIT_KIND_RULE_SET,
    AUDIT_KIND_CHANNEL_CREATED,
    AUDIT_KIND_CHANNEL_CLOSED,
    AUDIT_KIND_CHANNEL_EXPIRED,
    AUDIT_KIND_TASK_TERMINATED,
    AUDIT_KIND_EXPECTATION_VIOLATED,
)
类型触发时机常见字段
AUDIT_KIND_AGENT_REGISTERED
hc.register(...)
agent_id
、
name
、
owner
AUDIT_KIND_AGENT_UNREGISTERED
hc.unregister(agent_id)
agent_id
AUDIT_KIND_RESUME_SET
hub.set_resume(...)
来源:
RESUME_SOURCE_TENANT
或
RESUME_SOURCE_OBSERVED
AUDIT_KIND_SKILL_SET
hub.set_skill(...)
更新后的技能markdown
AUDIT_KIND_RULE_SET
hub.set_rule(...)
新的
Rule
AUDIT_KIND_CHANNEL_CREATED
alice.open(...)
creator_id
、清单类型/版本、参与者
AUDIT_KIND_CHANNEL_CLOSED
任何关闭路径
reason
AUDIT_KIND_CHANNEL_EXPIRED
TTL清理任务TTL详情
AUDIT_KIND_TASK_TERMINATED
agent.task(...)
通过
TaskMirror
进入终端状态
owner_id
、
capability
、
outcome
、
latency_ms
AUDIT_KIND_EXPECTATION_VIOLATED
预期评估器的阈值时间到
expectation
、
channel_id
、评估器详情

Common queries

常见查询

python
undefined
python
undefined

All violations on the system.

系统中所有违规记录。

violations = [r for r in await hub.audit_log.read_all() if r["kind"] == AUDIT_KIND_EXPECTATION_VIOLATED]
violations = [r for r in await hub.audit_log.read_all() if r["kind"] == AUDIT_KIND_EXPECTATION_VIOLATED]

Everything that happened on one channel.

某一通道的所有事件。

channel_records = [r for r in await hub.audit_log.read_all() if r.get("channel_id") == channel_id]
channel_records = [r for r in await hub.audit_log.read_all() if r.get("channel_id") == channel_id]

All registrations for one tenant.

某一租户的所有注册记录。

acme_agents = [r for r in await hub.audit_log.read_all() if r["kind"] == AUDIT_KIND_AGENT_REGISTERED and r.get("owner") == "acme"]

The audit log is **durable when backed by `DiskKnowledgeStore`**; with `MemoryKnowledgeStore` it lives only as long as the hub.
acme_agents = [r for r in await hub.audit_log.read_all() if r["kind"] == AUDIT_KIND_AGENT_REGISTERED and r.get("owner") == "acme"]

当使用`DiskKnowledgeStore`作为后端时,审计日志是**持久化**的;使用`MemoryKnowledgeStore`时,审计日志仅在中心节点运行期间存在。

Hub listeners — live programmatic observability

中心节点监听器——实时可编程可观测性

The audit log is the durable record. For live reactions to hub state changes — push to a metrics backend, stream to a dashboard, alert an on-call — register a
HubListener
: a read-only Protocol the hub fans out to after every state transition has committed. (The built-in audit log is itself one of these listeners.)
Method (exact signature)Fires when
on_envelope_posted(envelope, metadata)
an envelope was accepted, WAL-appended, folded, and dispatched
on_envelope_rejected(envelope, reason)
the arbiter / validation denied a send (
reason
is the typed
NetworkError
)
on_dispatch_failed(envelope, recipient_id, reason)
delivery to one recipient raised (
reason
is a
BaseException
)
on_channel_event(channel_id, kind, payload)
kind
∈
opened
/
closed
/
expired
/
participant_removed
/
participant_hidden
on_agent_event(agent_id, kind, payload)
kind
∈
registered
/
unregistered
/
resume_set
/
skill_set
/
rule_set
/
observation_recorded
on_expectation_fired(channel_id, expectation, violation)
an expectation evaluator emitted a
Violation
on_turn_failed(channel_id, agent_id, envelope_id, exc)
an agent's notify-handler turn raised (the default handler routes failures here)
on_task_event(task_id, kind, payload)
a
ag2.task.*
lifecycle event was observed (
kind
∈
started
/
progress
/
completed
/
failed
/
expired
/
cancelled
/
mirror_failed
)
on_inbox_pressure(agent_id, pending, cap)
a recipient's inbox first crosses
limits.inbox.high_water
(fires once per crossing, not per envelope)
All methods are
async
; the hub awaits them sequentially in registration order, each wrapped in
try/except
— a buggy listener can't stall dispatch. Keep them fast (queue I/O onto your own task). Subclass
BaseHubListener
(every method is a
pass
) and override only what you need:
python
from ag2.network import BaseHubListener

class MetricsListener(BaseHubListener):
    async def on_envelope_posted(self, envelope, metadata):
        statsd.incr(f"net.envelope.{envelope.event_type}")
    async def on_inbox_pressure(self, agent_id, pending, cap):
        statsd.gauge(f"net.inbox.{agent_id}", pending / cap)
    async def on_turn_failed(self, channel_id, agent_id, envelope_id, exc):
        sentry.capture_exception(exc)

hub.register_listener(MetricsListener())     # hub.unregister_listener(inst) to detach
Two related hub-subclass seams:
  • on_*
    hooks on
    Hub
    itself
    — the same method set exists as empty methods on
    Hub
    ; a
    Hub
    subclass can override them directly (the fan-out invokes the bound method alongside registered listeners). Use a subclass when the observability is the hub variant you're shipping; use
    register_listener
    for pluggable add-ons.
  • hub.register_sweeper(name, interval_seconds, fn)
    /
    unregister_sweeper(name)
    — adds your own periodic coroutine to the hub's interval-sweeper machinery (alongside the built-in TTL and expectation sweepers). Subclass-registered sweepers start immediately if
    Hub.start()
    has already run, otherwise queue until it does.
on_inbox_pressure
is governed by
limits.inbox.high_water
(an
InboxBlock
field) — an absolute pending-count threshold (
int | None
).
None
(the default) auto-resolves to
int(limits.inbox.max_pending * 0.8)
;
0
disables the signal. It's the early-warning sibling of the hard
InboxFull
(the cap is
limits.inbox.max_pending
, enforced by the arbiter's
authorize_inbox
).
审计日志是持久化记录。对于中心节点状态变更的实时响应——推送至指标后端、流式传输至仪表盘、告警值班人员——请注册**
HubListener
**:一个只读协议,中心节点在每次状态变更提交后会广播给所有注册的监听器。(内置审计日志本身就是其中一个监听器。)
方法(精确签名)触发时机
on_envelope_posted(envelope, metadata)
信封被接受、写入WAL、折叠并分发后
on_envelope_rejected(envelope, reason)
仲裁器/验证拒绝发送(
reason
是类型化的
NetworkError
)
on_dispatch_failed(envelope, recipient_id, reason)
向某一收件人分发消息失败(
reason
是
BaseException
)
on_channel_event(channel_id, kind, payload)
kind
∈
opened
/
closed
/
expired
/
participant_removed
/
participant_hidden
on_agent_event(agent_id, kind, payload)
kind
∈
registered
/
unregistered
/
resume_set
/
skill_set
/
rule_set
/
observation_recorded
on_expectation_fired(channel_id, expectation, violation)
预期评估器触发
Violation
后
on_turn_failed(channel_id, agent_id, envelope_id, exc)
智能体的通知处理程序执行失败(默认处理程序将失败路由至此)
on_task_event(task_id, kind, payload)
观测到
ag2.task.*
生命周期事件(
kind
∈
started
/
progress
/
completed
/
failed
/
expired
/
cancelled
/
mirror_failed
)
on_inbox_pressure(agent_id, pending, cap)
收件人收件箱首次超过
limits.inbox.high_water
阈值(每次跨越触发一次,而非每个信封触发一次)
所有方法均为
async
;中心节点按注册顺序依次等待执行,每个方法都包裹在
try/except
中——有bug的监听器不会阻塞分发。请保持方法执行快速(将I/O任务放入自己的队列)。继承
BaseHubListener
(所有方法默认
pass
)并仅覆盖需要的方法:
python
from ag2.network import BaseHubListener

class MetricsListener(BaseHubListener):
    async def on_envelope_posted(self, envelope, metadata):
        statsd.incr(f"net.envelope.{envelope.event_type}")
    async def on_inbox_pressure(self, agent_id, pending, cap):
        statsd.gauge(f"net.inbox.{agent_id}", pending / cap)
    async def on_turn_failed(self, channel_id, agent_id, envelope_id, exc):
        sentry.capture_exception(exc)

hub.register_listener(MetricsListener())     # 使用hub.unregister_listener(inst)移除
两个相关的中心节点子类接口:
  • Hub
    自身的
    on_*
    钩子
    ——
    Hub
    类本身也有一组空方法;
    Hub
    子类可直接覆盖这些方法(广播时会同时调用绑定的方法和注册的监听器)。当可观测性是你要发布的中心节点变体时,使用子类;对于可插拔的附加功能,使用
    register_listener
    。
  • hub.register_sweeper(name, interval_seconds, fn)
    /
    unregister_sweeper(name)
    ——将自定义的周期性协程添加到中心节点的定时清理机制中(与内置的TTL和预期清理任务一起运行)。如果
    Hub.start()
    已运行,子类注册的清理任务会立即启动;否则会排队等待启动。
on_inbox_pressure
由
limits.inbox.high_water
(
InboxBlock
字段)管控——一个绝对的待处理消息数阈值(
int | None
)。
None
(默认)会自动解析为
int(limits.inbox.max_pending * 0.8)
;
0
表示禁用该信号。它是硬限制
InboxFull
(上限为
limits.inbox.max_pending
,由仲裁器的
authorize_inbox
强制执行)的预警机制。

Task observation — building the track record

任务观测——构建任务记录

Capability-tagged tasks update an agent's
Resume.observed[capability]
automatically. This is how the network knows that "bob has completed 47 research tasks at a 4.2s median latency."
带有能力标签的任务会自动更新智能体的
Resume.observed[capability]
。这就是网络如何知道"bob已完成47个研究任务,中位延迟为4.2秒"的方式。

Tagging a task

为任务添加标签

agent.task(..., capability="X")
accepts a free-form capability string:
python
undefined
agent.task(..., capability="X")
接受自由格式的能力字符串:
python
undefined

.tool
and
.task(...)
live on
Agent
, not on the
AgentClient
returned

.tool
和
.task(...)
属于
Agent
,而非
hc.register(...)
返回的
AgentClient
。因此请在注册前装饰Agent。

by
hc.register(...)
. So decorate the Agent before registering.

—
@worker_agent.tool async def research(topic: str, ctx: Context) -> str: async with worker_agent.task( f"research: {topic}", capability="research", context=ctx, ) as task: await task.progress({"step": "gather"}) # ... do work ... await task.complete({"items_found": 7}) return "researched"
worker = await worker_hc.register(worker_agent, Passport(name="worker"), Resume())

Pass `context=ctx` so the task fires its events on the LLM-turn's stream — that's the stream the `TaskMirror` is attached to. Without it, the events never reach the hub.

If `capability=None` (the default), lifecycle events still go to the hub's audit log but `Resume.observed` is **not** updated. Track record is opt-in.
@worker_agent.tool async def research(topic: str, ctx: Context) -> str: async with worker_agent.task( f"research: {topic}", capability="research", context=ctx, ) as task: await task.progress({"step": "gather"}) # ... 执行任务 ... await task.complete({"items_found": 7}) return "researched"
worker = await worker_hc.register(worker_agent, Passport(name="worker"), Resume())

传入`context=ctx`,使任务在LLM轮次的流上触发事件——`TaskMirror`会监听该流。如果不传入,事件将无法到达中心节点。

如果`capability=None`(默认),生命周期事件仍会写入中心节点的审计日志,但`Resume.observed`**不会**更新。任务记录是可选的。

Reading the track record

读取任务记录

python
resume = await hub.get_resume(bob.agent_id)
stat = resume.observed.get("research")
if stat:
    print(f"completed={stat.completed}/{stat.n}  "
          f"failed={stat.failed}  "
          f"p50_latency={stat.p50_latency_ms}ms")
python
@dataclass(slots=True)
class ObservedStat:
    n: int = 0                        # total terminal events
    completed: int = 0
    failed: int = 0
    expired: int = 0
    p50_latency_ms: int | None = None  # rolling median of started_at → completed_at
Latency is computed from
task_meta.started_at
to the terminal event time, using the hub's clock. With a
MockClock
in tests you can construct deterministic latencies.
python
resume = await hub.get_resume(bob.agent_id)
stat = resume.observed.get("research")
if stat:
    print(f"completed={stat.completed}/{stat.n}  "
          f"failed={stat.failed}  "
          f"p50_latency={stat.p50_latency_ms}ms")
python
@dataclass(slots=True)
class ObservedStat:
    n: int = 0                        # 终端事件总数
    completed: int = 0
    failed: int = 0
    expired: int = 0
    p50_latency_ms: int | None = None  # 从started_at到completed_at的滚动中位值
延迟由中心节点时钟计算,从
task_meta.started_at
到终端事件时间。测试时使用
MockClock
可构造确定性的延迟值。

Where
TaskMirror
fits

TaskMirror
的作用

The default handler auto-attaches a
TaskMirror
per turn, scoped to the active channel. The mirror subscribes to
TaskStarted
/
TaskProgress
/
TaskCompleted
/
TaskFailed
/
TaskExpired
events on the LLM-turn's stream, forwards each as an
ag2.task.*
envelope to the hub, and on terminal events with a
capability
tag calls
Hub.record_observation(...)
to update
Resume.observed
.
You only attach
TaskMirror
manually if you've written a custom handler:
python
from ag2.network import TaskMirror
from ag2.stream import MemoryStream

mirror = TaskMirror(
    hub_client=client._hub_client,
    owner_id=client.agent_id,
    channel_id=metadata.channel_id,
)
stream = MemoryStream()
sub_ids = mirror.attach(stream)
try:
    await client.agent.ask(text, stream=stream)
finally:
    mirror.detach(stream, sub_ids)
The mirror is attached per turn, not per agent — a new one for each inbound envelope. It also swallows hub-forwarding errors silently; a flaky hub connection should not crash the LLM turn.
默认处理程序会为每个轮次自动附加一个
TaskMirror
,作用域为当前活跃通道。该镜像会监听LLM轮次流上的
TaskStarted
/
TaskProgress
/
TaskCompleted
/
TaskFailed
/
TaskExpired
事件,将每个事件转发为
ag2.task.*
信封至中心节点,并在带有
capability
标签的终端事件触发时调用
Hub.record_observation(...)
以更新
Resume.observed
。
仅当你编写了自定义处理程序时,才需要手动附加
TaskMirror
:
python
from ag2.network import TaskMirror
from ag2.stream import MemoryStream

mirror = TaskMirror(
    hub_client=client._hub_client,
    owner_id=client.agent_id,
    channel_id=metadata.channel_id,
)
stream = MemoryStream()
sub_ids = mirror.attach(stream)
try:
    await client.agent.ask(text, stream=stream)
finally:
    mirror.detach(stream, sub_ids)
镜像是每个轮次附加一次,而非每个智能体附加一次——每个入站信封对应一个新镜像。它还会静默忽略中心节点转发错误;不稳定的中心节点连接不应导致LLM轮次崩溃。

When to skip the capability tag

何时跳过能力标签

Tag only when:
  • The task represents a capability you want to track in the agent's resume.
  • Failure / latency signals are operationally meaningful (driving routing, alerting, peer ranking).
Untagged tasks still get full lifecycle audit records — just no
Resume.observed
update. Use untagged tasks for internal book-keeping that doesn't represent an externally-visible capability.
仅在以下情况添加标签:
  • 任务代表你想要在智能体简历中跟踪的能力。
  • 失败/延迟信号具有运营意义(用于驱动路由、告警、peer排名)。
未标记的任务仍会生成完整的生命周期审计记录——只是不会更新
Resume.observed
。对于不代表外部可见能力的内部记账任务,使用未标记任务。

Cross-cutting pattern: multi-capability worker

跨领域模式:多能力智能体

python
@worker_agent.tool
async def research(topic: str, ctx: Context) -> str:
    async with worker_agent.task(f"research: {topic}", capability="research", context=ctx) as t:
        # ...
    return "..."


@worker_agent.tool
async def summarise(text: str, ctx: Context) -> str:
    async with worker_agent.task("summarise", capability="summarisation", context=ctx) as t:
        # ...
    return "..."
After a few channels,
worker.resume.observed
holds both
"research"
and
"summarisation"
ObservedStat
s, each tracked independently. A peer-discovery query (
peers(action="find", capability="research")
— see
ag2-network-tools-and-views
) can then rank by latency or completion rate.
python
@worker_agent.tool
async def research(topic: str, ctx: Context) -> str:
    async with worker_agent.task(f"research: {topic}", capability="research", context=ctx) as t:
        # ...
    return "..."


@worker_agent.tool
async def summarise(text: str, ctx: Context) -> str:
    async with worker_agent.task("summarise", capability="summarisation", context=ctx) as t:
        # ...
    return "..."
经过几个通道后,
worker.resume.observed
会包含
"research"
和
"summarisation"
的
ObservedStat
,各自独立跟踪。peer发现查询(
peers(action="find", capability="research")
——见
ag2-network-tools-and-views
)可根据延迟或完成率进行排名。

Reading hub state

读取中心节点状态

CallReturns
await hub.get_channel(channel_id)
ChannelMetadata
snapshot (state, participants, close_reason)
await hub.get_resume(agent_id)
Current
Resume
(including
observed
)
await hub.get_passport(agent_id)
Current
Passport
await hub.list_agents(kind=None)
Registered passports;
kind="agent"
/
"human"
/
"remote_agent"
filters by
Passport.kind
await hub.read_wal(channel_id)
Ordered list of
Envelope
s in that channel
await hub.audit_log.read_all()
Every audit record
hub.arbiter
The active
HubArbiter
(read-only)
The hub stamps
Resume.last_updated
on every mutation, so you can detect stale views by comparing timestamps. For push (vs. these pull calls), register a
HubListener
.
调用返回值
await hub.get_channel(channel_id)
ChannelMetadata
快照(状态、参与者、关闭原因)
await hub.get_resume(agent_id)
当前
Resume
(包含
observed
)
await hub.get_passport(agent_id)
当前
Passport
await hub.list_agents(kind=None)
已注册的护照;
kind="agent"
/
"human"
/
"remote_agent"
按
Passport.kind
过滤
await hub.read_wal(channel_id)
该通道中有序的
Envelope
列表
await hub.audit_log.read_all()
所有审计记录
hub.arbiter
活跃的
HubArbiter
(只读)
中心节点会在每次变更时标记
Resume.last_updated
,因此你可通过比较时间戳检测视图是否过期。如需推送(而非这些拉取调用),请注册
HubListener
。

Quick reference — imports

快速参考——导入

python
from ag2.network import (
    # Identity
    Passport, Resume, ResumeExample, ObservedStat,
    # Rules
    Rule, AccessBlock, LimitsBlock, RateBlock, InboxBlock,
    ChannelTypeAccess, parse_duration,
    # Arbiter (swappable access / routing seam)
    HubArbiter, BaseHubArbiter, RuleBasedArbiter, Allow, Deny,
    # Listeners (live observability)  — hub.register_listener(...)
    HubListener, BaseHubListener,
    # Auth
    AuthAdapter, AuthRegistry, AuthBlock, NoAuth, ApiKeyAuth,
    # Expectations
    Expectation,
    ExpectationEvaluator, ExpectationContext,
    AcksWithinEvaluator, ReplyWithinEvaluator, MaxSilenceEvaluator,
    AuditHandler, NotifyChannelHandler, AutoCloseHandler,
    Violation, ViolationHandler,
    default_evaluators, default_handlers,
    # Audit kinds
    AUDIT_KIND_AGENT_REGISTERED,
    AUDIT_KIND_AGENT_UNREGISTERED,
    AUDIT_KIND_RESUME_SET,
    AUDIT_KIND_SKILL_SET,
    AUDIT_KIND_RULE_SET,
    AUDIT_KIND_CHANNEL_CREATED,
    AUDIT_KIND_CHANNEL_CLOSED,
    AUDIT_KIND_CHANNEL_EXPIRED,
    AUDIT_KIND_TASK_TERMINATED,
    AUDIT_KIND_EXPECTATION_VIOLATED,
    RESUME_SOURCE_OBSERVED, RESUME_SOURCE_TENANT,
    # Task observation
    TaskMirror,
    # Errors — the full family (no `LimitsExceeded`; limit/access denials raise AccessDeniedError, inbox-full raises InboxFull)
    NetworkError, AccessDeniedError, AuthError, InboxFull, NotFoundError, ProtocolError,
)
python
from ag2.network import (
    # 身份标识
    Passport, Resume, ResumeExample, ObservedStat,
    # 规则
    Rule, AccessBlock, LimitsBlock, RateBlock, InboxBlock,
    ChannelTypeAccess, parse_duration,
    # 仲裁器(可替换的访问/路由接口)
    HubArbiter, BaseHubArbiter, RuleBasedArbiter, Allow, Deny,
    # 监听器(实时可观测性)——hub.register_listener(...)
    HubListener, BaseHubListener,
    # 身份验证
    AuthAdapter, AuthRegistry, AuthBlock, NoAuth, ApiKeyAuth,
    # 预期配置
    Expectation,
    ExpectationEvaluator, ExpectationContext,
    AcksWithinEvaluator, ReplyWithinEvaluator, MaxSilenceEvaluator,
    AuditHandler, NotifyChannelHandler, AutoCloseHandler,
    Violation, ViolationHandler,
    default_evaluators, default_handlers,
    # 审计类型
    AUDIT_KIND_AGENT_REGISTERED,
    AUDIT_KIND_AGENT_UNREGISTERED,
    AUDIT_KIND_RESUME_SET,
    AUDIT_KIND_SKILL_SET,
    AUDIT_KIND_RULE_SET,
    AUDIT_KIND_CHANNEL_CREATED,
    AUDIT_KIND_CHANNEL_CLOSED,
    AUDIT_KIND_CHANNEL_EXPIRED,
    AUDIT_KIND_TASK_TERMINATED,
    AUDIT_KIND_EXPECTATION_VIOLATED,
    RESUME_SOURCE_OBSERVED, RESUME_SOURCE_TENANT,
    # 任务观测
    TaskMirror,
    # 错误——完整的错误家族(无`LimitsExceeded`;限制/访问拒绝抛出AccessDeniedError,收件箱满抛出InboxFull)
    NetworkError, AccessDeniedError, AuthError, InboxFull, NotFoundError, ProtocolError,
)