Skip to main content
A tool is any Python callable a ReActAgent can invoke to do something outside the LLM. In practice that means:
  • A plain Python function
  • A method on a class
  • Another agent (wrapped as a callable)
  • A method exposed by an MCP server
Pass any of these into tools=[...] and the agent sees them as named, schema-typed functions it can call by name.

Functions are tools

The fastest way to give an agent a tool is to pass a plain Python function.
Motus reads the name, the docstring, and the type hints and builds the JSON Schema the LLM sees. Sync and async functions both work.
Parameters without type annotations must have a default value. Adding docstrings to functions and descriptions to parameters is strongly recommended so the model understands when and how to call your tool.

The @tool decorator

Use @tool to customize how a function is exposed to the model: its name, description, schema, guardrails, an approval gate, or lifecycle hooks.
tool() also works as a post-hoc patcher on a callable you don’t own:

Describing parameters

You have three ways to describe what a tool’s arguments mean. Pick whichever is most convenient. Define a BaseModel subclass with Field descriptors. This gives you validation constraints, nested objects, enums, and rich descriptions all in one place.
Nested models like Filter are expanded into the JSON Schema the model sees. The function signature is what Motus calls when the tool runs, so make sure the field names match the parameter names.

Annotated for inline descriptions

A lighter alternative when you only need to add descriptions and do not need validation. Add a string after the type in an Annotated wrapper.

Raw JSON Schema

For exact control over the schema the model sees, pass a dict directly.

Type mapping

When Motus infers the schema from your type hints, this is the mapping it uses.

Tool collections from a class

Group related tools into a class with the @tools decorator. Public methods become tools automatically, and self is stripped from the schema.
tools() also works as a function call on an instance you don’t own, for cases where you want to pick specific methods:
A per-method @tool overrides the class-level @tools options for that one method:

Built-in tools

Motus ships a ready-made set of tools that cover the common needs of a code-writing agent: running shell commands, reading and writing files, searching the filesystem, and keeping a checklist of its own work. Most agents that do anything with code can drop these in without writing their own. Get them from builtin_tools(), which works out of the box against your local machine.
Pass skills_dir="..." to add a load_skill tool that lets the agent load self-contained instructions from disk on demand. See Skills.
With no sandbox, builtin_tools() runs bash, write_file, and edit_file directly against your machine. That means the agent can delete files, install packages, or run any shell command, just like a person with terminal access. Use a sandbox (see below) for untrusted prompts, and use approval gates on tools you do not want the agent running without permission.
You can customize an individual built-in tool by re-decorating it. The object returned by builtin_tools() has attribute access to every tool, so you can patch just one:

Sandboxed execution

A Sandbox in Motus is an execution environment that the built-in tools run inside: you create it, the tools bound to it execute commands there, and you close it when you’re done. It is not a tool itself; it is the place tools run. Two implementations ship with Motus:
  • LocalShell, the default, runs commands directly on your host machine.
  • DockerSandbox runs everything inside a container, so a rogue command cannot touch your host.
builtin_tools() uses LocalShell when you do not pass anything. To isolate execution, create a DockerSandbox and hand it in:
For a managed sandbox with mounts, ports, or a pre-built image, use the get_sandbox() factory. It reuses a single global provider under the hood, so repeated calls do not spin up new containers.

MCP tools

Any Model Context Protocol server can be used as a tool source via get_mcp().
The session exposes every tool the MCP server advertises. Both patterns for managing the session work. If you pass an unconnected get_mcp(...) session to the agent without async with, the agent connects it lazily on its first run. Using async with gives you deterministic cleanup when the block exits. See MCP Integration for connection options, filtering, and renaming tools.

Agents as tools

An agent can be another agent’s tool. The caller treats it like any other entry in tools=[...].
For a custom name, description, or stateful memory across calls, use researcher.as_tool(name="do_research", stateful=True). See Multi-agent for the full composition guide.

Approval gates

Mark a dangerous tool with requires_approval=True and the agent will pause before running it, emit an approval request to the caller, and resume once the caller approves.
motus serve surfaces these pauses through its REST API so a client can prompt the user and respond. See Human in the Loop for the full protocol.

Registration cheat sheet

A quick reference for how to pass each kind of tool to an agent.