mcpField
A
ag2-network-tools-and-views

ag2-network-tools-and-views

github.com/ag2aiエージェントワークフロー1261 回インストール13 週間前に更新
リポジトリ

ag2-network-tools-and-views について

AG2 Network のクライアント側で、エージェントや非 LLM 参加者がネットワークツールを使い、チャネル内容を参照してエンベロープを扱う方法を説明するスキルです。カスタムハンドラー、ゲートウェイ、人間とのブリッジを作る開発者向けに、プラグインツールとアダプターツール、HumanClient、ビュー方針を解説します。

主な機能

  1. 015 種類のネットワークプラグインツール
  2. 02アダプター提供のチャネルツール
  3. 03HumanClient のエンベロープ送受信
  4. 04標準エンベロープハンドラーの差し替え
  5. 05チャネル履歴のカスタムビュー
  6. 06エンベロープとイベント型のリファレンス

活用シーン

  1. 01エージェントのネットワークツールを制限・拡張
  2. 02LLM を使わないゲートウェイやワーカーの構築
  3. 03人や外部システムをチャネルに接続
  4. 04LLM に渡す履歴のフィルタリングや墨消し

MCP 対応

このスキルには MCP との関係について記載がありません。

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/ag2ai/ag2-skills --skill ag2-network-tools-and-views

インストール後、Claude Code で/ag2-network-tools-and-viewsを入力して読み込まれたか確認します。

詳細

AG2 Network — ツール、ビュー、カスタムハンドラー

ネットワークのエージェント/クライアント側にあるすべての機能です。ハブ側の ag2-network-governance を鏡に映したものです。チャネルについて LLM が何を認識するか(ビュー)、どのアクションを実行できるか(自動挿入される5つのプラグインツールと、アダプターが提供する say)、他のエージェントがチャネルについて何を把握しているか(スキルの Markdown)、ハンドラー全体を置き換える方法、そして Envelope の完全なリファレンスを扱います。

前提条件:まず ag2-network-quickstart をお読みください。このスキルでは、Hub.open、HubClient.register、チャネルのライフサイクル、基本的な agent_client.open(...)/channel.send(...) を理解していることを前提としています。

使用する場面

  • 「LLM が使えるネットワークツールの範囲を制限/拡張する」(プラグインツールとアダプターの tools_for)
  • 「カスタムエンベロープハンドラーを書く」
  • 「LLM を実行しないゲートウェイ/ヘッドレスワーカーを構築する」
  • 「LLM 以外の参加者を追加する — UI を操作する人、ブリッジ、スクリプトで動作するシーダー」(HumanClient)
  • 「各エージェントに表示するチャネル履歴をカスタマイズする」(ビューのポリシー)
  • 「LLM に届く前にエンベロープを除去/編集/フィルタリングする」
  • 「スキルの Markdown を使ってピアの検出を設定する」
  • 「カスタムイベントタイプを送信する(myapp.review_request など)」
  • 「EV_* 定数の一覧/Envelope の構造/audience のセマンティクスが必要」

ネットワークツール — プラグインツールとアダプターツール

エージェントの各ターンで使う LLM ツールの一覧は、2つの経路から組み立てられます。

  1. プラグインツール — デフォルトの attach_plugin=True で登録すると、NetworkPlugin が agent.tools に、ID レベルでチャネルに依存しない5つの動詞を追加します:peers/channels/tasks/context/delegate。どのチャネルでも同じ動作をします。
  2. アダプターツール — チャネルのアダプターは、adapter.tools_for(client, metadata, state, participant_id) を通じて、ターンごとにチャネル固有のツールを提供します。デフォルトハンドラーがこれらを解決し、agent.ask(tools=...) にマージします((adapter, client) ごとにキャッシュされるため、スキーマの構築コストは一度だけ発生します)。組み込みのアダプターツールは say のみです。

つまり、say はプラグインツールではありません。ターンの状態に応じてアダプターが提供します。

アダプターsay を提供する相手
consulting1Q1R で自分のターンを迎えている参加者(プロンプト前は開始者、返信前は応答者)
conversation常にすべての参加者
discussionexpected_next_speaker のみ
workflow誰にも提供されません — ルーティングには @tool のハンドオフ関数を使います。アダプターは [] を返します

そのため、attach_plugin=False で say が LLM に表示されるかどうかを制御することはできなくなりました。この設定が制御するのは、5つのプラグイン動詞のみです。(ユーザー向けの概要は ag2-network-quickstart →「プラグインツールとアダプターツール」を参照してください。)

