Tools

Tool exports are globals. Call them directly from scripts, routes, and hooks — no prefix, no namespace wrapper.

// src/scripts/triage.ts
const tasks = await kanban.list("open")
const session = await axon.prompt("session", { tasks })
const { stream } = axon.stream({ prompt: session })

Why globals

Tools are primarily called by the agent during its cognitive loop. But scripts often need to call tools directly — to load state before constructing a prompt, to act on the agent's output, or to run work without the loop.

You wrote the function; you should be able to call it. Tools execute in the capsule, a separate subprocess, and the transport is handled for you — a script author never has to know the boundary is there. The same call works from a script, a route handler, a hook, or the agent itself.

What becomes a global

Each top-level export from src/tools/*.ts lands on the global scope with its exact name.

// src/tools/kanban.ts
export const kanban = {
    list: async (status?: string) => ...,
    add: async (title: string) => ...,
}
// src/tools/time.ts
export async function now() { return new Date().toISOString() }
export async function format(date: string) { ... }

The agent and all scripts see: kanban, now, format — each directly callable.

Installed modules keep their namespace: @axon/github contributes github.openPr, not a bare openPr. See Modules.

Always awaited

Every tool call is awaited, whether you wrote the function sync or async:

const sum = await add(2, 3)

Each call is policy-checked before the function body runs, and a rule can escalate to the user for approval — a round trip that cannot be synchronous. See tools/.

axon.tools.* — the explicit path

The same functions are always reachable under axon.tools.<file>.<fn>:

const tasks = await axon.tools.kanban.list("open")

Identical behaviour — same capsule, same policy, same tracing. The globals are bindings onto this surface, not a separate route.

Reach for it when a bare name would be ambiguous or unavailable: a tool whose name collides with a host builtin (a tool called fetch will not shadow the real fetch, and stays callable as axon.tools.<file>.fetch), or code driving a runtime other than its own.

Typing

axon prepare generates .agent/tool-globals.d.ts — a declare global block declaring each export, with the types read from your source and any type your signature references carried alongside it. Full autocomplete with no imports required.

// .agent/tool-globals.d.ts — generated, do not edit
declare global {
    type Task = { id: string; title: string; done: boolean }

    /** Fetch the next unfinished task. */
    function next(): Promise<Task | null>

    namespace github {
        function openPr(title: string): Promise<{ number: number; url: string }>
    }
}

This file mirrors the scope the model receives — same source, same members — so what your editor tells you and what the agent can call never disagree.

Run axon prepare after adding or modifying tool files to refresh types.

Example — load state before prompting

const issues = await kanban.list({ status: "open" })
const session = await axon.prompt("session", { issues })

const { stream } = axon.stream({ prompt: session })

Loading state explicitly before the prompt is more reliable than letting the agent call the tool itself — you control exactly what data the agent sees.