Normandy.A2A.AgentTool (normandy v2.2.0)

View Source

Wraps a remote A2A agent as a Normandy.Tool.

When the LLM calls this tool, a message is sent to the remote agent via the A2A protocol and the result is returned.

Example

alias Normandy.A2A.AgentTool

{:ok, card} = Claudio.A2A.Client.discover("https://agent.example.com")

tool =
  AgentTool.new("https://agent.example.com/a2a", card,
    prefix: "research",
    auth_token: "bearer-token"
  )

# The model sees the tool as "research__<the card's name>".
agent = BaseAgent.register_tool(agent, tool, timeout: 60_000)

The tool polls a remote task until it ends; the registration :timeout (default Normandy.Tools.Executor.default_timeout/0, 30 s) bounds the whole call, so give a remote agent that works for minutes a longer one.

Tool names

The tool's name is prefix <> "__" <> card name (plus "__" <> skill id for a skill), the card name and skill id lowercased with every run of other characters turned into one _. The remote agent chooses its card, so :prefix is required, with the same rule as Normandy.MCP.ToolWrapper's (see "Tool names and policy rules" there): a policy rule such as "research__*" then matches exactly this remote's tools. prefix: "a2a" gives the names Normandy 1.x used; prefix: false gives the bare card name.

Trust

A remote agent is outside your trust boundary. Its card's name, description and skill descriptions are sent to the model as this tool's name and description, and its replies become the model's input: each is a prompt-injection channel that Normandy passes through unchanged. Register remotes you trust, and gate this tool with a policy when its replies can lead the model to act.

Summary

Functions

Creates a new AgentTool for a remote A2A agent.

Types

t()

@type t() :: %Normandy.A2A.AgentTool{
  agent_card: Claudio.A2A.AgentCard.t(),
  auth_token: String.t() | nil,
  endpoint: String.t(),
  poll_interval: pos_integer(),
  prefix: String.t() | false,
  skill_id: String.t() | nil,
  transport_opts: keyword()
}

Functions

new(endpoint, agent_card, opts \\ [])

@spec new(String.t(), Claudio.A2A.AgentCard.t(), keyword() | map()) :: t()

Creates a new AgentTool for a remote A2A agent.

Options

  • :prefix (String.t/0 or false) - Required. The remote's namespace: the tool is named prefix__card_name. The rule of Normandy.MCP.ToolWrapper.new/4's :prefix; "a2a" gives 1.x's names, false the bare card name.

  • :skill_id (String.t/0) - The skill to expose (default: general access to the agent).

  • :auth_token (String.t/0) - A bearer token for the remote agent.

  • :poll_interval (pos_integer/0) - Milliseconds between status requests while the remote's task is not finished. The call's timeout is the only bound on the wait. The default value is 1000.

  • :transport_opts (keyword/0) - Options for the A2A transport. Its :headers ([{name, value}]) are sent on every request; with OpenTelemetry loaded and a span active, W3C traceparent/tracestate headers are added beside them (a same-named header you set is kept). The default value is [].

The call's timeout is a registration option: BaseAgent.register_tool(agent, tool, timeout: ms). Passing :timeout here raises ArgumentError.