# Tools
Tools are stateless modules that implement the `ExAthena.Tool` behaviour.
The agent loop calls them, and the result is replayed back to the model as
a tool-result message.
## Builtin tools
| Tool | Purpose | Phase: `:plan` |
|---|---|---|
| `ExAthena.Tools.Read` | Read a file, with optional offset/limit | ✅ |
| `ExAthena.Tools.Glob` | Find files by wildcard pattern | ✅ |
| `ExAthena.Tools.Grep` | Search file contents by regex | ✅ |
| `ExAthena.Tools.Write` | Create/overwrite a file | ❌ |
| `ExAthena.Tools.Edit` | Exact-string replacement in a file | ❌ |
| `ExAthena.Tools.Bash` | Shell execution with timeout | ❌ |
| `ExAthena.Tools.WebFetch` | HTTP GET (http/https only, 1 MB cap) | ✅ |
| `ExAthena.Tools.TodoWrite` | Agent todo list | ❌ |
| `ExAthena.Tools.PlanMode` | Request phase transition | ✅ |
| `ExAthena.Tools.SpawnAgent` | Synchronous sub-agent | ✅ |
Use `:all` or a list of modules to enable tools:
```elixir
# All builtins
ExAthena.run("refactor this project", tools: :all)
# A subset
ExAthena.run("find the bug", tools: [
ExAthena.Tools.Read,
ExAthena.Tools.Glob,
ExAthena.Tools.Grep
])
# Configured globally
config :ex_athena, tools: [ExAthena.Tools.Read, ExAthena.Tools.Bash]
```
## Path resolution
The `Read`, `Write`, and `Edit` tools accept absolute paths or paths relative
to `ctx.cwd`. `ExAthena.ToolContext.resolve_path/2` rejects path traversal
(`..`) and null bytes before the tool runs.
## Writing your own tool
```elixir
defmodule MyApp.Tools.DescribePage do
@behaviour ExAthena.Tool
@impl true
def name, do: "describe_page"
@impl true
def description, do: "Summarise the content of a web page"
@impl true
def schema do
%{
type: "object",
properties: %{
url: %{type: "string", description: "URL to fetch"}
},
required: ["url"]
}
end
@impl true
def execute(%{"url" => url}, _ctx) do
# … fetch + summarise
{:ok, "summary of " <> url}
end
end
# Register it:
ExAthena.run("describe https://example.com", tools: [MyApp.Tools.DescribePage])
```
### Return shapes
| Return | Behaviour |
|---|---|
| `{:ok, result}` | Stringified and replayed to the model. |
| `{:error, reason}` | Replayed as an error tool-result; loop continues. |
| `{:halt, reason}` | Loop stops immediately (emergency brake). |
### Using `ctx.assigns`
`ExAthena.ToolContext.assigns` is a map threaded through every tool call.
Use it for data the host app needs during tool execution — project id,
conversation id, database ref, user id, pubsub name.
```elixir
ExAthena.run("…", assigns: %{project_id: 42, user_id: "abc"})
```
### `TodoWrite` notifier
`ExAthena.Tools.TodoWrite` optionally calls `ctx.assigns[:todo_writer]`
with the new list — useful for broadcasting to a LiveView:
```elixir
writer = fn todos -> MyAppWeb.Endpoint.broadcast("todos", "update", todos) end
ExAthena.run("build the feature",
tools: :all,
assigns: %{todo_writer: writer})
```
## Phase gating (permissions)
Each builtin has a static "mutating or not" classification. The `:plan`
phase permits only the non-mutating ones; `:default` permits everything
(subject to `can_use_tool` + hooks); `:bypass_permissions` skips checks.
See `ExAthena.Permissions` for the full check order and `guides/agent_loop.md`
for end-to-end examples including permission flows.