Skip to content

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.

ts
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 ​

FieldTypeMeaning
modelstring or { id, params }Cursor model for turns. Defaults to grok-4.5 with effort=high and fast=true. Subagents omit it to inherit.
namestringDisplay name. Defaults to the package or directory name.
descriptionstringWhat the agent is for. Required on subagents, where the parent model reads it to decide when to delegate.
instructionsstringInline 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.
toolsToolName[]Allowlist of built-in tools. Omit it for the full standard toolset. See Harness tools.
fsFileSystem or (ctx) => FileSystemCustom file tree for the session. root is the deprecated name. See Custom filesystem.
skillsfalse or (ctx) => FileSystemReplaces 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:

ts
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.

ts
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 with execution: "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.

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.

ts
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:

ts
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.

ts
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.