Equipping agents
Every agent()already has a working tool belt: by default it can read, edit, and run things in the run's workspace with no setup. This page is the guide to shaping that belt per call: narrowing the built-ins, adding your own tools, connecting MCP servers, loading skills, and giving an agent memory and a working directory. These are all per call: two agent() calls in the same workflow can carry entirely different tools, servers, and skills, and there are no workflow-level capability fields. The option signatures live on the SDK reference.
Scoping the built-in tools
Scope the belt with builtins: "read-only" is the non-mutating set, handy for an agent that reads untrusted input; "none"removes them all, which is what you want for a classifier or judge reading content you don't control; or pass an explicit list of names.
| Tool | What it does | In "read-only"? |
|---|---|---|
read | Read a file in the workspace. | yes |
write | Create or overwrite a file. | no |
edit | Edit a file in place. | no |
ls | List a directory. | yes |
grep | Search file contents. | yes |
glob | Find files by pattern. | yes |
bash | Run a shell command. | no |
apply_patch | Apply a patch to files. | no |
diagnostics | Surface code diagnostics. | yes |
clock | Read the current time. | yes |
todo | Keep a working todo list. | yes |
webfetch | Fetch a web page. | yes |
http | Make an HTTP request. | no |
web_search | Search the web. | yes |
artifacts | Store an artifact with the run. | no |
subagent | Spawn a child agent to isolate or fan out a subtask. | no |
run_code | Call the other tools from a code snippet. | no |
Three more appear only when you ask for them, so they are not in the table: skill (when the call pins skills), human_input (when humanInput: true), and find_tools (when a mcp server brings a tool set large enough to defer behind a search).
Two built-ins stand apart. subagent lets an agent spawn a child agent to isolate or fan out a subtask. The child runs one level deep (it gets no subagenttool of its own) with at most the parent's tools, and returns only its result, keeping the parent's context clean. It shares the run's budget. run_code lets the model call its other tools from a code snippet instead of one round-trip per call, which is how a step that touches many items stays cheap. Both are on by default, both drop under "none" and "read-only", and an explicit list must name them to get them back.
builtins: "none"is also how you quarantine untrusted input at the tool level: an agent that reads public tickets, user reports, or scraped pages holds no tools and no secrets, so it can do nothing but describe what it reads, and prompt injection can't cross into your trusted code. The full shape is the quarantine pattern.
// A judge reading content you don't control: no tools at all.
const { verdict } = await agent<Verdict>(`Is this correct? ...`, {
builtins: "none",
schema: VERDICT,
});Inline tools
Add your own tools on top: each is a typed function that runs in your program (the trusted layer), so its return value, not a model guess, comes back:
const result = await agent("Open the highest-priority ticket and draft a reply.", {
builtins: "read-only",
tools: [
{
name: "get_ticket",
description: "Fetch a ticket by id.",
inputSchema: {
type: "object",
properties: { id: { type: "string" } },
required: ["id"],
},
execute: async ({ id }) => fetchTicket(id as string), // real code, real data
},
],
});MCP servers
Point an agent at an external MCP server with mcp:
await agent("Triage and label this issue.", {
mcp: [
{ name: "linear", transport: "http", url: "https://mcp.linear.app", headers: { Authorization: `Bearer ${token}` } },
],
});An MCP server is either { transport: "http", url, headers? } or { transport: "stdio", command }. On hosted runners only HTTP servers are reachable (a remote endpoint, authenticated with a header you can build from a secret); stdio is for local servers under self-hosting. The connected tools are namespaced <server>__<tool>so two servers can't collide.
Skills
Load reusable instructions from your program package's skills/ directory with skills. The directory always ships with the package, so a skill deploys with the code that uses it:
await agent("Triage and label this issue.", {
skills: ["triage-rubric"], // skills/triage-rubric/SKILL.md, shipped in the package
});Memory
memory is a workspace-relative directory the agent reads and writes; it is persisted across runs automatically, so an agent can accumulate notes over time:
await agent("Groom the backlog; skip anything you've already triaged.", {
memory: "notes", // a workspace dir this agent keeps across runs
});cwd
cwd points an agent at a workspace subdirectory: its file tools resolve and stay inside that directory, bashstarts there, and the agent is told that directory's layout. A run that clones three repos can give each agent one checkout, so every path in its prompts and findings is clean and repo-relative:
await parallel(repos.map((repo) => () =>
agent(`Fix the failing tests in this repo.`, { cwd: `checkouts/${repo}` })
));The directory must already exist (clone or mkdir it in program code first). memory stays relative to the workspace root, and a subagent inherits its parent's cwd.
Attachments, iteration caps, sessions
Three smaller dials round out the call:
| Option | What it does |
|---|---|
attachments | Puts files the model can actually see in front of it, images and documents, each carrying a mimeType plus either inline base64 data or a url. Text and source belong in the prompt or behind the read tool instead. |
maxIterations | Caps the leaf's tool-calling turns; a cost guardrail, not a correctness one. Going past it doesn't error the call: the model is simply asked once more with its tools withheld so it has to answer from the work it already did. |
session | Hands the leaf a browser from computer.openBrowser(). See Browser use. |