Tools
Tools are async TypeScript functions in src/tools/. Export a function and the agent
can call it. No registration, no schema definitions, no wiring.
// src/tools/github.ts
/** Open a pull request against the base branch. Returns the PR number and URL. */
export async function openPr(title: string, body: string, head: string) {
const { data } = await octokit.pulls.create({ title, body, head, ...repo() })
return { number: data.number, url: data.html_url }
}
/** Get the diff for a pull request. */
export async function getPrDiff(number: number): Promise<string> {
const { data } = await octokit.pulls.get({ pull_number: number, ...repo() })
return data.diff_url
}
Axon reads the TypeScript signatures and JSDoc at boot and hands them to the model as
typed, documented capabilities. The filename is the namespace: tools/github.ts becomes
github.openPr and github.getPrDiff. You write the function. The agent knows how to
use it.
Write real JSDoc
The JSDoc is the tool's documentation to the model — it's the difference between the agent guessing at a function and using it correctly. Say what the function does, what it returns, and anything surprising:
/**
* Search the issue tracker. Returns at most 20 results, newest first.
* Query syntax: plain text, or `label:bug`, `assignee:name` filters.
*/
export async function searchIssues(query: string) { ... }
Types are read from the signature — don't repeat them in prose. After adding or changing
tools, axon prepare regenerates the type declarations (opening Axon runs it
automatically).
Where tools run
Tools execute in the capsule — an isolated subprocess, separate from the agent process. Two consequences you'll feel while building:
A tool that crashes doesn't crash the agent. Throws, leaks, even process.exit() —
the capsule takes the hit, the runtime surfaces a structured error, the agent adapts.
Module scope is per-capsule-lifetime. Clients and caches at module level are cheap
and fine, but they're recreated when the capsule reloads (tool edits, .env changes).
Anything the agent must remember across reloads belongs in data/.
// Recreated on capsule reload — fine for a client, wrong for durable state.
const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN })
Every tool call is also policy-checked before the function body runs — see Policy.
Calling tools yourself
Tools aren't only for the model. Scripts and routes call them directly, fully typed:
const diff = await axon.tools.github.getPrDiff(42)
Tools you don't write
Installed modules contribute tool namespaces the same way — axon install @axon/linear
and the agent has linear.*. Same discovery, same typing, same capsule. See
Modules.
Next: Prompts — the context the agent loads per task.