delegate と say
ツール経路シグネチャ目的
delegateプラグインdelegate(target, prompt, capability?, timeout=300.0)1回限りの相談です。target との consulting チャネルを開き、prompt を送信して、単一の返信を待ち、そのテキストを返します。別のチャネルを使うため、どのチャネルでもターンの途中で安全に呼び出せます。
sayアダプター(consulting/conversation/discussion)say(content, audience?, channel_id?)アクティブなチャネル(またはエージェントが参加している指定のチャネル)に EV_TEXT を投稿します。audience はピアの名前のリスト(ID に解決されます)です。None の場合はブロードキャストします。エンベロープの構造は adapter.build_text_envelope(...) から取得します。これは AG2 を使わないブリッジが呼び出す Layer-2 ヘルパーと同じものです。
# The LLM emits, e.g.:
delegate(target="bob", prompt="What's the right way to model X?", capability="modeling")
say(content="Here's my answer: …")

フレームワークは、通知ハンドラー内で ChannelInject(現在のチャネル)、ChannelStateInject、AgentClientInject を自動的に解決します。LLM にこれらのパラメーターが表示されることはありません。

4 つのアクションをグループ化したディスパッチツール

各ツールは action のリテラルと、アクション固有の引数を受け取るため、LLM のツールリストを短く保てます。

peers(action) — 検出

アクション引数戻り値
"find"query?, capability?, sort_by?, limit=20ピアの概要リスト(呼び出し元は含みません)。
"describe"name1 つのピアの完全なプロフィール:{passport, resume, skill_md}。SKILL.md が登録されていない場合、skill_md はレンダリングした passport と resume にフォールバックします。

channels(action) — ライフサイクル

アクション引数戻り値
"list"state="active"|"all"このエージェントが参加しているチャネル。
"open"type, target, knobs?, intent?, ttl?, message?agent_client.open と同様に動作します。message が指定されている場合、チャネルが OPENED に達した直後、開始者に代わって最初のエンベロープとして送信されます(consulting/workflow のシードに便利です)。シードに失敗すると、理由 seed_failed でチャネルを閉じます。{channel_id, type, participants[, seed_envelope_id]} を返します。
"info"channel_idエージェントが参加している場合、完全な ChannelMetadata を返します。
"close"channel_id?(デフォルトは現在のチャネル)理由 "closed_by_agent" で閉じます。

tasks(action) — タスクのライフサイクル

2 つの区分があります。アクティブアクション(エージェント自身の agent.task(...) ブロック内にいる場合)と、観測アクション(ハブが認識している任意のタスク)です。

アクション区分引数戻り値
"progress"アクティブpayloadTaskProgress を送信します。
"complete"アクティブresult?終端アクションです。TaskCompleted を送信します。
"list"観測scope="own"|"all", state="active"|"all", limit=20タスクの概要。
"status"観測task_id更新された TaskMetadata。
"wait"観測task_id, timeout=300, poll_interval=0.1タスクが終端状態になるまでブロックします。

"start" は意図的にツールではありません。LLM から呼び出すと、TaskInject を適切にスコープする async with agent.task(...) のライフサイクルを迂回してしまいます。タスクはオーナーが自身のコード内で開始します。LLM はタスクがアクティブになった後に progress/complete を使用し、単発のリモート作業には delegate を使用します。

context(action) — 過去のコンテンツ

アクション引数戻り値
"search"query, scope="channel"|"knowledge", limit=10テキストに query が含まれる抜粋(大文字と小文字を区別しない部分文字列検索)。
"quote"speaker, recent_n=1, channel_id?speaker からの直近 recent_n 件の EV_TEXT エンベロープ。

scope="knowledge" は、呼び出し元エージェント自身の KnowledgeStore を検索します(部分文字列検索のみです。ベクトル検索/セマンティック検索には、エージェント自身のループから framework-core の recall を直接呼び出してください)。

アダプター所有のツール(tools_for) — そして say の二重送信

デフォルトハンドラーは各ターンで adapter.tools_for(client, metadata, state, participant_id) を解決し、その結果をエージェント独自の @tool やプラグインツールと合わせて agent.ask(tools=...) にマージします。LLM からの入力を受け取らないアダプター(workflow)は [] を返します。それ以外のアダプターは、ターンの状態(上の表を参照)に応じて say を返します。アダプターは、さらに豊富なツールを返すこともできます。組み込みのツールは say だけです。

workflow では、ワークフローエージェントが say を受け取ることはありません。「何かを発言する」手段は、ハンドラーが reply.body から構築するラウンド終了時の EV_PACKET と、作成した @tool のハンドオフ関数だけです。以前の「approve() の後に say(...) を呼び出すと、ラウンド終了時の EV_PACKET と競合する」という失敗は、構造上起こり得なくなりました。

