Skip to main content

Command Palette

Search for a command to run...

BDK

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

FieldTypeMeaning
modelstring 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.
namestringDisplay name override. Defaults to the package name or directory name.
descriptionstringWhat the agent is for. Required on subagents; the parent model reads it to decide when to delegate. Documentation-only on the root.
instructionsstringInline instructions. Prefer instructions.md; this exists for subagents and generated configs.
local{ cwd?, workspaceDir?, sandbox? }Local harness defaults. See Local options.
persistentbooleanKeep 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.
toolsToolName[]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 with execution: "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.

  • Instructions: the required half of a minimal agent
  • CLI: the serve flags serve() accepts
  • Sessions: workspaces, identifiers, and turn admission
  • Schedules: reminder tools opted in here