Agent config
bot/agent.ts default-exports defineAgent(config): which model runs
the agent and its local harness defaults.
Everything 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, fast=true on the root agent. Subagents omit it to inherit. |
name | string | Display name override. Defaults to the package name or directory name. |
description | string | What the agent is for. Required on subagents; the parent model reads it to decide when to delegate. Documentation-only on the root. |
instructions | string | Inline instructions. Prefer instructions.md; this exists for subagents and generated configs. |
local | { cwd?, workspaceDir?, sandbox? } | Local harness defaults. See Local options. |
persistent | boolean | Keep one long-lived runtime for the hosted deployment. Defaults to false. See Persistent hosting. |
hosting | { egressDomains?, secretNames?, image? } | Managed-hosting declarations read by agent-sdk deploy: the deployment's egress allowlist, the secret names the agent expects, and a container image for a persistent agent. Mounted extensions add their own. Ignored by local serving. |
concurrency | { maxRunningTurns? } | Agent-wide concurrent turn limit. See Concurrency. |
builtinTools | { reminders? } | Framework-provided model-facing tools, opted in per capability. See Built-in tools. |
tools | ToolName[] | Allowlist of built-in harness tools offered to the model. Unset = the model's 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. |
Model
model is a Cursor model id string, or { id, params }. Effort and
speed are params, not id suffixes. Agent SDK rejects suffix-style ids
like grok-4.5-fast:
model: { id: "grok-4.5", params: [ { id: "effort", value: "high" }, { id: "fast", value: "true" }, ],}A plain string works when you don't need params:
model: "composer-2.5",Local options
local sets local-harness defaults.
local.workspaceDir points every session at one shared harness cwd,
for agents that work inside an existing checkout. It takes precedence
over cwd, and a per-send workspaceDir still wins over both.
Sessions share a working tree, so a file one turn writes is visible to
the next.
local.sandbox runs the harness inside Cursor's local sandbox. It's
off by default: shell then auto-approves and inherits the serve process
environment, including any credentials the host holds. Turn it on for
agents whose turns read untrusted input (webhook payloads, PR diffs,
inbound chat). The boundary is in the tools, not in the prompt, so it
holds when an injected prompt tells the model to ignore its instructions.
BDK runs the file tools (read, edit, delete, grep, glob, ls)
in the serve process, outside the sandbox. With local.sandbox on, these
tools refuse a path outside the session workspace, after symlinks, with
Path is outside the workspace. The read tools can also read the
large-output files that web search and web fetch write. With
local.sandbox off, the file tools can read and write any path the serve
process can.
Local cwd
local.cwd sets the parent directory for local harness workspaces. Each
session uses <cwd>/<sessionId> unless a per-send workspaceDir overrides
it.
Session workspaces are real Cursor project directories. The harness loads
AGENTS.md and .cursor config from ancestor directories. An agent nested
in another git repo (a monorepo package) defaults to a per-project
cache directory under ~/.cache when you omit cwd, so the enclosing checkout
does not leak rules, skills, or MCP servers into the turn. A standalone git
root keeps the in-project session workspace. Point cwd at a checkout only
when the agent should inherit that tree.
Persistent hosting
By default, a hosted deployment starts a runtime when an event arrives
and stops it when the work ends. Set persistent: true to keep one
long-lived runtime for the deployment instead, with its state directory
on the runtime's own disk. On a redeploy, the new version starts beside
the old one on the same state, and the old version stops after the new
one takes over. Each team can have one persistent deployment. Local
serving ignores the flag.
export default defineAgent({ persistent: true, hosting: { image: "incident-bot-base" },});hosting.image names a container image that your team pushed with
agent-sdk image push. The agent runs inside that image instead of the
default Node 22 image. Only persistent agents can use it, and deploy
rejects hosting.image on any other agent. A deploy pins the image it
finds, so after you push a new image under the same name, deploy again
to pick it up.
Custom filesystem
fs is mounted at /. Agent SDK mounts /tmp and /agent/skills over it.
root is the deprecated name of fs. Set one of them. A tree that already
has /agent/skills fails session start unless skills is false.
Semantic search is removed. Use Grep. See Migrate off semantic search.
Harness tools
Use tools to limit which built-in Cursor harness tools the model can
call. Omit it to keep the standard toolset. When you set it, the model
gets only the tools you list. An empty list disables all native
built-in tools. New platform tools stay disabled until you add them.
export default defineAgent({ model: "composer-2.5", // Read-only triage agent: search and read only. // No shell, no edits, no subagents. tools: ["read", "grep", "glob", "ls"],});Agent SDK always adds "mcp" to a configured allowlist, because
authored server tools in bot/tools/ reach the model over MCP. MCP
can also expose declared connections and servers from the harness
directory's ambient .cursor config. To exclude a checkout's MCP
servers, point local.cwd outside the checkout. See
Local cwd. local.sandbox makes MCP tool calls fail
closed.
Use the Cursor SDK's public tool names, including "shell", "read",
"edit", "grep", "glob", "ls", and "task". Unknown names
fail the turn with a ConfigurationError. "SemanticSearch" and
"semSearch" fail validation. Use "grep". See
Migrate off semantic search.
Two names have broader effects:
"shell"also grants shell input. Tools withexecution: "agent"need it to run their scripts. Discovery warns when your allowlist would prevent those tools from running."task"lets the root agent start subagents. Each subagent keeps its own curated toolset.
The allowlist controls which tools the model can call. It does not
isolate the serve host. For agents that process untrusted input, also
set local: { sandbox: true }.
Migrate off semantic search
Semantic search is removed. Use Grep.
Delete FileSystem.search from a custom fs. Content search is grep. A
GrepQuery has a regular expression pattern, and can set path,
glob, and maxResults. A GrepResult has path, lineNumber, and
line.
Delete imports of SearchQuery and SearchResult. SearchQuery
carried a natural-language query, plus optional maxResults and
targetDirectories. SearchResult carried path, score, and
snippet. Import GrepQuery and GrepResult from @grok/agent-sdk.
List "grep" in tools. Remove "SemanticSearch" and "semSearch".
Delete bot/tools/SemanticSearch.ts or bot/tools/semSearch.ts. In
instructions.md, tell the model to call Grep with pattern, and
with path or glob when the lookup should stay in one directory.
export default defineAgent({ tools: ["read", "grep"],});agent-sdk validate reports Semantic search has been removed. Use Grep.
when tools lists either name, or a file in bot/tools/ uses it.
Concurrency
concurrency.maxRunningTurns caps how many model turns run at once
across all of the agent's sessions (positive integer, hard cap 200).
When every slot is busy, newly admitted turns queue FIFO instead of
failing: the stream records a durable turn.queued event with the
queue position, GET /v1/sessions reports queued: true, and each
queued turn starts as soon as a slot frees. A queued turn still counts
as running for busy semantics: follow-ups preempt it, and direct
write-effect tool calls get 409 session_busy. Omit for unlimited.
export default defineAgent({ concurrency: { maxRunningTurns: 3 },});Built-in tools
builtinTools opts into framework-provided model-facing tools. Each
enabled capability shows up as ordinary server tools, so turns, direct
calls, info, and the playground treat them like authored tools. Authored tools with the same name win, with a
warning, and like all server tools they run on the local runtime.
builtinTools: { reminders: true } adds three tools bound to the
current conversation over host.reminders: reminders_create,
reminders_list, and reminders_cancel. Sessions without a
continuation key can't arm 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, and info. handle.project is the first agent's project. It is deprecated.
Host settings match the documented CLI serve flags.
serve() also accepts discovery (project-loading options) and
mode: "single" | "multi". Pass apiKey or use the same Cursor
credential as the CLI: CURSOR_API_KEY, CURSOR_API_KEY_FILE,
CURSOR_SERVICE_ACCOUNT_KEY, or agent-sdk login.
Inspect a project in tests
loadProject(dir) reads one project and does not serve it. The result is a Project. The only field is info. That object matches the GET /v1/info body for the project. agent-sdk info --json on a project directory prints { agents: [{ slug, ...info }] }. It prints the bare object only when the single mount slug is empty.
import { loadProject } from "@grok/agent-sdk";const project = await loadProject("./my-agent");console.log(project.info.name);console.log(project.info.tools.map((tool) => tool.name));loadProject throws ProjectLoadError when discovery reports an error. Pass { strict: true } to throw on a warning as well. error.diagnostics lists the findings. Each path is relative to the project directory. A path outside that directory starts with ../. loadProject does not take importModule. serve still accepts discovery.importModule for that override.
info.local is { cwd } when the project sets a cwd. The serve banner reads agents[].info.
serve accepts that Project or a directory path.
Related
- Instructions: the required half of a minimal agent
- CLI: the
serveflagsserve()accepts - Sessions: workspaces, identifiers, and turn admission
- Schedules: reminder tools opted in here