consulting/conversation/discussion では say が提供されますが、使う必要はほとんどありません。デフォルトハンドラーはすでにラウンド終了時のエンベロープを投稿するため(build_round_envelope → EV_TEXT(reply.body)、空の場合は None)、テキストで返信するだけでエージェントは発言済みです。say は、ターン内で 追加の メッセージを投稿する場合や、エージェントが参加している 別の チャネルに投稿する場合(channel_id=)に使います。

注意点:エージェントが say(content="…") を呼び出した うえで、空でない返信本文も返すと、1 ターン内に実質的なエンベロープが 2 つ送信されます。conversation では問題ありません。consulting では、2 つ目のエンベロープによって厳密な 1Q1R アダプターで ProtocolError: channel '<id>' is closed が発生します。まず最も確実な対策から、以下を検討してください。

  • エージェントに say を呼び出すよう指示しないでください。 通常の返信がチャネルへの正式な返信です。二重送信は、say を使うよう明示的に促した場合にのみ発生します。
  • デフォルトハンドラーを置き換えて、ラウンド終了時のエンベロープも送信しないようにします。そうすれば、say がエージェントの発言手段になります(以下のゲートウェイ/ヘッドレスワーカーパターンを参照)。
  • attach_plugin=False にしても、ここでは役に立ちません。say はプラグインではなくアダプターが所有するためです。これによって除外されるのは delegate/peers/channels/tasks/context だけです。say を抑止するためではなく、ツールを持たないエージェントにするために使用してください。

LLM を使用しない参加者 — HumanClient

HumanClient は、フレームワークにおける「Agent ではない参加者」の第一級の存在です。LLM も、NetworkPlugin も、アセンブリポリシーもありません。UI 上の人、別のシステムへのブリッジ、ワークフローを開始するスクリプト化された「ユーザー」などは、すべて HumanClient です。register(現在は kind="human" を拒否してこちらを案内します)ではなく、register_human で登録します。

from ag2.network import HumanClient, Passport

user = await hc.register_human(Passport(name="user", kind="human"))   # resume=, rule=, auto_ack_invites= optional

NetworkClient Protocol を満たしており、AgentClient と同じ送信インターフェースを備えています。

ch = await user.open(type="consulting", target="analyst")     # initiate a channel
await user.send(ch.channel_id, "What's our Q3 exposure?")     # EV_TEXT convenience
await user.post_envelope(env)                                 # escape hatch — adapter-shaped envelopes (e.g. workflow EV_PACKET via adapter.build_packet_envelope)

…さらに、受信エンベロープを利用する方法が 2 つあります(AgentClient が備えるのは通知ハンドラーのコールバックのみですが、HumanClient には明示的なキューが追加されています)。

インターフェース呼び出し動作
プッシュuser.on_envelope(callback)受信エンベロープごとに async コールバックが 1 回呼び出されます。複数のコールバックは登録順に連結されます。例外を発生させたコールバックはログに記録されますが、例外は伝播しません。切り離すには remove_envelope_callback(cb) を使います。
プルawait user.next_envelope(*, predicate=None, timeout=None)predicate に一致する次のエンベロープ(None の場合は任意のエンベロープ)を受信するまでブロックします。timeout に達すると例外を発生させます。
プル(ストリーム)async for env in user.envelopes(): ...user.disconnect() まで、受信するすべてのエンベロープを返します。

auto_ack_invites=True(デフォルト)にすると、人間がチャネルへの招待を自動承認するため、UI を介さずにハブの定足数ハンドシェイクが完了します。参加を手動で制御するには auto_ack_invites=False を渡してください(その場合は自分で post_envelope を使って EV_CHANNEL_INVITE_ACK を送信します)。hub.list_agents(kind="human") で人間を検出できます。hub.list_agents(kind="agent")/kind="remote_agent" では、それ以外を絞り込めます。

典型的な役割としては、workflow チャネルの開始役(FromSpeaker(user) → AgentTarget(first_agent)。ag2-network-workflow を参照)、consulting の Q&A における人間側の参加者、discussion のラウンドロビンに参加する人、またはこれらの前段に置く WebSocket/CLI ブリッジなどがあります。ヘッドレスエージェント(LLM は実行しないものの、プラグインツールや tools_for による解決を引き続き必要とする AgentClient)の場合は、通知ハンドラーを置き換えます。次の項を参照してください。

デフォルトハンドラーの置き換え

