Saltar al contenido principal
Versión: 0.18.0

AgentSpec

AgentSpec<TConfig> is everything about an agent's domain that the kit needs but doesn't decide: its system prompt, the MCP servers it always has, the folders with its skills and commands, and its subagents, plus a few optional knobs. TConfig is your config type (any type extending BaseSessionConfig); every method receives the full config, so it can depend on your own fields.

import type { AgentSpec, BaseSessionConfig } from "@falkenslab/agent-kit";

interface Config extends BaseSessionConfig {
courseUrl: string;
teacherName: string;
}

const spec: AgentSpec<Config> = {
buildSystemPrompt: (config) => `You help ${config.teacherName} with the course at ${config.courseUrl}.`,
buildMcpServers: () => ({}),
pluginRoots: () => [],
buildSubagents: () => undefined,
};
información
Why not AgentDefinition?

The SDK already uses AgentDefinition for a single subagent's definition. AgentSpec is a level above: it decides, among other things, which subagents get registered.

Required members​

buildSystemPrompt(config): string​

The system prompt for this session. Everything role-, mode- or domain-specific belongs here. The kit appends to it:

Long prompts are easier to maintain as files: see Prompts and createPromptLoader().

buildSystemPrompt: (config) =>
loadPrompt(config.mode === "autonomous" ? "system-autonomous.md" : "system.md", { course: config.courseUrl }),

buildMcpServers(config, runDir): Record<string, McpServerConfig>​

The MCP servers this agent always registers, keyed by server name. The kit adds its own next to them when they apply (approvals, manual intervention, the sources tools, date and time), so don't register those. runDir is this run's folder, useful for a server that writes files (a browser that downloads into it, say).

buildMcpServers: (config, runDir) => ({
inventory, // an in-process server built with createSdkMcpServer()
playwright: {
command: "npx",
args: ["@playwright/mcp@latest", "--output-dir", runDir],
},
}),

Every mcp__<server>__<tool> call is approved automatically (see Permissions); block the ones you don't want with disallowedTools. More in Tools and MCP servers.

pluginRoots(config): string[]​

Absolute paths of local plugins (folders with .claude-plugin/plugin.json, skills/ and commands/) to load, in order. Return [] for none. See Skills and plugins.

pluginRoots: () => [path.join(__dirname, "plugin")],

buildSubagents(config)​

The subagents this configuration registers, and which of them the Agent tool may spawn, or undefined for none:

buildSubagents: () => ({
agents: {
"joke-finder": {
description: "Searches the web for short jokes and returns candidates with their source.",
prompt: "Find 2 or 3 short, clean jokes on the web. Return a numbered list with each joke and its URL.",
tools: ["WebSearch", "WebFetch"],
model: "haiku",
maxTurns: 8,
},
},
allowedSubagentTypes: ["joke-finder"],
}),

Returning something here also gives the session the Agent tool, and Bash when a subagent lists it or has no tools (Bash for subagents only), and registers the three subagent gates. Return undefined when this configuration needs no subagents, so neither tool is granted. See Subagents.

Optional members​

MemberDefaultWhat it does
identity?: AgentIdentitynoneWho the agent is (name, version, description): told to the model with agent-kit's version, and the agent-help skill. See identity and helpGuide.
helpGuide?: stringnoneAbsolute path of a markdown guide to the agent's own domain, which the agent-help skill includes. Only with identity.
disallowedTools?: string[][]Tools blocked whatever else allows them, e.g. an MCP tool you don't trust. Takes precedence over everything. A built-in tool also leaves the context; an MCP tool is blocked but its definition is still sent.
saveToSourcesDescription?: stringa generic descriptionThe description of the save_to_sources tool (registered with sourcesDir), in your domain's words.
knowledgeBase?: booleantrueWith knowledgeDir, the built-in knowledge base (rules, plugin and tools). false for an agent with its own rules for its notes.
knowledgePageTypes?: PageType[]noneThe agent's own page types, besides the kit's. See Your own page types.
knowledgeStore?(config): KnowledgeStorethe kit's store over knowledgeDir's filesAnother store (a database, a vector store). See Knowledge store.
humanApprovalTexts?generic texts{ description, approved, rejected } of the approval tool (guided mode).
manualInterventionTexts?noneIts texts, and the opt-in, of the manual-intervention tool.
replyInLanguage?: booleantrueThe line asking the agent (and each subagent) to reply in the kit's language.
settingSources?: SettingSource[]["project"]Which Claude Code settings files the session loads.
skills?: string[] | "all" | "plugins""all"Which skills the agent offers: every one found, only its plugins' ("plugins"), or a list.
planMode?: PlanModeSpecnoneIn plan mode, the agent's plan files and its read-only MCP tools.

identity and helpGuide​

