Agent config
bot/agent.ts default-exports defineAgent(config), which sets the model
that runs the agent and the defaults for a local run. Every field is optional on
the root agent.
import { defineAgent } from "@grok/agent-sdk";export default defineAgent({ model: { id: "grok-4.5", params: [ { id: "effort", value: "high" }, { id: "fast", value: "true" }, ], }, // optional; this is the default});Agent fields
| Field | Type | Meaning |
|---|---|---|
model | string or { id, params } | Cursor model for turns. Defaults to grok-4.5 with effort=high and fast=true. Subagents omit it to inherit. |
name | string | Display name. Defaults to the package or directory name. |
description | string | What the agent is for. Required on subagents, where the parent model reads it to decide when to delegate. |
instructions | string | Inline instructions for subagents and generated configs. Prefer instructions.md. |
local | { cwd?, workspaceDir?, sandbox? } | Defaults for a local run. See Local options. |
hosting | { egressDomains?, secretNames? } | The egress allowlist and expected secret names that agent-sdk deploy reads. Mounted extensions add their own. |
concurrency | { maxRunningTurns? } | Agent-wide limit on concurrent turns. See Concurrency. |
builtinTools | { reminders? } | Opt-in tools that Agent SDK provides. See Built-in tools. |
tools | ToolName[] | Allowlist of built-in tools. Omit it for the full standard toolset. See Harness tools. |
fs | FileSystem or (ctx) => FileSystem | Custom file tree for the session. root is the deprecated name. See Custom filesystem. |
skills | false or (ctx) => FileSystem | Replaces the skills tree mounted at /agent/skills, or turns that mount off. |
The persistent field was removed. Discovery rejects a config that still sets it, and the error is persistent was removed. Delete it from defineAgent., so validate, dev, and deploy fail until you delete the field. A deployment that is already running keeps serving until you stop or delete it.
Model
Effort and speed are params, not id suffixes, so Agent SDK rejects an id like
grok-4.5-fast. Use the object form shown above to set params, or a plain
string when you don't need any:
model: "composer-2.5",Local options
local.workspaceDir points every session at one shared working directory, for
agents that work inside an existing checkout. It takes precedence over cwd,
and a per-send workspaceDir wins over both. Sessions share that tree, so one
turn's files are visible to the next.
local.sandbox runs the agent inside Cursor's local sandbox. It's off by
default, so shell commands auto-approve and inherit the serve process
environment, including the host's credentials. Turn it on when turns read
untrusted input, such as webhook payloads, PR diffs, or inbound chat. The
sandbox limits the tools themselves, so an injected prompt can't talk the model
past it.
Agent SDK runs the file tools (read, edit, delete, grep, glob, ls)
in the serve process, outside the sandbox. With local.sandbox on, they refuse
a path outside the session workspace with Path is outside the workspace. With
it off, they can reach any path the serve process can.
Local cwd
local.cwd sets the parent directory for local session workspaces. Each
session uses <cwd>/<sessionId> unless a per-send workspaceDir overrides it.
Session workspaces are real Cursor project directories, so the local agent reads
AGENTS.md and .cursor config from every ancestor directory. When you omit
cwd and the agent sits inside another git repo, such as a monorepo package,
Agent SDK uses a per-project directory under ~/.cache instead, so the
enclosing checkout's rules, skills, and MCP servers stay out of the turn. An
agent at its own git root keeps its workspaces inside the project. Point cwd
at a checkout only when the agent should inherit that tree.
Custom filesystem
fs is mounted at /, and Agent SDK mounts /tmp and /agent/skills over it. Set fs or the deprecated root, not both. A tree that already has /agent/skills fails session start unless skills is false.
Harness tools
Use tools to limit which built-in Cursor tools the model can call.
When you set it, the model gets only the tools you list. An empty list turns
off every built-in tool, and new platform tools stay off until you add them.
export default defineAgent({ model: "composer-2.5", // Read-only triage: no shell, no edits, no subagents. tools: ["read", "grep", "glob", "ls"],});Agent SDK always adds "mcp" to a configured allowlist, because the server
tools in bot/tools/ reach the model over MCP. With local.sandbox on, MCP
tool calls fail closed. An enclosing checkout can also add MCP servers, as
Local cwd explains.
Use the Cursor SDK's public tool names, such as "shell", "read",
"edit", "grep", "glob", "ls", and "task". An unknown name fails the
turn with a ConfigurationError, and "SemanticSearch" or "semSearch"
fails validation.
Two names have broader effects:
"shell"also grants shell input. Tools withexecution: "agent"need it to run their scripts, and discovery warns when your allowlist blocks them."task"lets the root agent start subagents. Each subagent keeps its own toolset.
The allowlist doesn't isolate the serve host, so pair it with local.sandbox
for untrusted input.
Migrate off semantic search
Semantic search is removed, and agent-sdk validate reports
Semantic search has been removed. Use Grep. while a project still uses it.
A turn fails or misbehaves lists what to
delete and what to use instead.
Concurrency
concurrency.maxRunningTurns caps how many model turns run at once across the agent's sessions. It takes a positive integer up to 200. Omit it for no limit.
When every slot is busy, a new turn queues in arrival order instead of failing. The stream records a turn.queued event with the queue
position, GET /v1/sessions reports queued: true, and the turn starts as
soon as a slot frees.
A queued turn still counts as running. A follow-up preempts it, and a direct
write-effect tool call gets 409 session_busy.
export default defineAgent({ concurrency: { maxRunningTurns: 3 },});Built-in tools
builtinTools opts into tools that Agent SDK provides. They behave like your
own server tools in turns, direct calls, info, and the playground. A tool you
write with the same name wins, with a warning.
builtinTools: { reminders: true } adds reminders_create, reminders_list,
and reminders_cancel, bound to the current conversation. A session without a
continuation key can't set reminders. See
Schedules and reminders.
Serve programmatically
serve(dirOrProject, options) embeds the server in your own process:
import { serve } from "@grok/agent-sdk";const handle = await serve("./my-agent", { port: 3000,});console.log(`listening on ${handle.url}`);// handle.callTool(...), handle.dispatchSchedule("heartbeat"),// handle.createReminder(...), handle.agents, await handle.close()handle.agents lists each mount as { slug, url, info }. handle.project is
deprecated.
serve() takes the same host settings as the CLI serve flags,
plus discovery for project-loading options and mode: "single" | "multi".
Pass apiKey, or sign in the same way as the CLI, as
Environment variables describes.
Inspect a project in tests
loadProject(dir) reads one project without serving it. Its info matches the
GET /v1/info body.
import { loadProject } from "@grok/agent-sdk";const { info } = await loadProject("./my-agent", { strict: true });expect(info.tools.map((t) => t.name)).toContain("inspect_pr");loadProject throws ProjectLoadError when discovery reports an error, and
{ strict: true } makes it throw on a warning too. error.diagnostics lists
the findings, and each path is relative to the project directory. serve
accepts the loaded project or a directory path.