デフォルトハンドラーは、「エージェントがエンベロープを受信 → 招待を自動承認 → LLM を実行(adapter.tools_for(...) をマージ)→ ラウンド終了エンベロープを送信」という一連の処理をすべて担います。ヘッドレスワーカー、ゲートウェイ、または LLM を実行すべきでないエージェントでは、これを置き換えてください。(そもそも LLM を使用する想定のない参加者には、カスタムハンドラーを設定した AgentClient ではなく、上記の HumanClient を使用してください。)

プラグイン(および/またはデフォルトハンドラー)を無効にする
worker = await hc.register(agent, passport, resume, attach_plugin=False)  # no peers/channels/tasks/context/delegate
worker.on_envelope(my_custom_handler)                                     # ← this is what swaps the handler

これらは独立した2つの設定です。

  • attach_plugin=False にすると、NetworkPlugin.register(agent) がスキップされ、エージェントのツールリストから peers/channels/tasks/context/delegate(および NetworkContextPolicy のプレフィックス)がなくなります。通知ハンドラーには影響せず、say も削除されません(これはアダプターが管理します。上記を参照してください)。
  • デフォルトの通知ハンドラーは、デフォルトで有効です(プラグインではなく AgentClient によって設定されます。ただし、attach_default_handler=False を指定して登録した場合を除きます)。client.on_envelope(callback) を呼び出すと置き換わります。復元するには client.on_envelope(client._run_default_handler) を呼び出してください。

client.on_envelope(callback) を呼び出すと失われるもの:

デフォルトハンドラーの処理…代わりの処理を実装しない場合…
EV_CHANNEL_INVITE を自動承認する(EV_CHANNEL_INVITE_ACK を投稿する)チャンネルは invite_ack_timeout(30秒)まで PENDING 状態のままとなり、ハブによって(理由 invite_timeout で)閉じられます
EV_TEXT/EV_PACKET で _process_substantive を実行する:WAL を読み取り、ビューを投影し、依存関係を設定し、TaskMirror をアタッチし、adapter.tools_for(...) を解決し、agent.ask(...) を呼び出し、adapter.build_round_envelope で構築したラウンド終了エンベロープを投稿するLLM は実行されず、返信も戻らないため、期待値が発火するまでチャンネルが停滞します
ag2.channel.*/ag2.task.* のライフサイクルエンベロープでは何もしない(スキップしても問題ありませんが、これらが届いたときに驚くかもしれません)

多くの処理を手軽にそのまま活用する方法は、必要なイベントを自分で処理し、それ以外は default_handler に委譲することです。

ハンドラーのシグネチャ。 client.on_envelope(...) に渡すコールバックは (envelope,) のみを引数として呼び出されます。フレームワークは await self._on_envelope(envelope) を実行します。ハンドラー内の client 参照は、外側のスコープから(クロージャ変数として)取得します。一方、エクスポートされている default_handler のシグネチャは (envelope, client) です。委譲する際は両方を渡してください。

ゲートウェイハンドラー
from ag2.network import Envelope, EV_TEXT, default_handler


async def gateway_handler(envelope: Envelope) -> None:
    """Forward inbound text to an external system instead of running an LLM."""
    if envelope.event_type != EV_TEXT:
        await default_handler(envelope, client)   # auto-ack invites etc. (client from closure)
        return
    text = envelope.event_data.get("text", "")
    await my_external_queue.put({
        "from": envelope.sender_id,
        "text": text,
        "channel": envelope.channel_id,
    })


client.on_envelope(gateway_handler)
選択的なオーバーライド(デフォルトにフォールバック)
from ag2.network import default_handler, EV_CHANNEL_INVITE


async def selective_handler(envelope: Envelope) -> None:
    if envelope.event_type == EV_CHANNEL_INVITE and envelope.sender_id not in TRUSTED_AGENTS:
        return   # untrusted invite → silent drop; hub will time out and close
    await default_handler(envelope, client)   # default for everything else


client.on_envelope(selective_handler)
フィルター付き転送(前後のフック)
async def logged_handler(envelope: Envelope) -> None:
    log.info("inbound %s from %s", envelope.event_type, envelope.sender_id)
    try:
        await default_handler(envelope, client)
    finally:
        log.info("processed %s", envelope.envelope_id)
アダプターツールを迂回する

アダプターは各ターンで tools_for(...) を通じてツールを提供します(特に consulting/conversation/discussion の say)。デフォルトハンドラーはそれらを agent.ask(tools=...) にマージします。高性能なモデルが、促されていないのに say を呼び出そうとし(Claude はよくそうします)、その結果の二重送信でチャンネルに問題が生じる場合は、同じ実質処理の流れを実行しつつ、agent.ask から tools= を省略する カスタムハンドラーに切り替えてください。エージェントの静的な agent.tools(自分の @tool とプラグインツール)は引き続き利用できます。ask(tools=…) で指定するのは追加のツールであり、置き換えではありません。構成例:

