Documentation Index Fetch the complete documentation index at: https://docs.standardagentbuilder.com/llms.txt
Use this file to discover all available pages before exploring further.
Tools are callable capabilities that extend what AI agents can do beyond generating text. They allow agents to:
Execute code : Run custom business logic
Access data : Query databases and APIs
Perform actions : Create records, send emails, trigger workflows
Call other AI : Invoke sub-prompts or hand off to specialized agents
In AgentBuilder, “tools” includes function tools, prompt tools, and agent tools. Callable dual_ai agent tools can execute as autonomous subagents.
Quick Start
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.
AgentBuilder supports three types of tools that all appear the same to LLMs:
Function Tools Custom TypeScript functions Defined in: agents/tools/Best for:
Database queries
API calls
File operations
Business logic
Prompt Tools Other prompts called as sub-prompts Defined in: agents/prompts/ with exposeAsTool: trueBest for:
Specialized analysis
Content generation
Data summarization
Validation tasks
Agent Tools Full agents for handoffs Defined in: agents/agents/ with exposeAsTool: trueBest for:
Specialist routing
Complex workflows
Domain expertise
Multi-turn tasks
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.
How They Work Together
// Function tool: agents/tools/lookup_customer.ts
export default defineTool ({
description: 'Look up customer by ID' ,
args: z . object ({ customerId: z . string () }) ,
execute : async ( state , args ) => {
const customer = await db . getCustomer ( args . customerId );
return { status: 'success' , result: JSON . stringify ( customer ) };
} ,
}) ;
// Prompt tool: agents/prompts/summarizer.ts
export default definePrompt ({
name: 'summarizer' ,
exposeAsTool: true ,
toolDescription: 'Summarize text into key points' ,
prompt: 'You are an expert summarizer...' ,
model: 'gpt-5.4' ,
requiredSchema: z . object ({
text: z . string (). describe ( 'Text to summarize' ),
}) ,
}) ;
// Agent tool: agents/agents/billing_specialist.ts
export default defineAgent ({
name: 'billing_specialist' ,
exposeAsTool: true ,
toolDescription: 'Hand off billing issues to specialist' ,
sideA: { prompt: 'billing_expert' } ,
}) ;
// Use all three in a prompt
export default definePrompt ({
name: 'support_router' ,
tools: [
'lookup_customer' , // Function tool
'summarizer' , // Prompt tool
'billing_specialist' , // Agent tool
] ,
// ...
}) ;
ThreadState Context
Every tool receives a ThreadState object providing execution context:
defineTool ({
description: 'My tool' ,
args: z . object ({ input: z . string () }),
execute : async ( state , args ) => {
// Access execution context
console . log ( 'Thread ID:' , state . threadId );
console . log ( 'Turn:' , state . turnCount );
console . log ( 'Agent:' , state . agentConfig . name );
// Access storage
const result = await state . storage . sql . exec ( 'SELECT * FROM data WHERE id = ?' , args . input );
// Access environment
const apiKey = await state . env ( 'MY_API_KEY' );
return { status: 'success' , result: 'Done' };
},
});
ThreadState gives tools access to thread storage, message operations, file system, and execution state. See the ThreadState documentation for details.
Sandboxed Code Execution
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.
Tools use Zod schemas to validate arguments:
const argsSchema = z . object ({
// Required string
query: z . string (). describe ( 'Search query' ),
// Optional with default
limit: z . number (). optional (). default ( 10 ). describe ( 'Max results' ),
// Enum
format: z . enum ([ 'json' , 'text' , 'markdown' ]). describe ( 'Output format' ),
// Nested object
filters: z . object ({
category: z . string (). optional (),
minPrice: z . number (). optional (),
}). optional (). describe ( 'Filter options' ),
// Array
tags: z . array ( z . string ()). optional (). describe ( 'Filter by tags' ),
});
Always use .describe() on parameters! These descriptions help LLMs understand how to use your tools correctly.
Tools return a ToolResult object:
interface ToolResult {
status : 'success' | 'error' ;
result ?: string ;
error ?: string ;
stack ?: string ;
attachments ?: Array < ToolAttachment | AttachmentRef >;
}
interface ToolAttachment {
name : string ; // Filename (e.g., "generated.png")
mimeType : string ; // MIME type
data : string ; // Base64-encoded file data
width ?: number ; // Image width in pixels
height ?: number ; // Image height in pixels
}
When tool-driven subagent communication crosses threads, attachment paths are copied and rewritten to destination-local paths before delivery.
Common Patterns
Text Result
return {
status: 'success' ,
result: 'Operation completed successfully' ,
};
JSON Data
return {
status: 'success' ,
result: JSON . stringify ({ id: '123' , name: 'John' }),
};
Error Handling
return {
status: 'error' ,
error: 'Failed to find user: User not found' ,
};
Thread Environment Variables
Tools can declare required thread environment variables using the variables option:
export default defineTool ({
description: 'Search through uploaded files using vector embeddings' ,
args: z . object ({
query: z . string (). describe ( 'Search query' ),
}) ,
execute : async ( state , args ) => {
const vectorStoreId = await state . env ( 'VECTOR_STORE_ID' );
const results = await searchVectorStore ( vectorStoreId , args . query );
return { status: 'success' , result: JSON . stringify ( results ) };
} ,
variables: [
{
name: 'VECTOR_STORE_ID' ,
type: 'secret' ,
required: true ,
description: 'OpenAI Vector Store ID' ,
},
] ,
}) ;
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' }).
Some tools are executed by the LLM provider rather than locally. Use executionMode and executionProvider to configure this:
export default defineTool ({
description: 'Generate images using DALL-E' ,
args: z . object ({
prompt: z . string (). describe ( 'Image description' ),
}) ,
execute : async () => {
// This won't be called - the provider handles execution
return { status: 'success' , result: 'Handled by provider' };
} ,
executionMode: 'provider' ,
executionProvider: 'openai' ,
}) ;
Provider-executed tools are typically defined by provider packages like @standardagents/openai. You can enable them using the providerTools option in your model definition.
Common Use Cases
import { defineTool } from '@standardagents/spec' ;
import { z } from 'zod' ;
export default defineTool ({
description: 'Look up customer information by ID or email' ,
args: z . object ({
customerId: z . string (). optional (). describe ( 'Customer ID' ),
email: z . string (). email (). optional (). describe ( 'Customer email' ),
}) ,
execute : async ( state , args ) => {
const databaseUrl = await state . env ( 'DATABASE_URL' );
let customer ;
if ( args . customerId ) {
customer = await lookupCustomer ( databaseUrl , { customerId: args . customerId });
} else {
customer = await lookupCustomer ( databaseUrl , { email: args . email });
}
if ( ! customer ) {
return {
status: 'error' ,
error: 'Customer not found' ,
};
}
return {
status: 'success' ,
result: JSON . stringify ( customer ),
};
} ,
}) ;
import { defineTool } from '@standardagents/spec' ;
import { z } from 'zod' ;
export default defineTool ({
description: 'Fetch current weather for a city' ,
args: z . object ({
city: z . string (). describe ( 'City name' ),
units: z . enum ([ 'metric' , 'imperial' ]). default ( 'metric' ),
}) ,
execute : async ( state , args ) => {
const apiKey = await state . env ( 'WEATHER_API_KEY' );
try {
const response = await fetch (
`https://api.weather.com/v1/current?city= ${ args . city } &units= ${ args . units } &key= ${ apiKey } `
);
if ( ! response . ok ) {
return {
status: 'error' ,
error: `Weather API error: ${ response . status } ` ,
};
}
const data = await response . json ();
return {
status: 'success' ,
result: `Weather in ${ args . city } : ${ data . temperature } °, ${ data . conditions } ` ,
};
} catch ( error ) {
return {
status: 'error' ,
error: `Failed to fetch weather: ${ error . message } ` ,
};
}
} ,
}) ;
Use queueTool to chain multiple tools:
import { defineTool } from '@standardagents/spec' ;
import { queueTool } from '@standardagents/builder' ;
import { z } from 'zod' ;
export default defineTool ({
description: 'Process order and queue follow-up tasks' ,
args: z . object ({
orderId: z . string (). describe ( 'Order ID' ),
}) ,
execute : async ( state , args ) => {
const order = await validateOrder ( args . orderId );
if ( ! order . valid ) {
return {
status: 'error' ,
error: 'Order validation failed' ,
};
}
// Queue additional tools to execute after this one
queueTool ( state . rootState , 'charge_payment' , {
orderId: args . orderId ,
amount: order . total ,
});
queueTool ( state . rootState , 'send_confirmation_email' , {
userId: order . userId ,
orderId: args . orderId ,
});
return {
status: 'success' ,
result: `Order ${ args . orderId } queued for processing` ,
};
} ,
}) ;
Returning File Attachments
export default defineTool ({
description: 'Generate an image based on a text prompt' ,
args: z . object ({
prompt: z . string (). describe ( 'Text description of the image' ),
}) ,
execute : async ( state , args ) => {
const image = await generateImage ( args . prompt );
return {
status: 'success' ,
result: `Generated image for: " ${ args . prompt } "` ,
attachments: [{
name: `generated- ${ Date . now () } .png` ,
mimeType: 'image/png' ,
data: image . base64 ,
width: 1024 ,
height: 1024 ,
}],
};
} ,
}) ;
ThreadState Utilities
AgentBuilder provides utility functions for tools:
Add a message to conversation without triggering execution await injectMessage ( state , {
role: 'system' ,
content: 'Important context update' ,
});
Retrieve message history const messages = await getMessages ( state , 20 );
Send custom events to frontend emitThreadEvent ( state , 'progress' , {
step: 3 ,
total: 10 ,
});
See the ThreadState documentation for complete details.
Best Practices
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.' ,
// ...
});
Avoid: defineTool ({
description: 'Search stuff' ,
// ...
});
Use .describe() on every parameter: args : z . object ({
query: z . string (). describe ( 'Search query - supports partial matches' ),
category: z . string (). optional (). describe ( 'Product category filter' ),
minPrice: z . number (). optional (). describe ( 'Minimum price in USD' ),
})
Return errors as tool results, don’t throw: try {
const result = await riskyOperation ( args );
return {
status: 'success' ,
result: JSON . stringify ( result ),
};
} catch ( error ) {
return {
status: 'error' ,
error: `Operation failed: ${ error . message } ` ,
};
}
File names should use snake_case: Good: search_database.ts, create_ticket.tsAvoid: SearchDatabase.ts, createTicket.ts
Use rootState for Queueing
When queueing tools from sub-prompts, use state.rootState: // Correct
queueTool ( state . rootState , 'next_tool' , args );
// Incorrect (may not work in sub-prompts)
queueTool ( state , 'next_tool' , args );
Next Steps
API Reference View complete tool API specification
ThreadState Learn about execution context and utilities
Prompts Configure prompts that use tools
Examples Explore real-world tool patterns