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.

ToolWhat it doesIn "read-only"?
readRead a file in the workspace.yes
writeCreate or overwrite a file.no
editEdit a file in place.no
lsList a directory.yes
grepSearch file contents.yes
globFind files by pattern.yes
bashRun a shell command.no
apply_patchApply a patch to files.no
diagnosticsSurface code diagnostics.yes
clockRead the current time.yes
todoKeep a working todo list.yes
webfetchFetch a web page.yes
httpMake an HTTP request.no
web_searchSearch the web.yes
artifactsStore an artifact with the run.no
subagentSpawn a child agent to isolate or fan out a subtask.no
run_codeCall 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:

OptionWhat it does
attachmentsPuts 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.
maxIterationsCaps 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.
sessionHands the leaf a browser from computer.openBrowser(). See Browser use.