async def no_say_handler(envelope, client):
    if envelope.event_type != EV_TEXT:
        await default_handler(envelope, client)         # invite-ack + lifecycle for free
        return
    if not await client._hub_client.can_send(envelope.channel_id, client.agent_id):
        return                                          # not our turn / channel closing

    # ... read_wal_until / resolve_view_policy / stamp_dependencies (hooks above) ...
    reply = await client.agent.ask(current_input, stream=stream, dependencies=deps)   # ← no tools=
    out = adapter.build_round_envelope(metadata=meta, sender_id=client.agent_id, reply=reply,
                                       events=events, state=state, hub=client._hub)
    if out is not None:
        out.causation_id = envelope.envelope_id
        await client.send_envelope(out)

これは、デフォルトハンドラーの実質処理から1行だけを省いたものです。同じ方法でツールを追加したり(独自の tools=[...] を渡す)、ビュー、依存関係、ラウンドエンベロープの形式などを変更したりすることもできます。

選択的なオーバーライド用フック

デフォルトハンドラーのロジックを部分的に置き換えたい場合、ハンドラーの処理は公開フックに分割されています。

from ag2.network import (
    read_wal_until,
    resolve_view_policy,
    stamp_dependencies,
)
フック目的
read_wal_until(client, envelope)指定されたエンベロープまで(そのエンベロープは含めずに)WAL を切り出します。
resolve_view_policy(client, metadata)この参加者が使用する ViewPolicy を返します。
stamp_dependencies(client, channel, state)LLM のターン用に context.dependencies 辞書(CHANNEL_DEP、AGENT_CLIENT_DEP、HUB_DEP、CHANNEL_STATE_DEP)を構築します。state はアダプターの現在の State オブジェクト(WorkflowState/DiscussionState/…)で、ターンごとに一度だけ折りたたまれます。

カスタムハンドラーで、LLM 呼び出し前は標準の設定処理を使いつつ、呼び出し後の動作は独自にしたい場合(またはその逆の場合)に、これらを使用してください。

ビュー — 各 LLM が見るもの

ViewPolicy は、チャネルの WAL と LLM の履歴との間にある投影レイヤーです。

class ViewPolicy(Protocol):
    name: str
    async def project(
        self,
        wal: list[Envelope],
        *,
        participant_id: str,
        channel: ChannelMetadata,
        render_envelope: EnvelopeRenderer,           # required — supplied by the adapter
        name_for: NameResolver = default_name_resolver,  # optional — resolves agent_id → Passport.name
    ) -> list[BaseEvent]: ...

参加者が見るべき WAL の範囲を受け取り、フレームワークが LLM のターンに事前入力済みのストリーム履歴として渡す BaseEvent のリストを返します。render_envelope(EnvelopeRenderer = Callable[[Envelope], str | None])は、ビューをアダプターに依存しない状態に保つため、チャネルのアダプターから呼び出しごとに渡されます。name_for(NameResolver = Callable[[str], str])は投影行にラベルを付けます。デフォルトは default_name_resolver(恒等関数)です。アダプターでデフォルトを宣言でき、チャネルごとに上書きできます。

組み込みビュー
ビュー動作デフォルト
FullTranscript()アダプターの render_envelope が文字列を返す、表示可能な実質的エンベロープをすべて順番に含めます。話者はアシスタント/ユーザーのロールビットで区別されます(2者間)。consulting
WindowedSummary(recent_n=N)表示可能な実質的エンベロープのうち、直近の N 件を含めます。件数がそれを超える場合は、省略されたターン数を記載した CompactionSummary のプレースホルダーを先頭に追加します。conversation(recent_n=10)
NamedWindowedSummary(recent_n=N)WindowedSummary と同様ですが、自分以外の各行の先頭に [<sender name>]: を付けます(name_for で解決)。ロールビットでは他の参加者を区別できない、N者間チャネルで必要です。discussion、workflow(recent_n = max(N*2, 4))
NamedTranscript()FullTranscript と同様ですが、自分以外の各行の先頭に同じ [<sender name>]: を付けます。(デフォルトではありません。N者間の全履歴を使う場合に明示的に選択します)

どのビューも audience を尊重します。たとえば、[bob] のみに宛てられたエンベロープは、carol の投影には表示されません。

