Skip to content

Network Tools

When you call HubClient.register(agent, ...) with the default attach_plugin=True, the framework attaches NetworkPlugin to your agent. The plugin does two things:

  1. Adds an assembly policy that prefixes every LLM call with the agent's name and id.
  2. Adds the identity-level tools to agent.tools: delegate, peers, channels, tasks, context. These are stable for the life of the registration and work in any channel context — discovery, channel lifecycle, task observation, and the one-shot delegate convenience.

The per-turn tool list an agent actually sees is the union of two streams:

Stream Where it comes from Examples
Identity-level NetworkPlugin, attached once at registration delegate, peers, channels, tasks, context
Channel-level adapter.tools_for(...), resolved per turn by the default handler and merged into agent.ask(tools=...) say (text channels), user-authored Handoff-returning tools (workflow)

So say is not on every agent's agent.tools — it's contributed by the adapter that owns the channel, only when that adapter accepts free-form text and only when it's this participant's turn. A workflow participant never sees say; it routes via handoff tools instead. See Channel-level tools below.

delegate — the flat hot-path tool#

Tool Signature Purpose
delegate delegate(target, prompt, capability?, timeout=300) One-shot consult: open a consulting channel with target, send prompt, await the single reply, return its text.

delegate is the most common cross-cutting verb because "ask one specialist a question, take their answer" is the canonical multi-agent pattern — and unlike say it isn't tied to a channel the agent is already in, so it lives at the identity level.

# The LLM emits, e.g.:
#   delegate(target="bob", prompt="What's the right way to model X?", capability="modeling")

The framework resolves ChannelInject (current channel) and AgentClientInject (calling agent's hub client) automatically inside the notify handler, so the LLM never sees those parameters.

Four grouped action-dispatch tools#

Each grouped tool takes an action literal plus action-specific args, keeping the LLM's tool list short.

peers(action) — discovery#

Action Args Returns
"find" query?, capability?, sort_by?, limit=20 List of peer summaries (excludes the calling agent).
"describe" name One peer's full profile: {passport, resume, skill_md}. skill_md falls back to a rendered passport+resume when no SKILL.md is registered.

channels(action) — lifecycle#

Action Args Returns
"list" state="active"\|"all" Channels this agent participates in.
"open" type, target, knobs?, intent?, ttl?, message? Mirrors AgentClient.open. Returns {channel_id, type, participants} — plus seed_envelope_id when message was supplied.
"info" channel_id Full ChannelMetadata if the agent is a participant.
"close" channel_id? (defaults to current) Closes the channel with reason "closed_by_agent".

"open" accepts an optional message: when set, the tool opens the channel and — once it transitions to OPENED — posts that text as the first envelope on the initiator's behalf, in the same call. If the seed send fails, the just-opened channel is closed (reason="seed_failed") so you never leave a dangling-open channel nobody ever sends into. Use it for short-lived channels where the agent wants "open and ask" to be one atomic step rather than two tool calls.

tasks(action) — task lifecycle#

Two halves: active actions (the agent is inside its own agent.task(...) block) and observation actions (any task the hub has observed).

Action Half Args Returns
"progress" active payload Emits TaskProgress on the active task.
"complete" active result? Terminal — emits TaskCompleted.
"list" observation scope="own"\|"all", state="active"\|"all", limit=20 Task summaries.
"status" observation task_id Refreshed TaskMetadata.
"wait" observation task_id, timeout=300, poll_interval=0.1 Blocks until the task reaches a terminal state.
"cancel" Not implemented; returns an error placeholder.

"start" is intentionally not a tool — calling it from the LLM would bypass the async with agent.task(...) lifecycle that scopes TaskInject correctly. Owners start tasks in their own code; the LLM uses "progress"/"complete" once a task is active, and delegate for one-shot remote work.

context(action) — past content#

Action Args Returns
"search" query, scope="channel"\|"knowledge", limit=10 Excerpts of envelopes whose text matches query (case-insensitive substring).
"quote" speaker, recent_n=1, channel_id? The last recent_n EV_TEXT envelopes from speaker in the current (or specified) channel.

scope="knowledge" reaches into the calling agent's own KnowledgeStore. Substring search only — for vector / semantic search, the agent's own loop calls into framework-core recall directly.

Channel-level tools — adapter.tools_for#

The tools above are identity-level — the same on every turn. The tools an agent needs to actually participate in a given channel depend on the channel's protocol and on whose turn it is, so they come from the adapter, not the plugin:

def tools_for(
    self,
    client: AgentClient,
    metadata: ChannelMetadata,
    state: AdapterState,
    participant_id: str,
) -> list[Tool]: ...

The default handler calls adapter.tools_for(...) once per turn and merges the result into the per-call agent.ask(tools=...) override. The built-in adapters use it like this:

Adapter tools_for returns Gating
conversation [say] Always — no turn order, both participants can post any time.
discussion [say] on your round, else [] Round-robin: say only when state.expected_next_speaker is you.
consulting [say] to whoever holds the floor, else [] Initiator until the prompt is sent; respondent after, until the one reply lands; then the channel auto-closes and nobody gets say.
workflow [] Workflow agents route via user-authored Handoff-returning tools (already on agent.tools) — see Workflow.

say(content, audience?, channel_id?) posts EV_TEXT into the active channel (audience is a list of peer names, resolved to ids; None broadcasts). Internally it builds the envelope via adapter.build_text_envelope(...) — the same Layer-2 helper a non-AG2 bridge would call — so per-adapter envelope shaping is honoured automatically. See Adapters Overview → The Adapter Protocol for the full three-layer picture.

Tool resolution is memoized per client.agent_id inside each adapter, so the fast_depends schema-build cost is paid once, not on every notify turn.

If you write a custom adapter, override tools_for to offer your channel's verbs (or leave the default, which returns []).

What gets injected, automatically#

Every grouped tool accepts AgentClientInject, ChannelInject, and (for tasks) TaskInject parameters that the framework resolves from context.dependencies when the tool runs inside a notify handler. Your code does not need to wire them up — the default handler stamps them via stamp_dependencies before invoking the agent's turn.

If you are testing a tool outside the notify-handler context, pass them yourself:

1
2
3
4
5
6
7
8
9
from autogen.beta import Context
from autogen.beta.network import AGENT_CLIENT_DEP, CHANNEL_DEP, Channel

ctx = Context(
    dependencies={
        AGENT_CLIENT_DEP: alice,
        CHANNEL_DEP: Channel(metadata=channel.metadata, client=alice),
    },
)

Opting out#

Pass attach_plugin=False to HubClient.register for a bare agent — useful for headless workers or gateways that handle envelopes entirely in your own code without needing the LLM-facing tool surface.

worker = await hc.register(agent, passport, resume, attach_plugin=False)
worker.on_envelope(my_custom_handler)

See also#

  • Agent Clients and Handlers — what the default handler does and how to replace it.
  • Workflow — hand-written Handoff-returning tools complement ToolCalled AgentTarget transitions for graph-driven routing.