Skip to content

Tools

AgentTool

tau_agent_core.tools.base.AgentTool

Validated tool wrapper used by the agent loop.

Reference: SUBPHASE-0.0.md, "2. Tool Definitions" section.

Wraps a ToolDefinition with validated name alias. The agent loop works with AgentTool (validated, wrapped), while extensions register with ToolDefinition (raw, unvalidated).

definition is annotated with the tau-llm BASE class deliberately, so every member of the hierarchy fits: the runtime :class:ToolDefinition, an :class:ExtensionToolDefinition, and a bare :class:tau_llm.tools.ToolDefinition straight out of define_tool(). Narrowing it to the subclass is what made AgentTool(definition=define_tool( ...)) raise "Input should be a valid dictionary or instance of ToolDefinition" against a value of an identically-shaped class.

definition

tau_agent_core.tools.base.AgentTool.definition: LlmToolDefinition

The underlying ToolDefinition

description

tau_agent_core.tools.base.AgentTool.description: str

Alias for definition.description.

execute

tau_agent_core.tools.base.AgentTool.execute: Callable[..., Any]

Alias for definition.execute.

execution_mode

tau_agent_core.tools.base.AgentTool.execution_mode: Literal['sequential', 'parallel']

Alias for definition.execution_mode.

name

tau_agent_core.tools.base.AgentTool.name: str

Alias for definition.name.

parameters

tau_agent_core.tools.base.AgentTool.parameters: dict[str, Any]

Alias for definition.parameters.

AgentToolResult

tau_agent_core.tools.base.AgentToolResult

Result from a single tool execution.

content

tau_agent_core.tools.base.AgentToolResult.content: list[dict[str, Any]]

List of content blocks (mirrors Message content)

details

tau_agent_core.tools.base.AgentToolResult.details: dict[str, Any] | None

Structured facts about the execution that are NOT part of what the model reads — a path, a line range, a match count, a diff. Every built-in tool sets it; it rides the toolResult message and the tool_execution_end event so a head can render more than the text block. None means the tool declared none, never "dropped".

error_message

tau_agent_core.tools.base.AgentToolResult.error_message: str | None

Error description (if is_error=True)

from_error

from_error(tool_name: str, error_message: str, tool_call_id: str | None = None) -> 'AgentToolResult'

tau_agent_core.tools.base.AgentToolResult.from_error

Create a failure result.

Parameters

  • tool_name: str — (no description)
  • error_message: str — (no description)
  • tool_call_id: str | None = None — (no description)

is_error

tau_agent_core.tools.base.AgentToolResult.is_error: bool

Whether the execution failed

terminate

tau_agent_core.tools.base.AgentToolResult.terminate: bool

Whether the agent loop should terminate after this tool

tool_call_id

tau_agent_core.tools.base.AgentToolResult.tool_call_id: str | None

ID of the tool call

tool_name

tau_agent_core.tools.base.AgentToolResult.tool_name: str

Name of the executed tool

ExtensionToolDefinition

tau_agent_core.tools.base.ExtensionToolDefinition

A tool an extension registered, as the registry holds it.

Third and last member of the hierarchy. It used to be a bare dict passed to api.register_tool() and stored as a dict, which meant the one shape crossing the extension boundary was the one shape with no schema — nothing to validate against, nothing to generate documentation from, and four separate defn["key"] / defn.get("key", default) readers each deciding for themselves what was required. A missing key surfaced as a KeyError from inside the agent loop, one turn after the mistake.

api.register_tool() still accepts the plain dict, so pi parity and every existing extension are unaffected; the dict is validated into this model at the boundary instead of being carried raw.

Two differences from the parent, both real rather than cosmetic:

source — who registered it, for the /extensions surface. Default "built-in", matching what :meth:ExtensionRegistry.get_all_tools reported before; ExtensionAPI.register_tool sets "extension". Accepted under its historical _source spelling too, because that is the key extensions and tests already write.

execute — the EXTENSION signature, execute(tool_call_id, params, signal, on_update, ctx), five arguments with the bound ExtensionContext last. The parent's is the loop's four-argument form. Nothing here can enforce that difference — both are Callable and a decorated or *args wrapper has no inspectable arity — so AgentSession._resolve_extension_tools adapts one to the other, and this docstring is where the difference is stated.

model_config

tau_agent_core.tools.base.ExtensionToolDefinition.model_config

No description. This object is marked but undocumented.

source

tau_agent_core.tools.base.ExtensionToolDefinition.source: str

No description. This object is marked but undocumented.

ToolBatchResult

tau_agent_core.tools.base.ToolBatchResult

Result from a batch of tool executions.

Returned by the agent loop after executing a batch of tool calls.

messages

tau_agent_core.tools.base.ToolBatchResult.messages: list[dict[str, Any]]

List of messages produced by the tool executions

terminate

tau_agent_core.tools.base.ToolBatchResult.terminate: bool

Whether the agent loop should terminate

tool_results

tau_agent_core.tools.base.ToolBatchResult.tool_results: list[AgentToolResult]

Individual tool execution results

tau_agent_core.tools.base.ToolDefinition

tau_agent_core.tools.base.ToolDefinition

