Building
Tools
Give agents a new function the model can call.
A tool is a function the model can decide to call, exactly like the built-in web_search or
workspace_read_file. You describe it with a name, a description and a JSON Schema for its input; the model
decides when to call it and with what arguments; your handler runs in the sandbox and whatever it returns goes
back to the model.
Register a tool
Written for these docs — no plugin in the official set registers a tool yet. The shape below is exactly what the runtime and the publish check enforce.
globalThis.manifest = {
egress: ["api.open-meteo.com"],
};
globalThis.tools.get_forecast = {
description: "Get tomorrow's temperature range for a latitude/longitude. Use when the user asks about weather.",
parameters: {
type: "object",
properties: {
latitude: { type: "number", description: "Decimal degrees, north positive" },
longitude: { type: "number", description: "Decimal degrees, east positive" },
},
required: ["latitude", "longitude"],
},
handler: async (input: { latitude: number; longitude: number }) => {
const response = await host.fetch({
url:
"https://api.open-meteo.com/v1/forecast" +
`?latitude=${input.latitude}&longitude=${input.longitude}` +
"&daily=temperature_2m_max,temperature_2m_min&forecast_days=2",
});
if (response.status !== 200) throw new Error(`forecast service returned ${response.status}`);
const daily = JSON.parse(response.body).daily;
return { minC: daily.temperature_2m_min[1], maxC: daily.temperature_2m_max[1] };
},
};
| Field | Rules |
|---|---|
| name (the key) | ^[a-z][a-z0-9_]{0,63}$ — lowercase, digits, underscores, starts with a letter |
description |
required, non-empty. This is what the model reads to decide when to call you: say what it does and when to use it. |
parameters |
optional JSON Schema for the input. Defaults to { "type": "object", "properties": {} } (no arguments). Must be plain JSON. |
handler |
required, a function (input) => result (sync or async). |
A bare function (globalThis.tools.x = async () => …) or a registration without a description fails the publish.
What your handler receives and returns
inputis the model's arguments object, parsed from JSON. The platform doesn't validate it against your schema — check what you depend on.- Return any JSON-serializable value. It is serialized and handed to the model as the tool's result. Returning
undefinedsendsnull. - Throwing is fine. A thrown error, a timeout or a memory fault becomes a failed tool result the model can see and react to; the agent's turn carries on.
Keep results compact: everything you return lands in the model's context.
How the model sees it
Your tool joins the agent's tool list next to the built-ins, named <plugin-slug>__<tool-name> — for example
weather__get_forecast for a plugin with slug weather. Two plugins can therefore both have a search tool.
(A tool whose final name equals a built-in tool's name is dropped with a warning.)
Which agents get it
Installing the plugin makes the tool available to every agent on the account. On each agent's Access tab the owner can switch Tools from "all" to a short list, deny your tool on one agent, or pin it. Nothing to do on your side.
What a tool can reach
A tool call runs inside a chat, on behalf of one agent, so it gets the richest context of any capability:
host.fetch,host.settings,host.crypto,host.log— always.host.session.*— act on the calling chat: read the conversation, post a message, manage todos, schedule a timer, spawn a subagent, message a peer (each by permission — see Permissions).host.workspace.*— read and write the calling agent's files (and the shared workspace).host.agents.*,host.models.list()— your plugin's own crew.host.session.llm,host.llm,host.decide— call a model.host.media.*— upload and transcode audio.
Written for these docs — a tool that saves a note into the agent's workspace:
globalThis.tools.save_note = {
description: "Save a short note into the agent's workspace under notes/.",
parameters: {
type: "object",
properties: { title: { type: "string" }, text: { type: "string" } },
required: ["title", "text"],
},
handler: async (input: { title: string; text: string }) => {
const path = `notes/${input.title.replace(/[^a-z0-9-]+/gi, "-").toLowerCase()}.md`;
await host.workspace!.write({ path, content: input.text });
return { saved: path };
},
};
Calling host.workspace.write derives host:workspace:write, so the installer sees "can write the agent's
files" before installing.
Limits
30 s wall clock and 128 MB per call (60 s with host:media:transcode). Each call is metered as a plugin
invocation.
TypeScript
The downloadable SDK types don't declare the tools registry yet. Until they do, add this to your project (any
.ts file, or a tools.d.ts next to the SDK types):
interface ToolRegistration<TInput = any> {
description: string;
parameters?: Record<string, unknown>;
handler: (input: TInput) => unknown | Promise<unknown>;
}
declare var tools: Record<string, ToolRegistration>;