# Agent 服务：内置 Agent 与提示词

> 四个内置 Agent 的名册与能力上限，Handlebars 模板怎样按模式与表面渲染成系统提示词，以及 AGENTS.md 等指导文件进入提示词的顺序与预算。

- 作者：David（道雾轩）
- 专栏：MiniMax Code 源码解读（https://daiw.org/manual/minimax-code.md）
- 最后更新：2026-09-19
- 原文：https://daiw.org/manual/minimax-code/agent-service
- 转载与引用：请注明出处并附原文链接（https://daiw.org/about/copyright）

模型每一轮看到的系统提示词不是一个文件，而是好几层拼起来的：Agent 自己的角色模板、运行时插进去的规则、全局与项目的指导文件、环境信息、技能目录。`packages/local-runtime-v2/src/service/agent`（44 个文件、11975 行）管前半段——有哪些内置 Agent、各自用哪份模板、能力上限是多少；后半段由回合系统的配置构建器在每轮开始前组装（受理与排队见[回合系统](https://daiw.org/manual/minimax-code/turn-system)）。

这里的 Agent 指“一份提示词加一组能力上限”的执行身份。命令行里用户直接对话的是主 Agent `mavis`，另外三个内置角色只以子 Agent 的身份出场，委派机制见[子 Agent 与后台任务](https://daiw.org/manual/minimax-code/subagents)。

## 怎么用

- `mcode init [directory]`：以 `/init` 为首条输入启动 TUI，由内置技能 `init` 生成或更新根目录的 `AGENTS.md`（`packages/tui/src/cli/program.ts:84`）。
- 项目指导：工作区根目录的 `CLAUDE.md` 或 `AGENTS.md`，只取先找到的那份非空文件，超过 32 KiB 截断（`packages/local-runtime-v2/src/service/turn-system/agent-host/preparation/static-prompt-reader.ts:124`）。
- 全局指导：数据目录下的 `AGENTS.md`，命令行默认是 `~/.minimax-code/AGENTS.md`，超过 32 KiB 整份不注入（`packages/local-runtime-v2/src/service/turn-system/persistence/global-instructions.ts:49`）。
- `mcode exec --prompt-mode <mode>`：选主 Agent 模板，可选 `tui`、`coding`、`work`，默认 `tui`（`packages/tui/src/cli/contract.ts:102`）。
- `config.yaml` 的 `agents.default`：所有 Agent 继承的能力默认值（`packages/config/src/config.ts:2064`）。
- 自定义 Agent：`<dataDir>/agents/<name>/agent.md` 一个文件（`packages/local-runtime-v2/src/service/agent/storage/agent-files.ts:110`）。
- 环境变量 `MAVIS_BUILTIN_AGENTS_V2_DIR`：换一套内置提示词资产目录（`packages/local-runtime-v2/src/service/agent/builtin/catalog.ts:586`）。

`mcode init` 本身不写任何文件。它只是把 `/init` 当首条输入交给 TUI，`/init` 被解析成运行时提供的技能调用，干活的是 `packages/local-runtime/assets/skills/init/SKILL.md` 这份提示词：先按清单文件识别生态（`package.json`、`pyproject.toml`、`Cargo.toml`、`go.mod`），再按模板写出一份控制在约 80 行以内的 `AGENTS.md`。“更新”也由提示词约束，文件已存在时必须先问用户（`packages/local-runtime/assets/skills/init/SKILL.md:55`）：

```text
- **File does not exist** → write the full template (step 3b).
- **File exists** → **ASK the user** which path to take:
  1. **Skip** — leave the existing file untouched (default if the user is silent).
  2. **Overwrite (with backup)** — copy current to `AGENTS.md.bak.<unix-ts>` then write the fresh template.
  3. **Show diff** — print a unified diff between the existing file and the proposed template; user copies the edits they want by hand.
```

技能描述还说它会在系统提示词出现 `<bootstrap_check>` 时自动加载（`SKILL.md:3`），对应的提醒是 `packages/agent-modules/system-reminder/src/providers.ts:441` 的 `bootstrapProvider`，冷却期默认 6 小时。但它的第一道判断是 `input.needsBootstrap`，而整个仓库里找不到给 `needsBootstrap` 赋值的代码，所以在这份快照里自动引导不会触发，只能靠 `/init`、`mcode init` 或用户直接要求。这份技能文件放在第一代运行时的包里，[第一代运行时](https://daiw.org/manual/minimax-code/runtime-v1)一篇还会提到；技能的加载与调用见[技能](https://daiw.org/manual/minimax-code/skills)。

## 内置名册与能力上限

`packages/local-runtime-v2/assets/agents/builtin-agents.json` 只有一行：`["mavis", "explore", "worker", "verifier"]`。`BuiltinAgentCatalog.readDefinition`（`catalog.ts:129`）为每个名字生成定义：`mavis` 的角色是 `orchestrator`，其余三个的角色名就是自己的名字；旧主 Agent 名 `main` 一律规范成 `mavis`（`packages/local-runtime-v2/src/service/agent/builtin/definitions.ts:107`）。能力覆盖写在各自目录的 `agent.md` frontmatter 里，用严格解析器读取（`catalog.ts:409`）：

| Agent | 工具上限 | 关掉的特性 | TUI 模板 |
| --- | --- | --- | --- |
| `mavis` | 无 `agent.md`，不设上限 | 无，继承配置 | `_v2/tui/SYSTEM.md.hbs` |
| `explore` | `bash` `read` `grep` `glob` `web_fetch`，无多媒体工具与技能 | `mavis`、`delegation` | `_v2/explore.md.hbs` |
| `worker` | 全部 12 个内置工具 | `delegation` | `_v2/worker.md.hbs` |
| `verifier` | `read` `grep` `glob` `bash` `task_query` `task_output` `task_stop` `web_fetch`，无多媒体工具与技能 | `mavis`、`delegation` | `_v2/verifier.md.hbs` |

“12 个内置工具”指 `packages/config/src/agent-capabilities.ts:1` 的 `AGENT_BUILTIN_TOOL_IDS`：`read`、`write`、`edit`、`bash`、三个任务控制工具、`grep`、`glob`、`todowrite`、`web_fetch`、`website_deploy`。能力结构里还有独立的多媒体 MCP 工具（`builtinTools`）、内置技能（`skills`）和三个特性开关 `mavis`、`delegation`、`webSearch`；列表字段为 `undefined` 表示“全部可用”，为 `[]` 表示“一个都不要”（`agent-capabilities.ts:87`）。

能力分三层收紧：

1. **配置默认值**：`resolveAgentCapabilities`（`agent-capabilities.ts:298`）把 `agents.default` 补全，缺省时人设与三个特性都开。`config.yaml` 走宽容解析器 `parseAgentsConfig`，旧式工具白名单若没写任务控制工具，会自动补上 `task_query`、`task_output`、`task_stop`（`agent-capabilities.ts:246`）。
2. **内置上限**：`resolveCanonicalCapabilities`（`definitions.ts:116`）拿配置和 `agent.md` 求交：特性按“与”合并，列表取交集。`agent.md` 因此只能收窄，不能放宽。
3. **场合收紧**：内置子 Agent 以 task-child 身份运行时，`applyBuiltinSubagentTaskChildCeiling`（`packages/local-runtime-v2/src/service/agent/domain/validation.ts:138`）再关掉 `mavis` 与 `delegation`，避免子 Agent 继续委派。注释解释了为什么整块关掉 `mavis`：会话间发消息只是复合工具 `mavis` 的一个子命令，只藏子命令会让提示词和工具表对不上。

命令行还有自己的一刀。`createV2AgentProfileSource`（`packages/local-runtime-v2/src/application/agent/profile-source.ts:25`）在 `capabilityProfile === 'cli'` 时把 `features.mavis` 强制设为假（`profile-source.ts:73`），并对命令行宿主关掉记忆与定时任务段落（`profile-source.ts:52`）；嵌入式宿主也把 `memory.enabled` 写死为假（`packages/tui/src/runtime/embedded-host.ts:54`）。所以 TUI 里的 `mavis` 没有 Agent 管理、记忆写入这些桌面端能力。

## 模板：一份文件按层渲染

模板资产在 `packages/local-runtime-v2/assets/agents/`。`usesV2Prompts`（`catalog.ts:609`）判断走哪套：提示词画像是 `tui` 或 `desktop`、或显式给了 `promptMode`，就用 `_v2/` 下的新模板；否则回到 `_default/` 与 `mavis/modes/<appMode>/` 那套旧分层。TUI 的画像恒为 `tui`（`profile-source.ts:49`），模式取 `promptMode`，缺省为 `tui`（`catalog.ts:605`），于是主 Agent 读 `_v2/tui/SYSTEM.md.hbs`，子 Agent 读 `_v2/<name>.md.hbs`（`catalog.ts:353`）。`--prompt-mode coding` 或 `work` 换成 `_v2/coding/`、`_v2/work/` 下的同名模板，那是桌面端的写法：交付文件用 `<deliver-assets>` 媒体标签、另有 Citations 段，`work` 还多一段 Artifact Completion Contract。

渲染用独立的 Handlebars 实例，`strict: true` 且 `noEscape: true`（`packages/local-runtime-v2/src/service/agent/builtin/prompt-renderer.ts:55`）：模板引用了不存在的变量就直接报错，文本也不做 HTML 转义。可用变量由 `createBuiltinPromptContext`（`prompt-renderer.ts:15`）给出：`tools.<工具名>`、`persona`、`profile.tui`、`features`、`skills`、`memory.enabled`、`cron.enabled`、`surface.interactive`、`surface.taskChild`、`surface.cli`，以及 `DATA_DIR` 和子 Agent 角色目录 `ROLE_DIRECTORY`。

共享层的做法有点特别。`renderSharedBaseParts`（`catalog.ts:243`）按旧分层的文件名列出要叠加的层——`prompt-base-all`、`prompt-base-worker` 等——但在 V2 分支里 `readLayerTemplates` 不看文件名，一律读 `_v2/AGENT_CONTEXT.md.hbs`（`catalog.ts:293`），再由 `renderLayerParts` 按来源名打上层标记（`catalog.ts:279`）：

```ts
const context = {
  ...this.context(input, {}),
  layer: {
    base: source.endsWith('/prompt-base-all'),
    worker: source.endsWith('/prompt-base-worker'),
    root: source.endsWith('/prompt-session-root.md.hbs'),
    branch: source.endsWith('/prompt-session-branch.md.hbs'),
  },
};
```

`AGENT_CONTEXT.md.hbs` 于是由四个 `{{#if layer.*}}` 块组成，同一份文件被渲染几遍，每遍只亮一块。落到 TUI 上：

- **主 Agent**：带核心模板时不叠 `base` 层（`catalog.ts:165`），编排者角色也不叠 `worker` 层，表面层同样置空（`catalog.ts:161`），系统提示词的主体就是 `SYSTEM.md.hbs` 一份。
- **子 Agent**：角色模板之后叠 `base`（Harness 规则）和 `worker`（先做事后汇报、受阻立即上报），表面层渲染 `branch` 块。自定义表面提示词缺了交接契约时，`ensureDelegatedTaskContract` 会把默认契约补在后面（`catalog.ts:320`）。

主模板开头写明身份（`packages/local-runtime-v2/assets/agents/_v2/tui/SYSTEM.md.hbs:2`，外面包着 `{{#if persona.enabled}}`）：

```text
You are a coding agent running in the MiniMax Code terminal, developed by MiniMax.
When asked about your identity, runtime environment, product ownership, or comparisons with other
coding tools, state these facts clearly. Do not describe yourself as a generic model detached from
MiniMax Code.
```

之后依次是 `# Harness`（优先用专用工具而不是 `bash`、代码引用写成 `file_path:line_number`）、`# Core Judgment`、只在记忆开启时出现的 `# Memory`，以及 `# Communication & Delivery`（回复风格、工具调用前的简短说明、最终答复必须自足、交付文件用绝对路径的 Markdown 链接）。子 Agent 那边，`packages/local-runtime-v2/assets/agents/_v2/AGENT_CONTEXT.md.hbs:181` 的交接契约开宗明义：

```text
## Delegated Task Contract
You are a hidden TUI child Agent working for a parent Agent, not speaking directly to the user.

- Complete only the assigned task and return a concise handoff to the parent.
```

`explore` 与 `verifier` 的模板各带一道“交付物闸门”：`explore` 发现任务需要写文件就立刻返回 `ROLE_MISMATCH`，并建议改派 `worker`（`packages/local-runtime-v2/assets/agents/_v2/explore.md.hbs:11`）；`verifier` 遇到需要改动被验对象或项目源码的任务同样返回 `ROLE_MISMATCH`，结论必须落在 `VERDICT: PASS | FAIL | PARTIAL` 三者之一（`packages/local-runtime-v2/assets/agents/_v2/verifier.md.hbs:29`）。`worker` 没有闸门，只要求在给定范围内做最小的一致改动。

## 一轮的系统提示词怎样拼出来

每轮开始前，配置构建器 `loadPromptLayers`（`packages/local-runtime-v2/src/service/turn-system/agent-host/preparation/config/local-agent-config-builder.ts:451`）并行读取几样东西：身份层、项目指导、全局指导、记忆块、可用技能。V2 组合里总有 Agent 画像源，身份层直接取画像里已渲染好的核心提示词与表面提示词，静态资产那条读取路径被跳过（`local-agent-config-builder.ts:492`）。然后 `buildIdentityPrompt` 定下前半段的顺序（`local-agent-config-builder.ts:780`）：

```ts
return composePromptParts([
  { text: persona ?? '' },
  {
    text: addRuntimeRules(
      hasIdentity
        ? corePrompt
        : "You are a local coding assistant running in the user's workspace.",
      interactiveSurface,
      [profile?.surfacePrompt, layers.sessionPrompt].filter(Boolean).join('\n\n'),
    ),
  },
  { text: layers.basePrompt.trim() },
  { text: instructionsPreamble },
  { text: globalInstructions, kind: 'OTHER' },
  { text: projectInstructions, kind: 'OTHER' },
]);
```

`buildSystemPrompt`（`local-agent-config-builder.ts:719`）再往后接环境块、子 Agent 专用的 `# Session Context`、记忆块和技能目录。整体顺序如下：

```mermaid
flowchart TB
  A["人设 persona"] --> B["核心提示词 + addRuntimeRules 插入的规则"]
  B --> C["指导文件前导语（有指导文件时才出现）"]
  C --> D["全局 AGENTS.md"]
  D --> E["项目 CLAUDE.md 或 AGENTS.md"]
  E --> F["Environment 环境块"]
  F --> G["Session Context（仅 task-child）"]
  G --> H["记忆块"]
  H --> I["技能目录"]
```

几处细节：

- **运行时规则**：`addRuntimeRules`（`packages/local-runtime-v2/src/service/turn-system/agent-host/preparation/config/runtime-prompt-rules.ts:8`）不是简单拼接，而是找到 `# Harness`、`## Response Style`、`## Preamble messages` 等标题插进去：说明 `<system-reminder>` 由宿主注入、文件操作规则、语言规则；Windows 上再加两条（安装系统软件须经用户明确同意、编辑时保留原有的 CRLF 或 LF 换行）。
- **前导语**：只要全局或项目指导有一份非空，就先放 `INSTRUCTIONS_CONTEXT_PREAMBLE`（`packages/local-runtime-v2/src/service/turn-system/agent-host/preparation/prompt-blocks.ts:2`），第二句原文是：

  > Codebase and user instructions are shown below. Be sure to adhere to these instructions. IMPORTANT: These instructions OVERRIDE any default behavior and you MUST follow them exactly as written.

- **环境块**：`environmentContextBlock`（`packages/local-runtime-v2/src/service/turn-system/agent-host/preparation/config/environment-prompt-composer.ts:12`）列出工作目录、是否 Git 仓库、平台、Shell、系统版本、模型、界面语言、区域和当前数据目录。
- **每轮重读**：这些层在每轮的 `buildForPromptScope` 里重新加载（`local-agent-config-builder.ts:224`），改完 `AGENTS.md` 不用重启，下一轮就生效。

## 指导文件：CLAUDE.md 优先

项目指导的读取在 `static-prompt-reader.ts:120`：

```ts
async function readProjectInstruction(
  workspaceDir: string,
  logger?: StaticPromptReaderLogger,
): Promise<{ readonly path: string; readonly content: string } | undefined> {
  for (const name of [LEGACY_AGENT_INSTRUCTIONS_FILE, 'AGENTS.md']) {
    const path = join(workspaceDir, name);
    const content = await readProjectInstructionsOrEmpty(path, name, logger);
    if (content) return { path, content };
  }
  return undefined;
}
```

`LEGACY_AGENT_INSTRUCTIONS_FILE` 定义在 `static-prompt-reader.ts:10`，值用十六进制转义写成 `'\x43\x4c\x41\x55\x44\x45.md'`，解码就是 `CLAUDE.md`。于是：

- 两份都有时只用 `CLAUDE.md`，`AGENTS.md` 被整份忽略；去掉首尾空白后为空的文件视为不存在，会继续找下一份。
- 只看工作区根目录这一层，不向上找父目录，不读子目录里的同名文件，也不展开文件内的引用。
- 上限 `PROJECT_INSTRUCTIONS_MAX_BYTES = 32 * 1024`（`static-prompt-reader.ts:11`）。超出时按 UTF-8 边界截断，截点前的最后一个换行若落在末尾 20% 以内就退到换行处，再附一段说明提示模型需要时去读原文件，同时记一条警告日志（`static-prompt-reader.ts:206`）。

全局指导走 `GlobalInstructions.readForPrompt`（`global-instructions.ts:47`），同样 32 KiB 上限，策略却不同：文件一超限就返回 `undefined`，整份不注入，读失败也静默跳过。记住这个差别：项目文件过大会被截断，全局文件过大则干脆消失。

## 快照：一次会话不混用两版提示词

画像渲染出的提示词带一份摘要：`describeAgentPromptSnapshot`（`packages/local-runtime-v2/src/service/agent/domain/prompt-snapshot.ts:4`）记录 `schema_version: 2`、模式、包版本，以及模板和渲染结果各自的 SHA-256，写进每轮配置的 `prompt_metadata`。子 Agent 所在的任务会话更进一步：它的 Agent 定义在创建时冻结，之后按冻结的模板重新渲染（`renderFrozenAgentProfile`，`packages/local-runtime-v2/src/service/agent/application/agent-profile.ts:204`）。冻结模板的模式与当前 `--prompt-mode` 不一致时直接拒绝（`agent-profile.ts:199`）：

> The saved Task Prompt does not match --prompt-mode. Start a new Session for this benchmark.

资产本身也可以冻结。`LocalAgentService` 在给定 `promptMode` 时打开 `freezeLocalPrompts`（`packages/local-runtime-v2/src/service/agent/application/agent.service.ts:154`），第一次读取就把整个资产目录的 `.md`、`.md.hbs`、`.json` 读进内存（`packages/local-runtime-v2/src/service/agent/builtin/prompt-assets.ts:5`），之后不再碰磁盘。`mcode exec` 总会带上 `promptMode`（`packages/tui/src/cli/run-exec-command.ts:75`）；交互式 TUI 创建运行时时不带（`packages/tui/src/tui/launcher.ts:326`），每次从磁盘读。

`service/prompt-config` 是另一套机制：从云端拉取 AES-256-GCM 加密的提示词包，默认每 6 小时刷新一次（`packages/local-runtime-v2/src/service/prompt-config/contracts.ts:1`），只供桌面端使用（`packages/local-runtime-v2/src/service/prompt-config/initialize.ts:77`）。在这份快照里它被整体关掉了，见 `packages/local-runtime-v2/src/application/session/runtime-prompt-support.ts:23`：

```ts
const support = await initializeRuntimePromptSupport({
  // Hotfix: keep package prompts paired with this client, including when an
  // existing profile explicitly enables auto-update. Do not bind old disk caches.
  enabled: false,
```

读模板的约定倒是留了下来。`LocalPromptSnapshotSource`（`packages/local-runtime-v2/src/service/agent/prompt-snapshot-source.ts:26`）把提示词存储包装成“先 `capture` 一份不可变快照、再按键读取”的接口，一次模型调用里的所有模板都来自同一份快照；`readPromptWithBuiltinFallback`（`packages/local-runtime-v2/src/service/agent/prompt-template.ts:8`）在远程快照读坏时整份退回内置快照，绝不在同一请求里混用两边的片段，会话标题生成这类内部任务用的就是它。给定 `promptMode` 时，冻结的本地资产也按同一接口提供（`catalog.ts:98`、`runtime-prompt-support.ts:22`）。

## 自定义 Agent 与启动时的定义切换

自定义 Agent 就是 `<dataDir>/agents/<name>/agent.md` 一个文件（文件名常量在 `agent-files.ts:48`）：frontmatter 可写 `name`、`description`、`model`、`effort`、`tools`、`disallowedTools`、`mcpServers`、`skills` 和扩展段 `x-mavis`（`packages/local-runtime-v2/src/service/agent/storage/canonical-agent-config.ts:102`），正文就是系统提示词，文件上限 1 MiB（`canonical-agent-config.ts:79`）。内置 Agent 另有一份运行时维护的镜像 `agents/.builtin/<name>/agent.md`，每次启动重建，用户只能改其中的模型选择（`agent-files.ts:89`）。

命令行没有管理 Agent 的斜杠命令，`mavis` 工具也被关掉了；手写的目录会在启动时被发现并补上数据库行（`packages/local-runtime-v2/src/service/agent/storage/agent.repository.ts:411`），子 Agent 工具的参数说明允许填内置角色名或自定义 Agent 的 `requestRef`（`packages/shared/src/subagent-roles.ts:76`）。

这些定义在启动时要经过一次“切换”。`createRuntimeAgentComposition`（`packages/local-runtime-v2/src/service/agent/composition.ts:182`）在跨进程的 Agent 存储锁里依次执行：目标库预检；若目标库只有内置 Agent、且本数据目录还没做过这一步，就扫描旧库的名字冲突、改写并从旧版 `<dataDir>/sqlite.db` 导入 Agent（`composition.ts:453`）；修复与内置同名的自定义 Agent；补齐内置 Agent 行；把旧的自定义定义落成文件。最后库里一个 Agent 都没有就抛错（`composition.ts:310`）。旧库的来历见[第一代运行时与 OpenCode 遗产](https://daiw.org/manual/minimax-code/runtime-v1)，这把锁在[存储](https://daiw.org/manual/minimax-code/storage)一篇里。

最后，`packages/config/src/agent-runtime-config.ts:20` 的执行框架类型只剩 `'pi-agent'` 一个值，`config.yaml` 里 `agentRuntime.defaultFramework` 写了别的会被当作过时配置，回落到 Pi（`agent-runtime-config.ts:56`）。

下一篇：[存储：SQLite、迁移与运行时归属](https://daiw.org/manual/minimax-code/storage)——会话、队列和 Agent 定义落在哪个库里，多个进程怎么共用一个数据目录。
