深入 Claude Code 源码(九):Agent 角色配置——用 `.claude/agents/` 定义专属助手

前两篇,我们学会了用 SDK 调用 Agent、给 Agent 加自定义工具。但不管哪种方式,都是在代码里配置------每次跑 Agent 都要在脚本里写一段提示词,传一堆参数,改起来麻烦,也不方便复用。

现实工作里,大家会希望有更多「随手可用」的专属 Agent:一个专门做代码审查的、一个专门写测试的、一个专门生成文档的......配置好了放在那里,需要的时候一行命令就能召唤出来,和同事共享,换个电脑也能用。

Claude Code 提供了一套标准的 Agent 角色定义机制------把 Agent 的身份、职责、工具权限、模型选择都写成一个 markdown 文件,放在 .claude/agents/ 目录下,就能让整个团队复用。

这套机制背后,就是我们在第六篇源码里读到的 loadAgentsDir.ts,今天把它彻底用起来。


一、Agent 定义文件的结构

一个 Agent 定义文件是一个普通的 .md 文件,结构分两部分:YAML 前置信息(Frontmatter)和文件正文(System Prompt)。

markdown 复制代码
---
description: 专职代码审查员,针对 Pull Request 提供详细的代码质量反馈
tools: Read, Bash, Grep
model: claude-sonnet-4-6
permissionMode: default
maxTurns: 30
---

你是一位经验丰富的代码审查员,专注于代码质量、安全性和可维护性。

你的工作流程:
1. 先理解代码的上下文(读入口文件、README、相关配置)
2. 分析具体改动(逐文件审查)
3. 按优先级给出建议(Critical → Major → Minor)

[系统提示词继续...]

Frontmatter 字段一览