from ag2.network import FullTranscript, WindowedSummary, NamedWindowedSummary

view = WindowedSummary(recent_n=12)
projected = await view.project(
    wal_slice,
    participant_id=carol.agent_id,
    channel=metadata,
    render_envelope=adapter.render_envelope,   # required
    name_for=hub.name_for,                      # optional; defaults to identity
)
デフォルトの解決
from ag2.network import resolve_view_policy

policy = resolve_view_policy(client, metadata)

アダプターのマニフェストから default_view_policy を読み取り、レジストリから該当するビューをインスタンス化します。デフォルトハンドラーはこれをターンごとに一度呼び出します。カスタムハンドラーでも、標準の投影モデルを意図的に迂回する場合を除き、同様に呼び出してください。

カスタムビュー

プロトコルを実装し、一意の name を付けて使用します。

from ag2.events import BaseEvent, ModelMessage, ModelRequest, TextInput
from ag2.network import EV_TEXT, ViewPolicy


class FromOneOnly(ViewPolicy):
    """Show only envelopes from a single named sender."""
    name = "from_one_only"

    def __init__(self, sender_id: str) -> None:
        self.sender_id = sender_id

    async def project(
        self,
        wal,
        *,
        participant_id,
        channel,
        render_envelope,                 # required by the protocol
        name_for=lambda agent_id: agent_id,
    ):
        out: list[BaseEvent] = []
        for env in wal:
            if env.event_type != EV_TEXT or env.sender_id != self.sender_id:
                continue
            text = render_envelope(env)   # adapter renders the envelope to a string (or None)
            if text is None:
                continue
            out.append(
                ModelMessage(text) if env.sender_id == participant_id
                else ModelRequest([TextInput(text)])
            )
        return out

出力する BaseEvent の種類によって、LLM に表示される履歴が決まります。「ユーザーから」の内容には ModelRequest([TextInput(...)])、「アシスタントから」の内容には ModelMessage(...) などを使います。event_data に直接アクセスするのではなく、渡された render_envelope コールバックを使ってエンベロープの本文を描画してください(これにより、ビューをアダプターに依存しない状態に保てます)。分類の全容については ag2.events を参照してください。

ビューの選び方
  • 短く、焦点の定まったやり取り → FullTranscript()。トークン予算がボトルネックではなく、一貫性が重要です。
  • 長期にわたる2者間のやり取り → ターン密度に合わせて N を調整した WindowedSummary(recent_n=N)。
  • 長期にわたるN者間の議論/ワークフロー → 話者ラベルがウィンドウ内に残るよう NamedWindowedSummary(recent_n=N)(これはすでに discussion/workflow のデフォルトです)。
  • 無関係な会話を無視すべき専門エージェント → オーディエンスやタグでフィルタリングするカスタムビュー。
  • プライバシーに配慮が必要なワークフロー → 投影前にフィールドを削除または秘匿するカスタムビュー。

ビューを切り替えても WAL には影響しません。すべてのエンベロープは引き続き保持され、すべてのオペレーターが引き続き確認できます。形作られるのは、LLM が認識する履歴だけです。

スキル — エージェントの自己紹介

スキルはフロントマター付きの Markdown で、ハブにそのまま保存され、peers(action="describe", name=...) の検索時にピアへ提示されます。登録時に渡します。

agent_client = await hc.register(
    agent,
    Passport(name="researcher"),
    Resume(claimed_capabilities=["research"]),
    skill_md="""\
---
title: Research Assistant
expertise: [policy, finance]
---

# Researcher

A senior policy analyst. Best at:

- Scenario synthesis from multi-source briefs.
- Rebuttal review with confidence scores.

Limitations: not for code review or numerical analysis.
""",
)
フロントマターの解析
from ag2.network import parse_skill_frontmatter, ParsedSkill

parsed: ParsedSkill = parse_skill_frontmatter(skill_md)
print(parsed.frontmatter)  # {"title": "Research Assistant", "expertise": [...]}
print(parsed.body)         # the markdown body
スキルのフォールバック

skill_md が指定されていない場合、ピア検索で空のハンドルが返されないよう、ハブは履歴書からスキルを生成します。

from ag2.network import render_fallback_skill

skill_md = render_fallback_skill(passport, resume)

スキルをプログラムで構築する場合に便利です。たとえば、テナントが履歴書をアップロードしても Markdown を用意していない場合に役立ちます。

登録後の更新
await hub.set_skill(agent_id, new_skill_md)

AUDIT_KIND_SKILL_SET を出力します。監査データの形式は set_resume と同じです。テナントのコードから、いつでもスキルを置き換えられます。

