Write a custom tool for your agent in TypeScript or Python.
Tools you write in code come in two languages, TypeScript and Python. This guide covers both. Pick the tab for your language in each step. Both kinds are bundled into the binary at compile time, so there is no runtime installation. For the full field reference, see the tool reference.
TypeScript tools run in an embedded JavaScript runtime. Python tools use RustPython by default,
which supports pure-Python packages and needs no Python installation at runtime. Select the
static interpreter to use CPython 3.14 for packages with C extensions. TypeScript tools use
agentc:tools, while Python tools use agentc_tools.
Generate a ready-to-use package with agentc tool init, then install its dependencies.
agentc tool init math --language javascript
cd math
pnpm installThis creates a ready-to-use package with the entry point, package metadata, and editor type
support already configured. agentc.d.ts is one line, and it is what tells an editor the shape of
the runtime surface.
The compiler installs dependencies with the package manager configured for the scaffolded package.
Each tool supplies a name, description, JSON Schema for its inputs, and an execute method. The name
and descriptions are what the model reads to decide when and how to call it, so write them carefully.
Export a class extending Tool from the package entry point. Its generic parameters are the argument
type, result type, and state type. Omit the state type when its unknown default is sufficient.
import { Schema, Tool, type ToolInput, type ToolOutput } from "agentc:tools";
type AddArgs = { a: number; b: number };
export class Adder extends Tool<AddArgs, number> {
static readonly description = "Adds two numbers together.";
static readonly parameters = new Schema({
type: "object",
properties: {
a: { type: "number", description: "The first number." },
b: { type: "number", description: "The second number." }
},
required: ["a", "b"]
});
async execute(input: ToolInput<AddArgs>): Promise<ToolOutput<number>> {
return { output: input.args.a + input.args.b };
}
}description tells the model what the tool does. parameters is the JSON Schema it uses to call the
tool, wrapped in Schema. Schema validates the document as JSON Schema draft 2020-12 when the
module loads, so an invalid schema prevents the tool from loading. ToolInput supplies read-only
args, state, and optional emit; ToolOutput contains required output and optional
state_update. execute must be asynchronous.
A single package can export as many tool classes as you need.
The runtime provides its libraries to every tool. They are not package dependencies, and each one is imported like any other module. The runtime libraries are catalogued at runtime libraries.
JavaScript tools do not run in Node. They run in an embedded JavaScript engine, and the environment
they see is assembled by the host, so it is neither Node's nor a browser's. The runtime libraries the
host does provide live under the agentc: prefix.
import { fetch } from "agentc:http";
const response = await fetch("https://example.com");The JavaScript surface the host binds is enumerated at standard.
The shapes of all of it come from @agentc-sh/runtime, which the scaffold installs and references for
you, so your editor reports what the host actually provides rather than guessing. The package is
optional. A component that does not depend on it compiles and runs identically.
The scaffold does not depend on @types/node, and its tsconfig.json sets types to an empty array
so nothing pulls it in. The guest is not a Node process, and @types/node describes a great deal that
the host never binds.
The package exists, but the agent does not see it until you declare a tool block pointing source at
the package directory.
tool "adder" {
kind = "javascript"
source = "./math"
export = "Adder"
}A tool can return a state_update alongside its output: an array of RFC 6902 JSON Patch operations.
For ReAct, add an operation at /context/- whose value is the operation to apply to context.
The nested operation's path is relative to the context object. Context changes persist and
appear in state delta events. See the ReAct state reference.
export class Adder extends Tool<AddArgs, number> {
async execute(input: ToolInput<AddArgs>): Promise<ToolOutput<number>> {
return {
output: input.args.a + input.args.b,
state_update: [
{
op: "add",
path: "/context/-",
value: { op: "add", path: "/last_sum", value: input.args.a + input.args.b }
}
]
};
}
}Tools can emit stateless activity updates while they run. emit is optional, so call it with
optional invocation.
input.emit?.({
activity_type: "progress",
patch: [
{ op: "add", path: "/message", value: "Working" }
]
});Rebuild so the tool is bundled into the binary:
agentc buildtool block for each kind.© 2026 pogue.dev. All rights reserved.
CC BY 4.0