Normandy.Tool behaviour
(normandy v2.2.0)
View Source
The contract every tool implements.
A tool is a struct whose module implements this behaviour. The struct holds the tool's
configuration (an endpoint, a store, an index name), set by the application when it
registers the tool (Normandy.Tools.Toolset.register/3). The model's input never goes into
it: the dispatcher validates the input against input_schema/1
(Normandy.Tools.Input.validate/2) and passes it to run/2 as context.input.
Callbacks
name/1: the name the model calls the tool by, 1 to 64 letters, digits,_or-.description/1: what the tool does, for the model.input_schema/1: the JSON Schema object of the input.run/2: runs the tool.context.inputis the validated input. Returns{:ok, result}or{:error, reason}; either reaches the model as the tool result.check_context!/2(optional): checks the agent'stool_contextwhen the agent is built or the tool is registered.native_definition/1(optional): the provider-defined tool type the tool implements, which an adapter that supports it sends instead of the description and input schema.
use Normandy.Tool
defmodule MyApp.Search do
defmodule Input do
use Normandy.Schema
io_schema "Search input" do
field(:query, :string, required: true, description: "What to look for")
field(:limit, :integer, default: 5, minimum: 1, maximum: 20)
end
end
use Normandy.Tool, name: "search", description: "Searches the catalogue", input: Input
defstruct [:index]
@impl Normandy.Tool
def run(%__MODULE__{index: index}, %Normandy.Tools.Context{input: %Input{} = input}),
do: MyApp.Catalogue.search(index, input.query, input.limit)
enduse Normandy.Tool declares the behaviour, and each option generates one callback:
:name → name/1, :description → description/1, :input (a module that uses
Normandy.Schema, defined before the use) → input_schema/1. With :input,
context.input is a struct of that schema. Leave an option out and write its callback
when the value depends on the struct, or to hand-write the JSON Schema map; without
:input, context.input is a string-keyed map checked against that map. Do not pass an
option and also define its callback.
name/1, description/1, input_schema/1 and run/2 on this module call the tool's own
callbacks.
Summary
Callbacks
Optional. Checks the agent's tool_context values (BaseAgentConfig.tool_context) when
the tool and the context first meet: BaseAgent.init/1, BaseAgent.register_tool/3 and
Normandy.Agents.ConfigTemplate.rebuild/3 (a Turn.Session built from a stored template).
Returns :ok or raises ArgumentError, so a misconfiguration fails when the agent is
built rather than on the model's first call. A context replaced for one call (as
Normandy.A2A.Server does) is not checked here; run/2 still sees it.
Returns what the tool does, as a string for the model.
Returns the JSON Schema object that describes the tool's input, or nil for none.
Returns the name the model calls the tool by, 1 to 64 letters, digits, _ or -.
Optional. Returns the fields of a provider-defined tool the tool implements, such as
%{type: "memory_20250818"} for Anthropic's memory tool, or nil for none.
Runs the tool for one call, with the validated input in context.input, and returns
{:ok, result} or {:error, reason}.
Functions
Checks values (a tool_context) through the module's check_context!/2; :ok when
the module defines none.
The tool's description, from its module's description/1.
Whether module implements this behaviour. Loads it first, so a module this node has not
loaded yet (interactive mode) is not taken for a non-tool.
The tool's input JSON Schema, from its module's input_schema/1. nil when the tool has
none (an MCP tool whose server sent no schema); such a tool's input is rejected.
The tool's name, from its module's name/1.
The tool's provider-defined fields, from its module's native_definition/1; nil when
the module defines none.
Runs the tool through its module's run/2.
Callbacks
Optional. Checks the agent's tool_context values (BaseAgentConfig.tool_context) when
the tool and the context first meet: BaseAgent.init/1, BaseAgent.register_tool/3 and
Normandy.Agents.ConfigTemplate.rebuild/3 (a Turn.Session built from a stored template).
Returns :ok or raises ArgumentError, so a misconfiguration fails when the agent is
built rather than on the model's first call. A context replaced for one call (as
Normandy.A2A.Server does) is not checked here; run/2 still sees it.
Returns what the tool does, as a string for the model.
use Normandy.Tool, description: "..." generates it.
Returns the JSON Schema object that describes the tool's input, or nil for none.
The dispatcher validates the model's input against it before run/2; a tool that
returns nil has its input rejected. use Normandy.Tool, input: Schema generates it from
the schema module.
Returns the name the model calls the tool by, 1 to 64 letters, digits, _ or -.
Toolset.register/3 registers the tool under it. use Normandy.Tool, name: "..."
generates it.
Optional. Returns the fields of a provider-defined tool the tool implements, such as
%{type: "memory_20250818"} for Anthropic's memory tool, or nil for none.
Normandy.LLM.ClaudioAdapter sends these fields and the tool's name in place of its
description and input schema; the model then calls the tool in the provider's shape,
which input_schema/1 must still describe (the dispatcher validates against it). An
adapter that does not know provider-defined tools must send the tool's name, description
and input schema instead.
@callback run(tool :: struct(), Normandy.Tools.Context.t()) :: {:ok, term()} | {:error, term()}
Runs the tool for one call, with the validated input in context.input, and returns
{:ok, result} or {:error, reason}.
Either reaches the model as the tool result; an error is marked as an error. A return value of any other shape, a raise, an exit or a throw becomes an error result as well, and so does running past the tool's timeout.
Functions
Checks values (a tool_context) through the module's check_context!/2; :ok when
the module defines none.
The tool's description, from its module's description/1.
Whether module implements this behaviour. Loads it first, so a module this node has not
loaded yet (interactive mode) is not taken for a non-tool.
The tool's input JSON Schema, from its module's input_schema/1. nil when the tool has
none (an MCP tool whose server sent no schema); such a tool's input is rejected.
The tool's name, from its module's name/1.
The tool's provider-defined fields, from its module's native_definition/1; nil when
the module defines none.
@spec run( struct(), Normandy.Tools.Context.t() ) :: {:ok, term()} | {:error, term()}
Runs the tool through its module's run/2.