Code mode
This feature is experimental. Breaking changes may occur without a major version bump until the API is stable.
Code mode gives an agent a single tool that runs a short TypeScript program in a sandbox. Instead of calling tools one at a time, the model writes a program that orchestrates your existing tools as external_* functions, batching calls with Promise.all, aggregating with reduce, branching, and doing math in a real runtime, then returns a single result.
createCodeMode returns this tool with the default id execute_typescript. The id is configurable, so an agent can have several code mode tools at once, each scoped to a different set of tools (see Scoping tools across multiple code tools).
When to use code modeDirect link to When to use code mode
Use code mode when a task touches several tools at once or needs real computation between calls:
- Fewer round-trips: a task that touches several tools runs in one tool call instead of one model turn per tool.
- Correct math: sums, averages, and other arithmetic run as JavaScript, not as token prediction.
- Planning up front: filtering, aggregation, and branching happen inside the program rather than across separate turns.
How it worksDirect link to How it works
Your tools keep running on the host with full validation, request context, and tracing. Only the model's orchestration code runs in the sandbox. Each external_* call is bridged back to the real tool on the host, so dangerous tools, approvals, and validation behave exactly as they do for normal tool calls.
The program runs in a Workspace sandbox. A sandbox is required, because code mode runs model-authored code and the execution boundary must be chosen deliberately. Pass one via sandbox, or run the agent in a workspace that provides one. To execute on the host machine, pass new LocalSandbox() explicitly. This runs the program as a host node process with host privileges, so only use it for trusted or local development.
QuickstartDirect link to Quickstart
createCodeMode returns the tool plus generated instructions. With no id, the tool is named execute_typescript. Add both to your agent:
import { Agent } from '@mastra/core/agent'
import { createCodeMode, createTool } from '@mastra/core/tools'
import { LocalSandbox } from '@mastra/core/workspace'
import { z } from 'zod'
const getTopProducts = createTool({
id: 'getTopProducts',
description: 'Get top selling products',
inputSchema: z.object({ limit: z.number() }),
outputSchema: z.object({
products: z.array(z.object({ id: z.string(), name: z.string(), totalSales: z.number() })),
}),
execute: async ({ limit }) => fetchTopProducts(limit),
})
const getProductRatings = createTool({
id: 'getProductRatings',
description: 'Get ratings for a product',
inputSchema: z.object({ productId: z.string() }),
outputSchema: z.object({ ratings: z.array(z.object({ score: z.number() })) }),
execute: async ({ productId }) => fetchRatings(productId),
})
const { tool, instructions } = createCodeMode({
tools: { getTopProducts, getProductRatings },
sandbox: new LocalSandbox(), // required; runs on the host — see "How it works"
})
const agent = new Agent({
name: 'shop-assistant',
instructions: ['You are a helpful shopping assistant.', instructions],
model: 'openai/gpt-5.4',
tools: { execute_typescript: tool },
})
Asked "What are the top 5 products and the average rating for each?", the model emits one execute_typescript call instead of many separate tool calls:
const top = await external_getTopProducts({ limit: 5 })
const ratings = await Promise.all(
top.products.map(p => external_getProductRatings({ productId: p.id })),
)
return top.products.map((product, i) => {
const scores = ratings[i].ratings.map(r => r.score)
const avg = scores.reduce((sum, s) => sum + s, 0) / scores.length
return {
name: product.name,
sales: product.totalSales,
averageRating: Math.round(avg * 100) / 100,
}
})
All five rating lookups run in parallel, the averages are computed in JavaScript, and the agent receives one structured result.
ConfigurationDirect link to Configuration
const { tool, instructions } = createCodeMode({
tools: { getTopProducts, getProductRatings }, // exposed as external_*; only these can be called
sandbox, // required WorkspaceSandbox, unless the agent runs in a workspace that provides one
timeout: 30_000, // optional execution timeout in ms (default 30000)
id: 'execute_typescript', // optional tool id (default "execute_typescript")
})
| Option | Type | Description |
|---|---|---|
tools | ToolsInput | Tools exposed to the program as external_<id>. Only these may be called. |
sandbox | WorkspaceSandbox | Sandbox to run the program in. Required unless the agent runs in a workspace that provides one. Pass new LocalSandbox() to run on the host. |
timeout | number | Execution timeout in milliseconds. Default 30000. |
id | string | The generated tool's id. Default execute_typescript. |
ResultDirect link to Result
The tool returns a CodeModeToolResult:
type CodeModeToolResult = {
success: boolean
result?: unknown // value returned by the program
logs?: string[] // captured console output
error?: { message: string; name?: string; line?: number }
}
Inspecting the instructionsDirect link to Inspecting the instructions
The generated instructions contain the usage contract and a typed external_* declaration for each tool, derived from your tool schemas. Print them to see exactly what the model receives:
import { createCodeModeInstructions } from '@mastra/core/tools'
console.log(createCodeModeInstructions({ tools: { getTopProducts, getProductRatings } }))
Scoping tools across multiple code toolsDirect link to Scoping tools across multiple code tools
createCodeMode captures its own allow-list. Call it more than once to give an agent several code tools, each scoped to a different subset of tools. A program can only call the external_* functions for the tools passed to its own createCodeMode call, so the subsets stay isolated.
Give each tool a distinct id so their ids do not collide, and add each tool's instructions to the agent:
const sales = createCodeMode({
id: 'sales_code',
tools: { listRecentOrders, getCustomer },
sandbox,
})
const inventory = createCodeMode({
id: 'inventory_code',
tools: { listProducts, getSupplier },
sandbox,
})
const agent = new Agent({
name: 'ops-assistant',
instructions: ['You are an ops assistant.', sales.instructions, inventory.instructions],
model: 'openai/gpt-5.4',
tools: { sales_code: sales.tool, inventory_code: inventory.tool },
})
A program run by sales_code cannot call an inventory tool, and the reverse holds too. Use this for least-privilege scoping and to keep each tool's prompt surface small.
TipsDirect link to Tips
- Keep tools focused, so each does one thing well and the model composes them in code.
- Code mode helps most when calls can be parallelized with
Promise.all. - Use
console.logfor debugging. Logs are captured in the result.