The runtime tool definition: :class:tau_llm.tools.ToolDefinition plus name-based identity.

It SUBCLASSES the tau-llm model rather than restating its fields. It used to restate them — eight fields duplicated verbatim in two packages — and the copy was not harmless: define_tool() returns the tau-llm class, and pydantic compares by class identity, so the value τ's own public builder produced could not be handed to :class:AgentTool. Two identical shapes that refuse each other is a worse failure than one shape, because it looks like it should work.

pi has the same layering and expresses it the same way -- AgentTool extends Tool (packages/agent/src/types.ts:366) over packages/ai's Tool. τ had copied the fields instead of the relationship.

Adds only identity: two definitions with one name ARE the same tool, because the name is what the model calls and what the registry keys on. Field-by-field equality (pydantic's default, which the base keeps) would call two wrappers around the same tool different whenever a closure differs.

Reference: SUBPHASE-0.0.md, "2. Tool Definitions" section.

tau_llm.tools.ToolDefinition

tau_llm.tools.ToolDefinition

Tool definition for the LLM API.

Reference: SUBPHASE-0.0.md, "2. Tool Definitions" section.

description

tau_llm.tools.ToolDefinition.description: str

No description. This object is marked but undocumented.

execute

tau_llm.tools.ToolDefinition.execute: ToolExecute

No description. This object is marked but undocumented.

execution_mode

tau_llm.tools.ToolDefinition.execution_mode: Literal['sequential', 'parallel']

No description. This object is marked but undocumented.

label

tau_llm.tools.ToolDefinition.label: str

No description. This object is marked but undocumented.

name

tau_llm.tools.ToolDefinition.name: str

No description. This object is marked but undocumented.

parameters

tau_llm.tools.ToolDefinition.parameters: dict[str, Any]

No description. This object is marked but undocumented.

prompt_guidelines

tau_llm.tools.ToolDefinition.prompt_guidelines: list[str] | None

No description. This object is marked but undocumented.

prompt_snippet

tau_llm.tools.ToolDefinition.prompt_snippet: str | None

No description. This object is marked but undocumented.

ToolSpec

tau_llm.tools.ToolSpec

What a provider needs in order to put a tool on the wire.

The three attributes _convert_tools_to_openai actually reads, and nothing else. This exists because the annotation there used to say list[ToolDefinition] and that was false at runtime: the agent loop passes tau_agent_core.tools.base.AgentTool instances, which are not ToolDefinition at all — they wrap one and re-expose these three as properties. The call worked by duck typing and the type checker could never see it, because tau_llm does not import tau_agent_core (the dependency arrow points the other way, correctly).

A Protocol states the real contract in the layer that owns it: any object with these three members may be sent, whichever package built it. Declared as read-only properties so an object exposing them as properties — which AgentTool does — satisfies it.

description

tau_llm.tools.ToolSpec.description: str

No description. This object is marked but undocumented.

name

tau_llm.tools.ToolSpec.name: str

No description. This object is marked but undocumented.

parameters

tau_llm.tools.ToolSpec.parameters: dict[str, Any]

No description. This object is marked but undocumented.

define_tool

define_tool(definition: Mapping[str, Any] | None = None, **fields: Any) -> ToolDefinition

tau_llm.tools.define_tool

Build a validated :class:ToolDefinition.

The keyword form is the one to reach for::

word_count = define_tool(
    name="word_count",
    label="Word count",
    description="Count the words in a string.",
    parameters={
        "type": "object",
        "properties": {"text": {"type": "string"}},
        "required": ["text"],
    },
    execute=lambda text: {"words": len(text.split())},
)

A single mapping may also be passed positionally — define_tool({"name": ..., ...}) — which is the shape this function originally advertised. Passing both forms, or neither, is a caller error.

NOTE: this is not the shape tau_agent_core's ExtensionAPI.register_tool() takes. That one is a plain dict whose execute has the five-argument extension signature execute(tool_call_id, params, signal, on_update, ctx), and it defaults a missing label to name. The two are separate contracts on purpose; do not feed the result of this function to register_tool.

Reference: SUBPHASE-0.0.md, "2. Tool Definitions" section.

Parameters

  • definition: Mapping[str, Any] | None = None — (no description)
  • **fields: Any — (no description)

Raises

  • TypeError — if both forms or neither form is used, if the positional argument is not a mapping, or if a field is present with the wrong Python type (execute not callable, parameters not a dict).
  • ValueError — if a required field is missing or empty, if an unknown field is passed, if name is not wire-safe, or if parameters is not a JSON Schema object schema this package can validate against.

validate_tool_arguments

validate_tool_arguments(tool: Any, tool_call: Any) -> dict[str, Any]

tau_llm.tools.validate_tool_arguments

Validate tool call arguments against tool schema.

Uses the tool's JSON Schema (parameters field) to validate the arguments from a ToolCall. Raises ValueError if validation fails.

Reference: SUBPHASE-0.0.md, "2. Tool Definitions" section.

Parameters

  • tool: Any — Tool with a 'parameters' attribute (dict) and optionally 'name'.
  • tool_call: Any — ToolCall with an 'arguments' attribute (dict).

Returns

Validated parameter dict.

Raises

  • ValueError — If arguments don't match the tool's JSON schema.