Envelope ワイヤーフォーマット

@dataclass(slots=True)
class Envelope:
    channel_id: str
    sender_id: str                       # agent_id
    audience: list[str] | None           # None = broadcast to all participants
    event_type: str                      # "ag2.msg.text", "ag2.channel.invite", etc.
    event_data: dict[str, Any]           # event-specific payload

    envelope_id: str = ""                # hub-stamped on accept
    task_id: str | None = None
    causation_id: str | None = None      # envelope_id this one is "in reply to"
    trace_id: str | None = None
    priority: Priority = "normal"        # Priority is a Literal, not an enum (see below)
    depth: int = 0                       # delegation hop count; hub auto-increments on reply path
    idempotency_key: str | None = None   # reserved for cross-process transports
    created_at: str = ""                 # hub-stamped ISO-Z on accept
    ttl_seconds: int | None = None       # per-envelope TTL; None defers to channel expires_at

ハブは受け入れ時に envelope_id と created_at を付与し(返信経路では depth を自動的にインクリメントします)、それ以外はすべて送信元が設定します。(sequence フィールドはありません。WAL の順序は受け入れ順です。「WAL の読み取り」を参照してください。)

内容を伴うイベント
定数文字列event_data
EV_TEXT"ag2.msg.text"{"text": "<body>"}
EV_PACKET"ag2.packet"{"routing": {...}, "context_updates": {...}, "body": "<text>"}

EV_TEXT はプレーンテキストを格納します。EV_PACKET はワークフローアダプターによる、ラウンド終了時のアトミックなキャプチャです。ルーティング決定(ToolCalled ルールとの照合)、蓄積された context_vars の変更、最終的な本文テキストを1つのエンベロープにまとめます。ワークフローチャネル上の各 Agent.ask ラウンドの後にフレームワークが投稿するものであり、ツール作成者が直接構築するものではありません。

チャネルのライフサイクルイベント
定数発生時点
EV_CHANNEL_INVITEチャネル作成時に、ハブが各 target に投稿します。
EV_CHANNEL_INVITE_ACK招待を受け入れた各参加者が投稿します。
EV_CHANNEL_INVITE_REJECT任意です。招待された参加者が拒否した場合に投稿します(デフォルトのハンドラーは拒否しませんが、オーバーライドできます)。
EV_CHANNEL_OPENEDすべての確認応答が届いたときに、ハブが投稿します。
EV_CHANNEL_CLOSEDいずれかの終了経路で、ハブが投稿します。event_data.reason に理由が格納されます。
EV_CHANNEL_EXPIREDTTL スイーパーがチャネルを閉じたときに、ハブが投稿します。
EV_EXPECTATION_VIOLATED評価器のしきい値を超え、ハンドラーが notify に設定されているときに、ハブが投稿します。
コンテキストおよびタスクイベント
定数文字列発生時点
EV_CONTEXT_SET"ag2.context.set"ツールまたは参加者が、チャネル単位の context_vars を変更するために送信します。event_data は {"set": {...}, "delete": [...]} です。
EV_TASK_CANCEL_REQUEST"ag2.task.cancel_request"ピアがタスクの所有者にキャンセルを要求します。event_data は {"task_id": str, "reason": str} です。所有者は要求を受け入れても(Task.cancel)、無視してもかまいません。
EV_TASK_CANCELLED"ag2.task.cancelled"ピアのハンドラーが照合できる、所有者が送信する終端エンベロープです。event_data は {"task_id": str, "reason": str} です。

タスクのライフサイクル(開始/進行状況/完了/失敗/期限切れ)は、ワイヤーエンベロープでは伝達されません。TaskMirror によって、所有者のストリームからハブの TaskMetadata キャッシュに直接反映されます。上記の2つの EV_TASK_* 定数は例外です。キャンセルにはアドレス指定可能なエンベロープを必要とする、ピア主導の処理があるためです。デフォルトのハンドラーは、ag2.task.* イベントタイプをすべて何もせずに処理します。

受信者と可視性

audience: list[str] | None は、エンベロープを誰が閲覧できるかを制御します。

  • None — すべての参加者にブロードキャストします。
  • [agent_id_1, agent_id_2, ...] — 指定した参加者だけが閲覧できます。
from ag2.network import visible_to

if visible_to(env, my_agent_id):
    process(env)

ビュー(FullTranscript、WindowedSummary)は audience を尊重します。[bob] のみに宛てられたエンベロープは、carol のプロジェクションには表示されません。

優先度
Priority = Literal["background", "normal", "urgent"]