Without an identity, the model only knows what your prompt says about it, and nothing about the chat it runs in: asked "how do I go back to yesterday's conversation?" or "what does Shift+Tab do?", it improvises. With one, the kit does two things:

  • It appends a "Who you are" section to the system prompt: the agent's name, version and what it is, and the version of agent-kit it runs on.
  • It offers the agent-help skill (agent-kit:agent-help, added to your skills list if you have one). The skill holds the kit's part, the same for every agent: the modes and how to switch them, approvals and questions, the chat's keys and slash commands, resuming, --continue and --language. It adds this session's facts (the starting mode, which modes Shift+Tab goes through, the knowledge base's commands, the sources folder) and your helpGuide for everything domain-specific. The model answers from it, and says so when something isn't covered instead of inventing an option.
const { version } = JSON.parse(readFileSync(path.join(__dirname, "package.json"), "utf8")) as { version: string };

const spec: AgentSpec<Config> = {
// …
identity: { name: "padawan", version, description: "an agent that takes a Moodle course as a student" },
helpGuide: path.join(__dirname, "guide.md"),
};

Only name is required. Write the guide in English, like the rest of what the model reads, and for the person's questions: the agent's own commands, its configuration (environment variables, config files), its folders, what it can do. Leave out the chat: the kit's part already covers it and changes with the kit. The guide is read when the session starts and goes inside the skill (written to <runDir>/agent-help/), so the agent needs no file tools to read it, and it costs no context until the skill is loaded.

disallowedTools​

// A browser-driving agent: this Playwright tool runs arbitrary code, so it's never allowed.
disallowedTools: ["mcp__playwright__browser_run_code_unsafe"],

The SDK checks disallowedTools before anything else: a disallowed tool never reaches the kit's permission callback, let alone runs. Built-in tools can be disallowed too (Captain Whiskers disallows Read, Write and Glob).

humanApprovalTexts​

The approval tool exists outside autonomous mode. Its description tells the model when to call it, so write it for your domain:

humanApprovalTexts: {
description:
"Call this right before publishing anything students will see (a grade, a forum post, a page). " +
"Not for browsing or reading.",
approved: "Approved by the teacher. Go ahead.",
rejected: "The teacher rejected it. Don't publish it; ask what to change.",
},

See Approvals.

manualInterventionTexts​

Setting it is what registers the request_manual_login tool (outside autonomous mode). It only makes sense for an agent that drives a live interface a person can step into, such as a browser window:

manualInterventionTexts: {
toolDescription: "Call this when you can't log in by yourself; a person will log in by hand in the browser window.",
confirmedMessage: "The person says they logged in. Check the page and continue.",
checkpointTitle: "Manual login needed",
checkpointLines: ["Log in in the browser window the agent opened, then confirm here."],
},

See Manual intervention.

settingSources and skills​

settingSources: [], // load no Claude Code settings or CLAUDE.md at all
skills: "plugins", // only the skills of the plugins it loads are listed to the model

See Permissions and isolation and Context and cost.

A complete spec​

spec.ts
import path from "node:path";
import { fileURLToPath } from "node:url";
import { createPromptLoader, createSdkMcpServer, tool, type AgentSpec, type BaseSessionConfig } from "@falkenslab/agent-kit";
import { z } from "zod";

const here = path.dirname(fileURLToPath(import.meta.url));
const loadPrompt = createPromptLoader(path.join(here, "prompts"));

export interface Config extends BaseSessionConfig {
audience: string;
}

const inventory = createSdkMcpServer({
name: "inventory",
version: "1.0.0",
tools: [
tool("find_product", "Find products in the inventory by name.", { query: z.string() }, async ({ query }) => ({
content: [{ type: "text" as const, text: JSON.stringify(await db.products.search(query)) }],
}), { annotations: { readOnlyHint: true } }),
],
});

export const spec: AgentSpec<Config> = {
buildSystemPrompt: (config) => loadPrompt("system.md", { audience: config.audience }),
buildMcpServers: () => ({ inventory }),
pluginRoots: () => [path.join(here, "plugin")],
buildSubagents: () => ({
agents: {
researcher: {
description: "Researches a question on the web and returns a short, sourced answer.",
prompt: "Research the question with WebSearch and WebFetch. Answer in five lines at most, with sources.",
tools: ["WebSearch", "WebFetch"],
model: "haiku",
},
},
allowedSubagentTypes: ["researcher"],
}),
disallowedTools: ["Write"],
skills: ["my-agent:summaries"],
settingSources: [],
};

planMode​

In plan mode the agent only reads and plans. Two callbacks say what else it may do there:

  • isPlanFile(filePath, config): whether Write/Edit may touch this file (absolute path). It must also be writable under the file scope.
  • isReadOnlyTool(toolName, input): whether one of the agent's MCP tools only reads. Every MCP tool it doesn't vouch for is denied in plan mode.
planMode: { isReadOnlyTool: (toolName) => toolName === "mcp__inventory__find_product" },

Without planMode, the agent can read, search, ask a person and delegate in plan mode, writes nothing and presents its plan in its reply.