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 "@cursor/bdk";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 bdk 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. |
Model
model is a Cursor model id string, or { id, params }. Effort and
speed are params, not id suffixes. BDK 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); it's a tool boundary, not a prompt-level one.
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
bdk images 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.
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"],});BDK 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.
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 }.
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 "@cursor/bdk";const handle = await serve("./my-agent", { port: 3000, apiKey: process.env.CURSOR_API_KEY,});console.log(`listening on ${handle.url}`);// handle.callTool(...), handle.dispatchSchedule("heartbeat"),// handle.createReminder(...), handle.project, await handle.close()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 bdk login.
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