Priority は列挙型ではなく文字列リテラル型です。エンベロープに priority="urgent" などを直接設定します。優先度の高いエンベロープは、キュー内で低いものより先に処理されます。優先度の使用は控えめにしてください。ほとんどのアプリケーションエンベロープでは、priority をデフォルトの "normal" のままにします。

原因

causation_id は、あるエンベロープが別のエンベロープへの「返信」であることを示します。

await channel.send(reply_text, causation_id=incoming_envelope.envelope_id)

デフォルトのハンドラーは、受信した EV_TEXT に返信するときにこれを自動的に設定します。カスタムハンドラーでは、論理的な返信にこの値を設定してください。スレッド表示に役立ちます。

生のエンベロープを送信する(カスタムイベントタイプ)

channel.send(text, audience=...) は EV_TEXT でラップします。カスタムイベントタイプの場合は、Envelope を構築して直接投稿します。

from ag2.network import Envelope

envelope = Envelope(
    channel_id=channel.channel_id,
    sender_id=alice.agent_id,
    audience=[bob.agent_id],
    event_type="myapp.review_request",
    event_data={"document_id": "doc-123", "kind": "security"},
)
await alice.send_envelope(envelope)

ハブは event_type を許可リストと照合して検証しません。カスタムタイプは変更されずにそのまま通過します。アダプターは認識するイベントタイプ(内容を伴うイベント、ワークフローでの EV_PACKET、およびライフサイクルイベント)のみを集約します。カスタムイベントタイプは WAL に記録され、受信者に配信されますが、ターン進行状態には影響しません。

カスタムイベントタイプのガイドライン
  1. 将来の ag2.* イベントとの衝突を避けるため、ドット区切りの名前空間プレフィックス("review" ではなく "myapp.review_request")を使用します。
  2. ストアを経由しても正常に往復できるよう、event_data はJSON にシリアライズ可能な形式に保ちます(日付時刻やデータクラスなどは使用しないでください)。
  3. 複数の参加者が反応する必要がある場合は、audience=None に設定します。1 人だけの場合は、その人を指定します。ビューは宛先以外のイベントを除外します。
  4. カスタムタイプに対してアダプターが何らかの処理を行うことを前提にしないでください。アダプターはそのまま通過させます。処理はカスタムハンドラーが担います。

WAL の読み取り

wal = await hub.read_wal(channel.channel_id)
for i, env in enumerate(wal):
    print(f"{i:>3}  {env.event_type}  from={env.sender_id[:8]}")

エンベロープは受け入れ順(WAL リストの順序)で表示されます。エンベロープごとの sequence カウンターはありません。WAL はリプレイの正規の基盤です。Hub.hydrate() は、再起動時に各アダプターを介して WAL を再度適用し、インメモリ状態を再構築します。

クイックリファレンス — インポート

from ag2.network import (
    # Default handler + hooks (for selective override)
    default_handler,
    read_wal_until,
    resolve_view_policy,
    stamp_dependencies,
    # Plugin (auto-attached by attach_plugin=True) — peers/channels/tasks/context/delegate.
    # `say` is NOT here; it comes from the channel adapter's tools_for(...).
    NetworkPlugin,
    # Non-LLM participant
    HumanClient,           # via HubClient.register_human(Passport(name=..., kind="human"))
    PassportKind,          # Literal["agent", "human", "remote_agent"] | None
    # Views
    ViewPolicy,
    FullTranscript,
    WindowedSummary,
    NamedTranscript,
    NamedWindowedSummary,
    # Skills (peer discovery)
    ParsedSkill,
    parse_skill_frontmatter,
    render_fallback_skill,
    # Envelopes + events
    Envelope,
    Priority,
    visible_to,
    EV_TEXT,
    EV_PACKET,
    EV_CHANNEL_INVITE,
    EV_CHANNEL_INVITE_ACK,
    EV_CHANNEL_INVITE_REJECT,
    EV_CHANNEL_OPENED,
    EV_CHANNEL_CLOSED,
    EV_CHANNEL_EXPIRED,
    EV_EXPECTATION_VIOLATED,
    EV_CONTEXT_SET,
    EV_TASK_CANCEL_REQUEST,
    EV_TASK_CANCELLED,
    # Dependency injection keys (for tests outside the notify handler)
    AGENT_CLIENT_DEP,
    CHANNEL_DEP,
    CHANNEL_STATE_DEP,
    HUB_DEP,
    TASK_DEP,
    # Injects (for tool signatures)
    AgentClientInject,
    ChannelInject,
    ChannelStateInject,
    HubInject,
    TaskInject,
)

コメント