Create your first tool in agents/tools/search_database.ts:
import { defineTool } from '@standardagents/spec';import { z } from 'zod';export default defineTool({ description: 'Search the customer database for matching records', args: z.object({ query: z.string().describe('Search query'), limit: z.number().optional().default(10).describe('Maximum results'), }), execute: async (state, args) => { const results = await searchDatabase(args.query, args.limit); return { status: 'success', result: JSON.stringify(results), }; },});
That’s it! Your tool is now available to any prompt that includes it in its tools array. The framework auto-discovers it from the agents/tools/ directory.
When a prompt defines resumable subagent relationships, AgentBuilder injects runtime lifecycle tools:
subagent_create
subagent_message
These tools are runtime-provided and scoped to the prompt/thread state. They are not user-defined files in agents/tools/.subagent_create requires a non-empty name for the spawned child instance.Non-resumable subagents still behave like regular tool calls.
Use state.runCode() when a tool needs to evaluate model- or user-authored JavaScript/TypeScript. The code runs in a Dynamic Worker sandbox with no implicit host capabilities and is loaded by stable content ID when possible. Bridge only the functions and data you want the code to access. Use modules when the entry source imports local relative modules, and execute when you want to run a named export or pass arguments.
const run = state.runCode(code, { execute: { fn: 'main', args: ['notes.txt'] }, imports: { fs: { readFile: async (path: string) => { const data = await state.readFile(path); return data ? new TextDecoder().decode(data) : null; }, }, }, report: (value) => console.log('sandbox report', value),});const result = await run;
The handle is awaitable, exposes live reports, and can be stopped with run.terminate(reason) from a caller-owned timeout.
Variables are validated at runtime. If required variables are missing, the tool execution fails with a clear error message. Thread env values marked secret should be redacted from tool output and errors; values marked text may be shown. Tools can write thread env values with state.setEnv(name, value, { type: 'text' | 'secret' }).When a tool reads state.env(name), AgentBuilder resolves values in this order:
thread, user account, AgentBuilder instance, agent definition, then prompt definition.
User-account and instance-level variables can also persist their own text /
secret display type, so safe defaults may preload in the UI without forcing
secrets to be revealed.
Some tools are executed by the LLM provider rather than locally. Provider
packages usually expose these names through getTools(), and models opt in with
providerTools:
Prompts select enabled provider tools by name in tools, just like local tools.
AgentBuilder marks those definitions with executionMode: 'provider' and
passes them to the provider. The local tool executor does not run provider tools;
the provider package owns native request translation, execution, and reporting
completed calls through the generic provider-tool log path. Legacy provider
events such as web search are adapted into the same log format.
Generated prompt files may store selected provider tools as
provider:<toolName> so they remain distinct from local tools with the same
name.
Tool descriptions help LLMs understand when to use each tool:Good:
defineTool({ description: 'Search the knowledge base for product documentation, FAQs, and troubleshooting guides. Returns relevant articles sorted by relevance.', // ...});