字段 类型 含义
description 必填字符串 Agent 的一句话描述,也是 Claude 决定何时使用这个 Agent 的依据
tools 字符串数组或逗号分隔 允许使用的工具列表,* 表示全部
disallowedTools 字符串数组 明确禁用的工具(优先级高于 tools
model 字符串 使用的模型,inherit 表示继承父 Agent 的模型
permissionMode 枚举 default / bypassPermissions / acceptEdits
maxTurns 整数 最大执行轮次,超出后停止
effort 枚举 推理强度:low / medium / high
mcpServers 数组 这个 Agent 专用的 MCP Server
background 布尔 是否作为后台任务运行
memory 枚举 持久化记忆范围:user / project / local

文件正文(前置信息之后的所有内容)就是这个 Agent 的系统提示词(System Prompt) 。在 loadAgentsDir.ts 里,前置信息解析之后,正文直接作为 systemPrompt 注入进子代理的 queryLoop


二、放置位置的优先级

Agent 定义文件可以放在三个地方,优先级从高到低:

  1. ~/.claude/agents/ --- 用户级别,只有自己能用
  2. <项目目录>/.claude/agents/ --- 项目级别,团队共享(建议提交到版本控制)
  3. Claude Code 内置 Agent --- ExplorePlangeneral-purpose 等(不可覆盖)

同名 Agent:项目级别会覆盖用户级别,用户级别会覆盖内置(但内置的 agentType 命名方式与自定义不同,基本不会冲突)。


三、实践一:专职代码审查 Agent

创建文件 .claude/agents/code-reviewer.md

markdown 复制代码
---
description: 专职代码审查员。对一个或多个文件进行代码审查,从可维护性、安全性、性能三个维度给出反馈。
tools: Read, Grep, Bash
model: claude-sonnet-4-6
permissionMode: default
maxTurns: 20
---

你是一位资深软件工程师,专注于代码审查工作。你的目标是帮助团队提升代码质量,而不是为了批评而批评。

## 审查维度

每次审查从以下三个维度出发,按重要性排序:

**1. 安全性(Critical)**
- SQL 注入、XSS、命令注入等 OWASP Top 10 问题
- 敏感信息泄露(密钥、密码硬编码)
- 权限控制缺失

**2. 可维护性(Major)**
- 函数/类职责是否单一
- 是否有明显的代码重复
- 命名是否表达意图
- 异常处理是否完整

**3. 性能(Minor)**
- 明显的 O(n²) 复杂度
- 不必要的同步阻塞
- 重复的计算/查询

## 输出格式

```
## 代码审查报告

### 文件:<文件路径>

**[Critical] <问题标题>**
位置:第 X-Y 行
问题:<具体描述>
建议:<修复方案>

[其他问题...]

### 总体评价
<2-3 句综合评价>

### 改进优先级
1. (必须修复)...
2. (建议修复)...
3. (可以考虑)...
```

## 工作原则

- 只指出真实存在的问题,不泛泛批评「写得不好」
- 给出具体可行的改进建议,而不是「这应该更好」
- 如果代码逻辑不清楚,先通过 Read 和 Grep 理解上下文,再做判断
- 中文输出

然后从命令行使用这个 Agent:

bash 复制代码
# 让代码审查员审查特定文件
claude --agent code-reviewer "审查 src/auth/middleware.ts,重点关注安全性"

# 也可以在 Claude Code 交互模式里召唤它
# 在对话框里输入:@code-reviewer 帮我审查这次 PR 里的改动

也可以通过 SDK 使用:

typescript 复制代码
import { query } from '@anthropic-ai/claude-code'

const response = query({
  prompt: '审查 src/auth/middleware.ts,重点关注安全性',
  options: {
    cwd: process.cwd(),
    // 指定使用 code-reviewer Agent
    agentType: 'code-reviewer',
  },
})

for await (const message of response) {
  if (message.type === 'assistant') {
    const blocks = message.message?.content ?? []
    for (const block of blocks) {
      if (block.type === 'text') process.stdout.write(block.text)
    }
  }
  if (message.type === 'result') break
}

四、实践二:测试工程师 Agent

创建文件 .claude/agents/test-engineer.md

markdown 复制代码
---
description: 测试工程师 Agent。为指定的函数或模块编写完整的单元测试,覆盖正常路径、边界情况和错误情况。
tools: Read, Grep, Bash, Write, Edit
model: claude-sonnet-4-6
permissionMode: acceptEdits
maxTurns: 40
---

你是一位专注于测试驱动开发的工程师。你的职责是为代码编写高质量的测试,确保测试真正能捕获潜在的 Bug。

## 工作流程

**第一步:理解被测代码**
1. 读取目标文件,理解函数签名、功能、依赖
2. 查看现有测试(如有),避免重复
3. 查看相关类型定义

**第二步:设计测试用例**
对每个函数,至少覆盖:
- 正常输入 + 期望输出(Happy Path)
- 边界值(空字符串、0、null、空数组)
- 异常情况(应该抛出错误的场景)
- 若有异步操作,测试 Promise 的 resolve 和 reject

**第三步:编写测试**
- 遵循项目已有的测试框架(先用 Bash 检测用的是 Jest/Vitest/其他)
- 每个测试用例描述要清晰,一看就知道在测什么
- 不 Mock 不必要的东西------Mock 越少,测试越可靠

## 输出规范

- 测试文件放在与被测文件同目录,命名为 `<filename>.test.ts`
- 每个 describe 块对应一个函数
- 每个 it 的描述格式:`应该 <期望行为> 当 <条件>`
- 写完后运行测试,确认全部通过

## 禁止事项

- 不编写「看起来像测试但实际不测任何东西」的占位测试
- 不为了提高覆盖率而写无意义的测试
- 不修改被测代码来迁就测试

使用方式:

bash 复制代码
# 为指定文件写测试
claude --agent test-engineer "为 src/utils/parser.ts 里的所有导出函数编写单元测试"

# 结合 CI 自动触发(在 git commit hook 里)
# 检测到新增文件但没有对应测试时,自动召唤测试工程师

五、用多个 Agent 组成流水线

有了多个专职 Agent,更有意思的玩法是让它们串联工作------第一个 Agent 完成一项工作,结果传给下一个 Agent 继续处理。

下面是一个完整的代码改进流水线:先让「测试工程师」写测试,再让「代码审查员」审查原始代码,最后汇总报告:

typescript 复制代码
// pipeline.ts
import { query } from '@anthropic-ai/claude-code'

async function runAgentAndCollect(options: {
  prompt: string
  agentType: string
  cwd: string
}): Promise<string> {
  const response = query({
    prompt: options.prompt,
    options: {
      cwd: options.cwd,
      agentType: options.agentType,
      permissionMode: 'acceptEdits',
    },
  })

  let output = ''
  for await (const message of response) {
    if (message.type === 'assistant') {
      const blocks = message.message?.content ?? []
      for (const block of blocks) {
        if (block.type === 'text') output += block.text
      }
    }
    if (message.type === 'result') break
  }
  return output
}

async function codeImprovementPipeline(targetFile: string) {
  const cwd = process.cwd()

  console.log('📝 步骤 1/2:代码审查...\n')
  const reviewReport = await runAgentAndCollect({
    prompt: `审查 ${targetFile},按照 Critical/Major/Minor 三级输出问题列表`,
    agentType: 'code-reviewer',
    cwd,
  })
  console.log(reviewReport)

  console.log('\n🧪 步骤 2/2:补充测试...\n')
  const testReport = await runAgentAndCollect({
    prompt: `为 ${targetFile} 编写单元测试,重点覆盖以下已知问题点:\n${reviewReport}`,
    agentType: 'test-engineer',
    cwd,
  })
  console.log(testReport)

  // 汇总报告
  const summary = await runAgentAndCollect({
    prompt: `
      基于以下代码审查和测试报告,生成一份 PR 描述:
      
      ## 代码审查结果
      ${reviewReport}
      
      ## 测试补充情况
      ${testReport}
      
      PR 描述要包含:本次改进的核心目标、解决的主要问题、测试覆盖情况。300 字以内。
    `,
    agentType: 'general-purpose',
    cwd,
  })

  console.log('\n📋 PR 描述草稿:\n')
  console.log(summary)
}

await codeImprovementPipeline(process.argv[2] ?? 'src/index.ts')

运行之后,三个步骤顺序执行,每一步的输出作为下一步的输入,最终输出一份完整的改进报告和 PR 描述草稿。这就是------一个人提交代码,三个「专家」背后帮大家自动过一遍。


六、Agent 配置进阶:Memory 和 MCP

Frontmatter 里还有两个字段值得单独拿出来讲:

memory 字段------让 Agent 有「记忆」

yaml 复制代码
---
description: 技术文档写作助手,记住项目的写作风格规范
memory: project
---

配置了 memory: project 之后,这个 Agent 每次被召唤,会自动加载 CLAUDE.md 里和项目记忆相关的部分。它会「记住」上次告诉它的写作风格、特殊约定,不需要每次都重新解释。

memory 有三个选项:

  • user:用户级别的持久记忆(跨项目)
  • project:项目级别(只在当前仓库有效)
  • local:本地临时记忆(不提交到版本控制)

mcpServers 字段------给 Agent 装备专属 MCP 工具

yaml 复制代码
---
description: 数据分析助手,能查询生产数据库和内部知识库
mcpServers:
  - readonly-postgres        # 引用已配置的 MCP Server 名字
  - knowledge-base           # 另一个
---

这样「数据分析 Agent」启动时,就会自动挂载 readonly-postgresknowledge-base 两个 MCP Server,而其他 Agent 不会有这两个工具------权限和能力精确地绑定在对应的角色上。


七、Agent 定义的设计哲学

回头看 src/tools/AgentTool/built-in/exploreAgent.ts 里的 Explore Agent,大家会发现 Anthropic 内部在设计 Agent 时遵循几个原则,值得借鉴:

单一职责 :Explore Agent 只做搜索,Plan Agent 只做规划,绝不越界。每个 Agent 的 description 第一句话就是它的核心职责边界。

最小权限disallowedTools 明确列出了不该有的工具。Explore Agent 的 disallowedTools 里有 EditWrite------它是只读探索的,永远不应该改文件,这个约束写在定义里,不依赖 Claude 自觉遵守。

说清楚何时不用它whenToUse 字段不只是说「什么时候用我」,也要暗示「什么情况下换别人」。这帮助 Claude 在多 Agent 可用时做出合理选择。

提示词写给执行者,而不是读者:Agent 的系统提示词是给另一个 Claude 看的,不是给人看的。要直接、具体、可操作,避免模糊表述。「先读取文件,再理解上下文,再给出建议」比「理解代码后提供帮助」好得多。


学到这里,大家已经掌握了完整的自制 Agent 工具箱:

  1. 调用方式 (第七篇):claude -p 命令行调用、SDK query() 流式调用
  2. 自定义工具 (第八篇):tool() + createSdkMcpServer() 扩展 Agent 能力
  3. 专属角色 (本篇):.claude/agents/ 配置文件定义可复用 Agent

把这三篇连起来,从「会调用 Claude」到「能配出一套专业 AI 工作流」,中间并没有太大的门槛------只是需要花时间把工具和提示词打磨好。工具设计得好,提示词写得清楚,Agent 的表现就会远超通用模式。

很多人用 Claude Code 只是把它当「聊天助手」,用了一半的力气。用好自定义工具和专属 Agent,才算真的把这套架构的潜力释放出来。