defineTool
Create custom tools with schema validation and lifecycle hooks.
defineTool
defineTool creates a typed tool definition with full parameter inference from a Zod schema. The execute function receives validated arguments and a ToolContext, and must return a ToolResult.
defineTool
Creates a ToolDefinition with full type inference from the Zod schema.
import { z } from "zod";
import { defineTool, okResult, errorResult } from "@comma-agents/core";
const weatherTool = defineTool({
description: "Get the current weather for a location",
parameters: z.object({
location: z.string().describe("City name"),
unit: z.enum(["celsius", "fahrenheit"]).optional(),
}),
execute: async (validatedArguments, toolContext) => {
const { location, unit } = validatedArguments;
// Access agent context
console.log(`Tool called by agent: ${toolContext.agentName}`);
// Access the per-tool guard for authorization
const abs = await toolContext.guard.authorize(
{ type: "fs.read", resource: "data/cache.json" },
{ agentName: toolContext.agentName, toolName: "weather", signal: toolContext.abort },
);
try {
const weather = await fetchWeather(location, unit ?? "celsius");
return okResult(weather.summary, {
data: { temp: weather.temp, humidity: weather.humidity },
});
} catch (err) {
return errorResult({
kind: "unknown",
message: `Weather fetch failed: ${err instanceof Error ? err.message : String(err)}`,
recoverable: true,
suggestedNextAction: "Retry with a different location name or check connectivity.",
});
}
},
});Guard System
Every tool receives a per-tool Guard on toolContext.guard. The guard is the runtime enforcement layer that authorizes every file-system access, command execution, and custom policy check. It is provided by the agent's sandbox — there is always one, defaulting to permissive if none is explicitly configured.
What the guard does
| Responsibility | Description |
|---|---|
| Workspace jail | Rejects paths escaping the workspace root when jail: true. |
| Path policies | Each path access passes through ordered read/write deny/allow lists. |
| Command approval | Gate dangerous commands behind deny/approve lists or interactive prompts. |
| Forbidden globs | Always-deny patterns for secrets, keys, and sensitive paths. |
Authorizing access
Call guard.authorize() inside execute for any operation that touches the file system or shell:
const abs = await toolContext.guard.authorize(
{ type: "fs.read", resource: "data/cache.json" },
{
agentName: toolContext.agentName,
toolName: "weather",
signal: toolContext.abort,
},
);The guard returns the resolved absolute path. If access is denied, it throws a SandboxViolationError — the tool framework catches this and converts it to a structured ToolError with kind permission_denied.
Tool-level policies
Custom tools can declare their own policies via the policies option. These are appended to the guard's policy chain at tool construction time:
import { defineTool, denyCommandsPolicy } from "@comma-agents/core";
const tool = defineTool({
description: "Run a user-supplied shell command",
parameters: z.object({ command: z.string() }),
policies: [
denyCommandsPolicy(["rm -rf /", "sudo shutdown"]),
],
execute: async ({ command }, ctx) => {
await ctx.guard.authorize(
{ type: "command.execute", resource: command },
{ agentName: ctx.agentName, toolName: "my_tool", signal: ctx.abort },
);
// ...
},
});Built-in policy factories include denyCommandsPolicy, approveCommandsPolicy, forbiddenGlobsPolicy, and pathPolicy. You can also define custom Policy functions with the full "allow" / "deny" / "ask" / "pass" decision model.
For the full guard API — jail mode, path policies, the "ask" dispatch flow, session decisions, and interactive permission prompts — see the Sandbox documentation.
Guard System
Every tool receives a per-tool Guard on toolContext.guard. The guard is the runtime enforcement layer that authorizes every file-system access, command execution, and custom policy check. It is provided by the agent's sandbox — there is always one, defaulting to permissive if none is explicitly configured.
What the guard does
| Responsibility | Description |
|---|---|
| Workspace jail | Rejects paths escaping the workspace root when jail: true. |
| Path policies | Each path access passes through ordered read/write deny/allow lists. |
| Command approval | Gate dangerous commands behind deny/approve lists or interactive prompts. |
| Forbidden globs | Always-deny patterns for secrets, keys, and sensitive paths. |
Authorizing access
Call guard.authorize() inside execute for any operation that touches the file system or shell:
const abs = await toolContext.guard.authorize(
{ type: "fs.read", resource: "data/cache.json" },
{
agentName: toolContext.agentName,
toolName: "weather",
signal: toolContext.abort,
},
);The guard returns the resolved absolute path. If access is denied, it throws a SandboxViolationError — the tool framework catches this and converts it to a structured ToolError with kind permission_denied.
Tool-level policies
Custom tools can declare their own policies via the policies option. These are appended to the guard's policy chain at tool construction time:
import { defineTool, denyCommandsPolicy } from "@comma-agents/core";
const tool = defineTool({
description: "Run a user-supplied shell command",
parameters: z.object({ command: z.string() }),
policies: [
denyCommandsPolicy(["rm -rf /", "sudo shutdown"]),
],
execute: async ({ command }, ctx) => {
await ctx.guard.authorize(
{ type: "command.execute", resource: command },
{ agentName: ctx.agentName, toolName: "my_tool", signal: ctx.abort },
);
// ...
},
});Built-in policy factories include denyCommandsPolicy, approveCommandsPolicy, forbiddenGlobsPolicy, and pathPolicy. You can also define custom Policy functions with the full "allow" / "deny" / "ask" / "pass" decision model.
For the full guard API — jail mode, path policies, the "ask" dispatch flow, session decisions, and interactive permission prompts — see the Sandbox documentation.
Result Helpers
okResult
Builds a successful ToolResult. The output field is the text returned to the model.
import { okResult } from "@comma-agents/core";
// Basic success
return okResult("Wrote 12 bytes");
// With structured data
return okResult("File saved successfully", {
data: { sha256: "abc123", sizeBytes: 4096 },
metadata: { durationMs: 15 },
});errorResult
Builds a failure ToolResult from a structured ToolError. If no output override is provided, it defaults to error.message.
import { errorResult } from "@comma-agents/core";
return errorResult({
kind: "stale_file",
message: "File changed since last read",
path: "/workspace/src/index.ts",
recoverable: true,
suggestedNextAction: "Re-read the file to get the latest content before editing.",
});toolError
Utility for constructing a ToolError without repeating the kind as a literal.
import { toolError } from "@comma-agents/core";
const err = toolError("not_found", "No such file", {
path: "/workspace/missing.txt",
recoverable: true,
suggestedNextAction: "Check the path and try again.",
});