当智能体变成一份 Markdown 文档

在传统的软件工程中,构建一个"智能体"(Agent)意味着编写大量代码:状态机、决策树、API 集成、错误处理、权限管理。而在 LLM 时代,定义一个功能完备的 AI 智能体,可能只需要一份结构良好的 Markdown 文档。

这不是夸张。以 Claude Code 的配置体系为例,一个 .md 文件可以定义 Agent 的身份、行为约束、工具权限、专业知识、工作流程,甚至跨会话的持久记忆。但要真正理解"为什么 Markdown 就够了",以及"如何写出高效的 Agent 定义",需要先回到第一性原理——理解 LLM 的本质、Agent 的运行机制,以及上下文窗口的物理约束。

本文将从底层原理出发,逐层构建对 Agentic Coding 的完整认知:先理解 LLM 是什么、Agent Loop 如何运转、上下文窗口为何是核心瓶颈;再拆解 Markdown 配置体系的四层架构;最后落地到实操方法论——如何管理对话、沉淀知识、用工程手段约束 Agent、为 AI 设计友好的工作环境。

全景思维导图

mindmap
  root((Agentic Coding 全景))
    第一性原理
      LLM 本质
        概率性文本生成器
        非确定性推理引擎
        Token 预测机制
      Agent Loop
        感知-推理-行动循环
        LLM + 工具 + 循环 = Agent
        自主决策与工具调用
      上下文窗口
        工作记忆的物理约束
        注意力退化曲线
        Lost in the Middle 效应
    Markdown 配置体系
      三层提示机制
        L1 System Prompt 行为宪法
        L2 项目配置 CLAUDE.md
        L3 用户输入 任务指令
      CLAUDE.md
        项目记忆
        行为约束
        分层加载
      Commands
        参数化提示词模板
        斜杠触发
        变量插值
      Skills
        可复用知识包
        领域专业知识
        工作流定义
      Sub Agents
        独立上下文窗口
        工具权限隔离
        持久化记忆
      MCP 协议
        标准化工具调用
        运行时能力发现
        传输层解耦
    实操方法论
      对话管理
        短对话优于长对话
        任务分解策略
        上下文隔离
      知识沉淀
        从会话到持久记忆
        Rules 与 Memory
        经验复利效应
      工程约束
        Linter 与 Git Hooks
        拦截偷懒行为
        工程手段优于 Prompt 指令
      AI 友好设计
        信息架构优化
        API 设计平衡
        命名与统计直觉
      刻意练习
        像学乐器一样学 AI
        从失败中提取教训
        建立肌肉记忆

模式总览

本文覆盖的核心可迁移模式:

# 模式名称 一句话口诀 覆盖场景
1 概率性推理 LLM 是统计直觉而非逻辑推理 理解 Agent 行为的不确定性、命名偏好、错误模式
2 感知-行动循环 LLM + 工具 + 循环 = Agent Agent Loop 的运行机制、工具调用、自主决策
3 上下文即工作记忆 有限窗口决定一切策略 对话管理、任务分解、信息密度优化
4 分层配置 环境-触发-知识-执行四层分离 CLAUDE.md / Commands / Skills / Sub Agents
5 知识外化 把脑中隐性知识变成文档 Rules、Memory、经验复利
6 双重投资回报 对人好的改进对 AI 同样有效 文档、架构、测试速度的双重价值
7 工程约束优于提示 用代码而非文字约束 Agent Git Hooks、Linter、命令拦截
8 统计直觉对齐 顺应而非对抗 Agent 的命名偏好 AI 友好的命名、API 设计、信息架构
flowchart LR
    subgraph 原理层
        P1[概率性推理]
        P2[感知-行动循环]
        P3[上下文即工作记忆]
    end
    subgraph 配置层
        P4[分层配置]
        P5[知识外化]
    end
    subgraph 实践层
        P6[双重投资回报]
        P7[工程约束优于提示]
        P8[统计直觉对齐]
    end
    P1 --> P2
    P2 --> P3
    P3 --> P4
    P3 --> P5
    P4 --> P6
    P5 --> P6
    P4 --> P7
    P1 --> P8
    P6 --> P7
    P7 --> P8

第一性原理:理解 Agentic Coding 的底层逻辑

在讨论如何用 Markdown 定义 Agent 之前,需要先回答三个基础问题:LLM 到底是什么?Agent 如何运转?上下文窗口意味着什么?这三个问题的答案,决定了所有上层策略的设计逻辑。

flowchart TB
    subgraph 第一性原理
        A[LLM 的本质] --> B[Agent Loop 的运行机制]
        B --> C[上下文窗口的物理约束]
    end
    C --> D[为什么 Markdown 就够了]
    C --> E[为什么需要分层配置]
    C --> F[为什么短对话优于长对话]
    style A fill:#e1f5fe
    style B fill:#e1f5fe
    style C fill:#e1f5fe

LLM 的本质:概率性文本生成器

理解 Agentic Coding 的第一步,是准确理解 LLM 的本质。LLM 不是数据库,不是搜索引擎,也不是确定性的推理引擎。它是一个概率性的文本生成器——给定一段输入文本(prompt),它预测最可能的下一个 token,然后把这个 token 加入输入,再预测下一个,如此循环。

flowchart LR
    A["输入: 'The capital of France is'"] --> B[LLM 计算概率分布]
    B --> C["P('Paris')=0.92"]
    B --> D["P('Lyon')=0.03"]
    B --> E["P('the')=0.02"]
    C --> F["输出: 'Paris'"]
    F --> G["新输入: 'The capital of France is Paris'"]
    G --> H[继续预测下一个 token...]

这个机制带来几个关键推论:

1. LLM 的"知识"是统计相关性,而非逻辑推理。 当 LLM 回答"法国的首都是巴黎"时,它并不是在"推理",而是因为在训练数据中,"法国"和"巴黎"在"首都"这个上下文中高频共现。这意味着 LLM 在训练数据覆盖良好的领域表现出色,但在罕见场景下可能给出"看起来合理但实际错误"的答案。

2. LLM 的输出具有概率性。 同样的输入可能产生不同的输出(受 temperature 参数控制)。这不是 bug,而是 feature——它使 LLM 能够生成创造性的内容,但也意味着不能期望 LLM 每次都给出完全一致的结果。

3. LLM 没有持久记忆。 每次对话都是从零开始。LLM 不会"记住"上一次对话的内容,除非这些内容被显式地放入当前的上下文窗口中。这个特性直接决定了为什么需要 CLAUDE.md 这样的外部记忆机制。

4. LLM 的"推理"是涌现行为。 当模型规模足够大、训练数据足够丰富时,LLM 展现出类似推理的能力。但这种能力的边界是模糊的——它在常见模式上表现优异,在需要真正创新推理的场景下则不可靠。

模式提炼:概率性推理

模式公式P(next_token) = f(所有前文 tokens 的统计相关性)

迁移表

场景 输入上下文 统计基础 可靠性 说明
常见编程模式 标准 API 调用代码 训练数据中大量出现 如 Spring Boot REST 端点
项目特定约定 私有框架代码 训练数据中未出现 需要通过 CLAUDE.md 补充上下文
创新性设计 全新架构方案 训练数据中无直接对应 可组合已知模式,但需人工验证
边界情况处理 罕见错误场景 训练数据中稀少 需要显式提供示例和约束

核心洞察:当听到"AI 给了错误答案"时,首先应该检查的不是模型能力,而是上下文是否充分。LLM 的表现与输入上下文的质量直接相关——垃圾进,垃圾出;精确的上下文进,精确的答案出。这就是为什么 CLAUDE.md 和 Skills 机制如此重要——它们本质上是在为 LLM 提供高质量的上下文。

Agent Loop:感知-推理-行动的循环

单独的 LLM 只能做文本补全。要让它成为一个能完成实际任务的 Agent,需要三个要素的组合:LLM(大脑)+ 工具(手脚)+ 循环(自主性)

flowchart TB
    A[用户输入任务] --> B{Agent Loop}
    B --> C[LLM 分析当前状态]
    C --> D{需要更多信息?}
    D -->|是| E[调用工具: 读文件/搜索/执行命令]
    E --> F[工具返回结果]
    F --> B
    D -->|否| G{需要修改代码?}
    G -->|是| H[调用工具: 编辑文件]
    H --> I[验证修改结果]
    I --> B
    G -->|否| J{任务完成?}
    J -->|否| B
    J -->|是| K[输出最终结果]
    style B fill:#fff3e0
    style C fill:#e8f5e9
    style E fill:#e3f2fd
    style H fill:#e3f2fd

这个循环的每一轮都遵循相同的模式:

  1. 感知:LLM 读取当前上下文(用户消息 + 历史对话 + 工具返回的结果)
  2. 推理:基于上下文,决定下一步应该做什么(调用哪个工具、传什么参数,或者直接回复用户)
  3. 行动:执行决策(调用工具或生成回复)
  4. 观察:将行动的结果加入上下文,开始下一轮循环
sequenceDiagram
    participant U as 用户
    participant A as Agent(LLM)
    participant T as 工具集
    
    U->>A: "修复登录页面的 bug"
    
    Note over A: 第1轮: 感知+推理
    A->>T: read_file("src/login.tsx")
    T-->>A: 文件内容
    
    Note over A: 第2轮: 分析+搜索
    A->>T: grep("handleLogin")
    T-->>A: 搜索结果
    
    Note over A: 第3轮: 定位+修复
    A->>T: edit_file("src/login.tsx", changes)
    T-->>A: 修改成功
    
    Note over A: 第4轮: 验证
    A->>T: run_tests("login.test.tsx")
    T-->>A: 测试通过
    
    A->>U: "已修复: 问题是..."

理解 Agent Loop 的关键在于:每一轮循环都会消耗上下文空间。用户的消息、LLM 的思考过程、工具调用的参数、工具返回的结果——所有这些都会被追加到上下文中。这意味着 Agent Loop 的轮次越多,上下文消耗越大,最终可能触及上下文窗口的物理限制。

Agent Loop 还有一个重要特性:LLM 在每一轮都会重新阅读完整的上下文。这既是优势(保证了一致性),也是劣势(随着上下文增长,注意力会被稀释)。

模式提炼:感知-行动循环

模式公式Agent = while(!done) { context = perceive(); action = reason(context); result = act(action); context += result; }

迁移表

场景 感知 推理 行动 说明
Bug 修复 读取错误日志+代码 定位根因 编辑代码+运行测试 典型的多轮循环
代码审查 读取 diff+规范 对比规范找问题 输出审查意见 可能只需 1-2 轮
新功能开发 读取需求+现有架构 设计方案 创建文件+编写代码 轮次最多,上下文消耗最大
重构 读取现有代码+依赖 规划重构步骤 批量修改文件 需要精确的任务分解

核心洞察:当听到"Agent 做了很多无用功"时,问题往往不在 Agent 的推理能力,而在于循环的效率——每一轮是否提供了足够精确的上下文?工具返回的信息是否有助于推进任务?这就是为什么 Commands 和 Skills 机制如此重要——它们通过预定义的工作流减少了 Agent 的"摸索"轮次。

上下文窗口:Agent 的工作记忆

上下文窗口是理解所有 Agentic Coding 策略的关键。可以把它类比为人的工作记忆(Working Memory)——它是 Agent 在任何时刻能"看到"和"思考"的全部信息。

flowchart TB
    subgraph 上下文窗口["上下文窗口 (如 200K tokens)"]
        direction TB
        A["系统提示词 (CLAUDE.md 内容)"]
        B["用户消息历史"]
        C["Agent 的思考过程"]
        D["工具调用记录"]
        E["工具返回结果"]
        F["当前正在生成的回复"]
    end
    
    G["窗口外: 之前的对话"] -.->|"不可见"| 上下文窗口
    H["窗口外: 未读取的文件"] -.->|"不可见"| 上下文窗口
    I["窗口外: 其他项目知识"] -.->|"不可见"| 上下文窗口
    
    style 上下文窗口 fill:#fff8e1
    style G fill:#ffebee
    style H fill:#ffebee
    style I fill:#ffebee

上下文窗口的三个关键特性

1. 有限性

即使是 200K token 的上下文窗口,看起来很大(约 15 万个英文单词),但在实际的 Agent 工作中会被迅速消耗:

pie title "一次典型 Agent 会话的上下文消耗"
    "系统提示词 + Rules" : 10
    "用户消息" : 5
    "Agent 思考过程" : 20
    "工具调用参数" : 10
    "工具返回结果(文件内容等)" : 40
    "Agent 生成的代码" : 15

一个中等复杂度的编程任务,可能在 10-20 轮 Agent Loop 后就消耗掉大部分上下文空间。

2. 注意力退化(Lost in the Middle)

研究表明,LLM 对上下文中不同位置的信息,注意力分布是不均匀的。开头和结尾的信息得到更多关注,而中间部分的信息容易被"遗忘"。

flowchart LR
    subgraph 注意力分布
        direction LR
        A["开头<br/>注意力: 高"] --- B["中间<br/>注意力: 低"] --- C["结尾<br/>注意力: 高"]
    end
    
    D["实际影响:<br/>早期的工具返回结果<br/>可能被后续信息'淹没'"]
    
    注意力分布 --> D

这意味着:随着对话的进行,Agent 可能会"忘记"早期获取的关键信息,导致重复搜索、前后矛盾,甚至引入之前已经修复过的 bug。

3. 上下文污染

上下文中的错误信息不会自动消失。如果 Agent 在第 3 轮生成了一段有 bug 的代码,即使在第 5 轮修复了,第 3 轮的错误代码仍然存在于上下文中,可能在后续轮次中"误导" Agent。

flowchart TB
    A["第3轮: Agent 生成错误代码"] --> B["第4轮: 发现错误"]
    B --> C["第5轮: 修复错误"]
    C --> D["第8轮: Agent 再次参考上下文"]
    D --> E{"看到了哪个版本?"}
    E -->|"可能看到第3轮的错误版本"| F["再次引入相同 bug"]
    E -->|"看到第5轮的修复版本"| G["正确继续"]
    style F fill:#ffcdd2
    style G fill:#c8e6c9

上下文管理的核心策略

理解了上下文窗口的特性后,所有的 Agentic Coding 策略都可以归结为一个核心目标:在有限的上下文空间内,最大化有效信息密度

flowchart TB
    A[上下文管理核心策略] --> B[减少无效信息]
    A --> C[增加有效信息]
    A --> D[隔离信息空间]
    
    B --> B1[短对话优于长对话]
    B --> B2[精确的工具调用]
    B --> B3[避免重复搜索]
    
    C --> C1[CLAUDE.md 预加载关键上下文]
    C --> C2[Skills 注入领域知识]
    C --> C3[结构化的错误信息]
    
    D --> D1[Sub Agent 独立上下文]
    D --> D2[任务分解与隔离]
    D --> D3[新对话 vs 继续对话]

模式提炼:上下文即工作记忆

模式公式有效性 = 有效信息量 / 总上下文消耗 × 注意力权重(位置)

迁移表

场景 上下文压力 推荐策略 说明
简单 bug 修复 单次对话完成 2-5 轮 Loop 即可
中等功能开发 预加载 Rules + 精确搜索 控制在 10-15 轮
大型重构 任务分解 + Sub Agent 每个子任务独立上下文
跨模块修改 极高 多次短对话 + 持久记忆 每个模块一次对话

核心洞察:当听到"Agent 在长对话中表现越来越差"时,根因几乎总是上下文窗口的退化。解决方案不是"换一个更强的模型",而是"开一个新对话"。这就是为什么 Sub Agent 机制如此重要——它通过创建独立的上下文窗口,从根本上解决了上下文污染问题。


Markdown 配置体系:用文档定义智能体

理解了第一性原理之后,再来看 Claude Code 的 Markdown 配置体系,就能理解每一层设计背后的"为什么"。

这套体系包含四种机制:CLAUDE.md(项目记忆)、Commands(参数化触发)、Skills(专业知识包)、Sub Agents(独立执行体)。它们按照关注点分离的原则,各自承担不同的职责。

flowchart TB
    subgraph 四层配置体系
        direction TB
        L1["CLAUDE.md<br/>──────────<br/>项目记忆与行为约束<br/>始终在场 | 自动加载"]
        L2["Commands<br/>──────────<br/>参数化提示词模板<br/>按需加载 | 用户触发"]
        L3["Skills<br/>──────────<br/>可复用知识包<br/>按需加载 | Agent 自动匹配"]
        L4["Sub Agents<br/>──────────<br/>独立执行的专业角色<br/>按需加载 | 独立上下文"]
    end
    
    subgraph 工具扩展层
        PLG["Plugins<br/>──────────<br/>外部工具扩展<br/>独立部署 | MCP 协议"]
    end
    
    L1 -->|"提供环境上下文"| L2
    L2 -->|"触发具体任务"| L3
    L3 -->|"提供专业知识"| L4
    L4 -->|"隔离执行"| R[任务完成]
    
    L4 -.->|"通过 tools 字段引用"| PLG
    PLG -.->|"提供扩展能力"| L4
    
    style L1 fill:#e8eaf6
    style L2 fill:#e3f2fd
    style L3 fill:#e8f5e9
    style L4 fill:#fff3e0
    style PLG fill:#f3e5f5

:Plugins 是外部工具扩展层(MCP Servers 和可执行程序),通过 Sub Agent 的 tools 字段被引用。它与四层配置体系的关系是:四层配置定义"Agent 是什么、怎么做",Plugins 提供"Agent 可以使用哪些工具"。

全景视角:三层提示机制

在深入四层配置体系之前,有必要先理解一个更宏观的分层结构。Agent 的行为实际上由三层提示机制共同决定,它们各自解决不同层次的问题:

flowchart TB
    subgraph L1["L1: 行为宪法 (System Prompt)"]
        A1["安全边界: 拒绝危险操作"]
        A2["工作流规范: 先读文件再修改"]
        A3["工具调用协议: 何时使用哪个工具"]
    end
    
    subgraph L2["L2: 项目记忆 (CLAUDE.md / .cursor/rules)"]
        B1["技术栈: Spring Boot 3.2 + Java 21"]
        B2["架构约束: 严格分层, 禁止跨层调用"]
        B3["团队规范: commit 格式, 代码风格"]
    end
    
    subgraph L3["L3: 任务指令 (用户实时输入)"]
        C1["具体需求: 实现登录表单"]
        C2["即时反馈: 修复这个 bug"]
    end
    
    L1 -->|"由平台预置, 变更频率极低"| L2
    L2 -->|"由用户编写, 随项目演进更新"| L3
    L3 -->|"每次对话动态变化"| D["Agent 执行"]
    
    style L1 fill:#e8eaf6
    style L2 fill:#c5cae9
    style L3 fill:#9fa8da
层级 职责 维护者 变更频率 类比
L1: System Prompt 基础行为准则与安全边界 平台厂商(Anthropic、Cursor 等) 极低 操作系统内核
L2: 项目配置 项目特定知识与约束 开发者/团队 随项目演进 应用配置文件
L3: 用户输入 具体任务指令 用户 每次对话 用户输入

三层为何不能互相替代

仅靠 L2(项目配置)的局限:项目配置文件是用户可编辑的文本,无法提供强制性安全防护——Agent 可能被诱导绕过其中的约束。此外,"调用工具前必须确认"这类行为规范属于工具调用的元协议,必须由 System Prompt 提供底层支持。如果每个项目都要重新定义"先规划再执行"等通用准则,将造成大量冗余。

仅靠 L1(System Prompt)的局限:如果将所有项目知识塞入 System Prompt,会迅速消耗上下文窗口。每个项目需定制独立的 System Prompt,破坏工具的通用性。而且 System Prompt 通常由厂商控制,无法随项目架构的持续变化而动态更新。

三层协同示例

以"为 Next.js 项目添加 Dark Mode"为例:

  • L1(System Prompt) 提供行为框架:必须先理解现有代码再修改;调用执行工具前需用户确认;拒绝执行可能破坏系统的命令
  • L2(CLAUDE.md 提供项目知识:技术栈为 Next.js 14 + TypeScript + Tailwind;主题方案使用 next-themes,配置位于 /lib/theme.ts;所有组件必须基于 shadcn/ui
  • L3(用户输入) 提供具体任务:“在导航栏右侧添加 Dark Mode 切换按钮”

Agent 的执行路径:依据 L1,先读取 /lib/theme.ts 理解现有主题实现;依据 L2,使用 next-themes API 而非自研方案,按钮样式遵循 shadcn/ui;依据 L3,将按钮精准放置在导航栏右侧。三层缺一不可:缺少 L1 会导致盲目生成代码;缺少 L2 会用错技术栈;缺少 L3 则不知具体目标。

本文后续章节聚焦于 L2 层——即开发者可以直接控制的项目配置体系。理解 L1 的存在,有助于理解为什么 CLAUDE.md 不需要重复定义安全规则和通用行为准则。

CLAUDE.md:项目记忆与行为约束

CLAUDE.md 是整个体系的基础层。它解决的核心问题是:LLM 没有持久记忆,每次对话都从零开始。通过在对话开始时自动加载 CLAUDE.md 的内容,Agent 能立即获得项目的关键上下文。

分层加载机制

CLAUDE.md 支持三级作用域,按从全局到局部的顺序加载:

flowchart TB
    A["~/.claude/CLAUDE.md<br/>全局级:跨项目通用偏好"] --> D[合并注入上下文]
    B["项目根目录/CLAUDE.md<br/>项目级:技术栈、规范、架构"] --> D
    C["当前目录/CLAUDE.md<br/>目录级:模块特定约束"] --> D
    D --> E["Agent 开始工作<br/>已具备完整的项目认知"]
    
    style A fill:#e8eaf6
    style B fill:#c5cae9
    style C fill:#9fa8da

这种分层设计的精妙之处在于:它用最小的上下文消耗,提供了最大的信息覆盖。全局偏好只写一次,项目规范只在项目级定义,模块特定的约束只在需要时加载。

CLAUDE.md 的内容结构

一份高效的 CLAUDE.md 应该包含以下信息(按优先级排序):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 项目概述
这是一个基于 Spring Boot 3.2 的电商平台后端服务。
Java 21,Maven 构建,MySQL + Redis。

# 关键命令
- 构建: `mvn clean package -DskipTests`
- 测试: `mvn test -pl module-name`
- 本地运行: `mvn spring-boot:run -pl app`

# 代码规范
- REST API 统一返回 `Result<T>`,见 `common/Result.java`
- 使用 MapStruct 做 DTO 转换,禁止手写 getter/setter 转换
- 异常统一通过 `@RestControllerAdvice` 处理

# 架构约束
- 严格分层: Controller → Service → Repository
- 禁止 Controller 直接访问 Repository
- 跨模块调用必须通过 API 接口,禁止直接依赖实现类

# 当前工作重点
- 正在重构订单模块,从单体迁移到 DDD 风格
- 支付模块不要改动,正在等待第三方 SDK 升级

关键原则CLAUDE.md 应该是声明式的——告诉 Agent “是什么"和"不要做什么”,而不是"怎么做"。具体的"怎么做"应该交给 Skills 和 Commands。

从第一性原理理解 CLAUDE.md 的设计

回到上下文窗口的约束:CLAUDE.md 的内容会在每次对话开始时被注入上下文。这意味着:

  • 内容必须精炼:每多一行,就多消耗一点宝贵的上下文空间
  • 信息密度要高:用最少的 token 传达最关键的信息
  • 避免重复:不要在 CLAUDE.md 中写已经在代码注释或 README 中存在的信息
flowchart LR
    A["CLAUDE.md 内容"] -->|"每次对话都消耗"| B["上下文空间"]
    B -->|"剩余空间"| C["实际工作可用"]
    
    D["过长的 CLAUDE.md"] -->|"挤占"| C
    E["过短的 CLAUDE.md"] -->|"Agent 需要额外搜索<br/>反而消耗更多上下文"| C
    
    F["最优: 精炼但完整"] -->|"平衡"| C
    style F fill:#c8e6c9
    style D fill:#ffcdd2

Commands:参数化的提示词模板

术语说明:本文中 “Commands” 指的是功能概念,即"封装了特定工作流的可调用单元"。当需要强调用户通过 / 前缀触发的方式时,也会使用 “Slash Commands” 这个术语。两者关系是:所有 Slash Commands 都是 Commands,但 Commands 可以被 Agent 自动调用,不一定需要用户手动触发。

Commands 解决的问题是:重复性任务的标准化触发。如果发现自己经常对 Agent 说类似的话(“帮我写一个 API 端点,路径是 X,方法是 Y”),就应该把它封装成一个 Command。

Command 的定义结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
---
name: create-api
description: 创建一个新的 REST API 端点
arguments:
- name: method
description: HTTP 方法 (GET/POST/PUT/DELETE)
required: true
- name: path
description: API 路径 (如 /api/v1/users)
required: true
- name: description
description: API 功能描述
required: false
---

创建一个新的 REST API 端点。

HTTP 方法: $ARGUMENTS.method
API 路径: $ARGUMENTS.path
功能描述: $ARGUMENTS.description

请按照以下步骤实现:
1. 在对应的 Controller 中添加端点方法
2. 在 Service 层添加业务逻辑接口和实现
3. 如需要,在 Repository 层添加数据访问方法
4. 编写单元测试
5. 更新 API 文档

遵循项目的 REST API 规范(见 CLAUDE.md)。

用户只需输入 /create-api POST /api/v1/orders "创建订单" 即可触发完整的工作流。

Command 与上下文的关系

从上下文管理的角度看,Command 的价值在于:用少量的用户输入 token,展开为大量的精确指令 token。这比用户每次手动输入完整指令更高效,也更一致。

flowchart LR
    A["用户输入:<br/>/create-api POST /orders"] -->|"展开"| B["完整的提示词模板<br/>+ 参数值<br/>+ 工作流步骤<br/>+ 规范引用"]
    
    C["用户手动输入:<br/>帮我创建一个 POST API...<br/>路径是 /orders...<br/>记得遵循规范...<br/>要写测试..."] -->|"对比"| D["信息可能遗漏<br/>表述可能不一致<br/>消耗更多 token"]
    
    style A fill:#c8e6c9
    style C fill:#ffcdd2

Skills:可复用的知识包

Skills 是 Commands 的升级版。如果说 Command 是一个"提示词模板",那么 Skill 就是一个"知识包"——它不仅包含指令,还包含领域知识、参考文档、工作流定义。

Skill 的完整结构

1
2
3
4
5
6
7
8
9
.claude/skills/
└── api-design/
├── skill.md # Skill 定义(入口)
└── references/
├── rest-conventions.md # REST API 设计规范
├── error-codes.md # 错误码定义
└── examples/
├── user-api.md # 用户 API 示例
└── order-api.md # 订单 API 示例

Skill 定义文件(skill.md)的结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
---
name: api-design
description: REST API 设计专家,遵循团队的 API 设计规范
---

作为 API 设计专家,在设计和实现 REST API 时:

1. 遵循 `references/rest-conventions.md` 中的命名和路径规范
2. 错误响应使用 `references/error-codes.md` 中定义的错误码
3. 参考 `references/examples/` 中的示例了解团队风格

设计原则:
- URL 使用名词复数形式(/users, /orders)
- 使用标准 HTTP 状态码
- 响应体统一使用 Result<T> 包装
- 分页使用 cursor-based pagination

Skill 的加载机制

当 Agent 判断当前任务需要某个 Skill 的知识时,会自动加载该 Skill 的全部内容(包括 references/ 目录下的文件)到上下文中。这个过程是按需的——只有在需要时才加载,避免了不必要的上下文消耗。

sequenceDiagram
    participant U as 用户
    participant A as Agent
    participant S as Skill 工具
    
    U->>A: "设计一个订单退款 API"
    
    Note over A: 判断需要 API 设计知识
    A->>S: 搜索匹配的 Skill
    S-->>A: 找到 api-design Skill
    
    Note over A: 加载 Skill 内容到上下文
    A->>S: 读取 skill.md + references/*
    S-->>A: API 规范 + 错误码 + 示例
    
    Note over A: 基于 Skill 知识执行任务
    A->>U: 按照团队规范设计的退款 API

Skills 与 CLAUDE.md 的分工

flowchart TB
    subgraph CLAUDE.md
        A1["项目级通用信息"]
        A2["技术栈声明"]
        A3["全局行为约束"]
    end
    
    subgraph Skills
        B1["领域专业知识"]
        B2["详细的参考文档"]
        B3["具体的工作流程"]
    end
    
    C["上下文空间"] 
    
    CLAUDE.md -->|"始终占用"| C
    Skills -->|"按需占用"| C
    
    D["设计原则:<br/>CLAUDE.md 放'必须始终知道的'<br/>Skills 放'需要时才加载的'"]
    
    style D fill:#e8f5e9

Sub Agents:独立执行的专业角色

Sub Agents 是四层体系中最强大的机制。它解决的核心问题是:上下文隔离

回到第一性原理:上下文窗口是有限的,长对话会导致注意力退化和上下文污染。Sub Agent 通过创建独立的上下文窗口,从根本上解决了这个问题。

flowchart TB
    subgraph 主对话["主 Agent 上下文窗口"]
        M1["用户任务"]
        M2["项目上下文"]
        M3["任务分解决策"]
    end
    
    subgraph SA1["Sub Agent A 上下文窗口"]
        A1["独立的上下文"]
        A2["专属工具权限"]
        A3["子任务 A 的全部工作"]
    end
    
    subgraph SA2["Sub Agent B 上下文窗口"]
        B1["独立的上下文"]
        B2["专属工具权限"]
        B3["子任务 B 的全部工作"]
    end
    
    主对话 -->|"委派子任务 A"| SA1
    主对话 -->|"委派子任务 B"| SA2
    SA1 -->|"只返回摘要结果"| 主对话
    SA2 -->|"只返回摘要结果"| 主对话
    
    style 主对话 fill:#e8eaf6
    style SA1 fill:#e3f2fd
    style SA2 fill:#e8f5e9

Sub Agent 的定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
---
name: code-reviewer
description: 代码审查专家,专注于代码质量和安全性审查
tools: Read, Grep, Glob
---

作为代码审查专家,执行以下审查流程:

1. 读取待审查的代码文件
2. 检查代码质量:命名规范、函数长度、复杂度
3. 检查安全性:SQL 注入、XSS、敏感信息泄露
4. 检查性能:N+1 查询、不必要的循环、内存泄漏风险
5. 输出结构化的审查报告

审查标准:
- 函数不超过 30 行
- 圈复杂度不超过 10
- 所有外部输入必须验证
- 数据库查询必须使用参数化

注意 tools: Read, Grep, Glob——这个 Sub Agent 只有读取权限,没有编辑权限。工具列表就是权限边界。

内置的默认 Sub Agents

除了用户自定义的 Sub Agents,Claude Code 还提供了几个内置的默认 Sub Agents,它们针对特定的任务类型进行了优化:

内置 Agent 用途 特点
explore 代码探索和理解 只读权限,专注于理解代码结构和关系
analyze 深度分析 只读权限,专注于性能、安全性、架构分析
edit 代码修改 读写权限,专注于精确的代码修改
test 测试生成和执行 读写权限,专注于测试覆盖和验证

这些内置 Agent 的优势在于:

1. 预优化的行为模式

  • explore Agent 会优先使用 grepfind 等搜索工具,快速定位代码
  • analyze Agent 会使用静态分析工具,输出结构化的分析报告
  • edit Agent 会采用更保守的修改策略,优先使用 edit_file 而非重写整个文件

2. 自动化的上下文管理

  • 每个内置 Agent 都有自己优化的上下文加载策略
  • explore 会优先读取目录结构和关键文件
  • analyze 会加载依赖关系和调用链信息

3. 与用户自定义 Sub Agent 的协同

flowchart TB
    subgraph 内置Sub Agents
        EXP["explore<br/>代码探索"]
        ANA["analyze<br/>深度分析"]
        EDT["edit<br/>代码修改"]
        TST["test<br/>测试验证"]
    end
    
    subgraph 用户自定义Sub Agents
        USA1["code-reviewer<br/>代码审查"]
        USA2["api-developer<br/>API开发"]
    end
    
    U[用户] -->|"简单任务"| EXP
    U -->|"简单任务"| EDT
    U -->|"复杂任务"| USA1
    U -->|"复杂任务"| USA2
    
    USA1 -.->|"调用内置"| ANA
    USA2 -.->|"调用内置"| TST
    
    style EXP fill:#e3f2fd
    style ANA fill:#e3f2fd
    style EDT fill:#e3f2fd
    style TST fill:#e3f2fd
    style USA1 fill:#fff3e0
    style USA2 fill:#fff3e0

用户自定义的 Sub Agent 可以调用内置 Agent来完成子任务。例如:

1
2
3
4
5
6
7
8
9
10
11
---
name: code-reviewer
description: 代码审查专家
tools: Read, Grep, Glob, task
---

作为代码审查专家,执行以下审查流程:

1. 使用内置的 `explore` Agent 理解代码结构
2. 使用内置的 `analyze` Agent 检测潜在问题
3. 综合分析结果,输出审查报告

4. 何时使用内置 vs 自定义

场景 推荐 原因
快速浏览代码 explore 内置优化,无需额外配置
查找性能问题 analyze 内置分析工具,结果更准确
简单的代码修改 edit 内置的保守修改策略
项目特定的审查规则 自定义 Sub Agent 需要注入项目规范
领域专业知识 自定义 Sub Agent + Skill 需要领域知识

Sub Agent 的高级特性

1. 持久化记忆

Sub Agent 可以拥有跨对话持久化的记忆目录:

1
2
3
4
5
---
name: code-reviewer
description: 代码审查专家
memory: project
---

memory 字段支持三种作用域:

作用域 存储位置 适用场景
user ~/.claude/agent-memory/<name>/ 跨项目通用的经验积累
project .claude/agent-memory/<name>/ 项目特定的知识,可提交 Git
local .claude/agent-memory-local/<name>/ 项目特定但不提交 Git

启用记忆后,Sub Agent 的系统提示词中会自动注入记忆目录中 MEMORY.md 的前 200 行内容。这意味着 Agent 会随着使用越来越"聪明"——它在审查代码时发现的模式、踩过的坑,都会被记录下来,下次自动参考。

2. Hook 机制——工具调用的拦截与增强

Sub Agent 支持在工具调用前后执行自定义脚本:

1
2
3
4
5
6
7
8
9
10
11
---
name: db-reader
description: 只读数据库查询专家
tools: Bash
hooks:
PreToolUse:
- matcher: "Bash"
hooks:
- type: command
command: "./scripts/validate-readonly-query.sh"
---

对应的验证脚本会在每次 Bash 调用前拦截并验证命令内容,只允许 SELECT 查询通过。这实现了比简单的工具白名单更细粒度的权限控制。

3. 预加载 Skills

Sub Agent 可以声明需要预加载的 Skills:

1
2
3
4
5
6
7
---
name: api-developer
description: API 开发专家
skills:
- api-conventions
- error-handling-patterns
---

skills 字段列出的 Skill 的全部内容会被注入到 Sub Agent 的上下文中,使得 Sub Agent 在启动时就具备了特定领域的专业知识。

四种机制的 MECE 对比

flowchart TB
    subgraph 何时加载
        direction LR
        A1["始终在场"] ---|"←→"| A2["按需加载"]
        A1 --- A1a["CLAUDE.md"]
        A2 --- A2a["Commands / Skills / Sub Agents"]
    end
    
    subgraph 在哪执行
        direction LR
        B1["主对话上下文"] ---|"←→"| B2["独立上下文"]
        B1 --- B1a["CLAUDE.md / Commands / Skills"]
        B2 --- B2a["Sub Agents"]
    end
    
    subgraph 信息密度
        direction LR
        C1["低密度<br/>CLAUDE.md"] --- C2["中密度<br/>Commands"] --- C3["高密度<br/>Skills"] --- C4["最高密度<br/>Sub Agents"]
    end
维度 CLAUDE.md Commands Skills Sub Agents
加载时机 对话开始自动加载 用户 / 触发 Agent 自动匹配 Agent 委派任务
执行上下文 主对话 主对话 主对话 独立上下文窗口
工具权限 继承主对话 继承主对话 继承主对话 独立工具白名单
持久记忆 不支持 不支持 不支持 支持
信息密度 声明式偏好 提示词模板 知识+流程 角色+工具+流程+权限+记忆
适用场景 项目级通用约束 重复性任务标准化 领域专业知识 自包含的独立任务

判断标准:如果任务需要频繁的用户交互和迭代,用 Skill;如果任务是自包含的、可以独立完成并返回摘要的,用 Sub Agent。

深度理解:Skill vs Sub Agent 的本质区别

为了更深入地理解 Skill 和 Sub Agent 的机制差异,让我们从第一性原理出发,通过问答形式来澄清关键概念。

Q: Skill 和 Sub Agent 在上下文管理上的本质区别是什么?

A: 核心区别在于上下文融合 vs 上下文隔离

  • Skill(内联融合):Skill 的全部内容会被直接插入到当前对话的上下文中,成为上下文的一部分。Agent 在同一个上下文窗口中继续工作,Skill 不创建新的上下文窗口,只是向现有上下文"注入"信息。这类似于编程中的"宏"(Macro)展开。
flowchart TB
    subgraph 主对话上下文窗口
        A["用户消息"] 
        B["Agent 思考"]
        C["Skill 内容<br/>(直接插入上下文)"]
        D["Agent 继续推理"]
        E["工具调用"]
    end
    
    C -.->|"按需加载<br/>成为上下文的一部分"| D
    
    style C fill:#e8f5e9
  • Sub Agent(独立上下文):Sub Agent 拥有完全独立的上下文窗口,与主对话隔离。主对话只传递任务摘要,Sub Agent 内部的工作过程对主对话不可见(黑箱执行)。Sub Agent 完成后只返回结果摘要,不返回完整的上下文。这类似于函数调用或服务调用。
flowchart TB
    subgraph 主对话上下文["主对话上下文"]
        A["用户任务"]
        B["任务分解决策"]
        C["委派请求"]
    end
    
    subgraph SubAgent独立上下文["Sub Agent 独立上下文窗口"]
        D["接收任务描述"]
        E["独立推理"]
        F["独立工具调用"]
        G["生成结果"]
    end
    
    C -->|"摘要传递"| D
    G -->|"摘要返回"| H["主对话继续"]
    
    style 主对话上下文 fill:#e8eaf6
    style SubAgent独立上下文 fill:#fff3e0

Q: Sub Agent 可以使用 Skills 吗?

A: 是的,而且有两种方式。

方式 1:预加载 Skills(启动时注入)

Sub Agent 可以在定义中声明需要预加载的 Skills:

1
2
3
4
5
6
7
---
name: api-developer
description: API 开发专家
skills:
- api-conventions
- error-handling-patterns
---

机制:Sub Agent 启动时,skills 字段列出的 Skill 的全部内容会被注入到 Sub Agent 的独立上下文窗口中。这些 Skill 成为 Sub Agent 上下文的一部分,在 Sub Agent 的整个生命周期中可用。这是Sub Agent 内部的 Skill,与主对话的上下文无关。

flowchart TB
    subgraph SubAgent独立上下文["Sub Agent 独立上下文窗口"]
        A["系统提示词"]
        B["预加载的 Skill 1<br/>(api-conventions)"]
        C["预加载的 Skill 2<br/>(error-handling)"]
        D["任务描述"]
        E["Agent 推理"]
        F["工具调用"]
    end
    
    B -.->|"上下文的一部分"| E
    C -.->|"上下文的一部分"| E
    
    style B fill:#e8f5e9
    style C fill:#e8f5e9

方式 2:运行时动态加载(通过 task 工具调用内置 Agent)

Sub Agent 可以调用内置 Agent(如 exploreanalyze),这些内置 Agent 会自动匹配并加载相关的 Skills。这是Sub Agent 主动触发的 Skill 加载,发生在 Sub Agent 的独立上下文中。

1
2
3
4
5
6
7
8
9
10
11
---
name: code-reviewer
description: 代码审查专家
tools: Read, Grep, Glob, task
---

作为代码审查专家,执行以下审查流程:

1. 使用内置的 `explore` Agent 理解代码结构
2. 使用内置的 `analyze` Agent 检测潜在问题
3. 综合分析结果,输出审查报告

Q: 如何在编程类比中理解 Skill 和 Sub Agent?

A:

维度 Skill Sub Agent
编程类比 宏(Macro) 函数/服务调用
上下文位置 内联到当前对话上下文 独立的上下文窗口
加载时机 Agent 判断需要时按需加载 Sub Agent 启动时预加载 / 运行时调用
可见性 对当前对话完全可见 对主对话不可见(黑箱)
隔离性 无隔离,共享上下文 完全隔离
返回方式 直接返回结果 只返回摘要
Sub Agent 能否使用 不适用(Skill 是内容) ✅ 可以预加载或动态调用

核心洞察

  • Skill内容注入(向上下文添加信息)
  • Sub Agent执行隔离(创建独立的执行环境)
  • Skill 像"把代码内联展开",Sub Agent 像"调用一个服务"
  • Sub Agent 可以使用 Skills,但那些 Skill 是在 Sub Agent 的独立上下文中,与主对话无关

Q: 什么时候应该用 Skill,什么时候应该用 Sub Agent?

A: 回到之前的判断标准,但用机制视角重新理解:

  • 用 Skill 的情况

    • 任务需要频繁的用户交互和迭代
    • 需要领域专业知识
    • 需要在主对话上下文中与用户持续沟通
    • 关键:期望 Agent "内联"地使用这些知识,保持上下文的连续性
  • 用 Sub Agent 的情况

    • 任务是自包含的
    • 可以独立完成并返回摘要
    • 需要独立的上下文窗口
    • 需要独立的工具权限控制
    • 需要持久化记忆
    • 关键:期望 Agent "隔离"地执行任务,避免污染主对话的上下文

这个判断标准背后的第一性原理是:上下文窗口的有限性和注意力退化。Skill 通过内联注入增加了上下文空间的有效信息密度,而 Sub Agent 通过隔离执行从根本上解决了上下文污染问题。选择 Skill 还是 Sub Agent,本质上是在"增加信息密度"和"隔离信息空间"之间做权衡。

组件包含关系全景图

graph TB
    subgraph 配置组件
        MD["CLAUDE.md<br/>项目记忆"]
        CMD["Commands<br/>参数化触发"]
        SKL["Skills<br/>知识包"]
        SA["Sub Agents<br/>独立执行体"]
        PLG["Plugins<br/>外部工具"]
    end
    
    subgraph SubAgent可包含
        SA_tools["tools<br/>工具白名单"]
        SA_memory["memory<br/>持久记忆"]
        SA_hooks["hooks<br/>工具拦截"]
        SA_skills["skills<br/>预加载知识"]
    end
    
    subgraph Skill可包含
        SKL_refs["references/<br/>参考文档目录"]
        SKL_ref_files["*.md<br/>规范/示例/文档"]
    end
    
    subgraph 独立组件
        IND1["Commands<br/>独立配置"]
        IND2["CLAUDE.md<br/>独立配置"]
        IND3["Plugins<br/>独立部署"]
    end
    
    subgraph Plugin类型
        PLG_mcp["MCP Servers<br/>标准协议工具"]
        PLG_exec["可执行程序<br/>命令行工具"]
    end
    
    SA -->|"可以包含"| SA_tools
    SA -->|"可以包含"| SA_memory
    SA -->|"可以包含"| SA_hooks
    SA -->|"可以包含"| SA_skills
    
    SKL -->|"可以包含"| SKL_refs
    SKL_refs -->|"包含"| SKL_ref_files
    
    PLG -->|"包含类型"| PLG_mcp
    PLG -->|"包含类型"| PLG_exec
    
    CMD -.->|"不包含其他组件"| IND1
    MD -.->|"不包含其他组件"| IND2
    PLG -.->|"独立部署"| IND3
    
    SA_tools -.->|"可以引用"| PLG_mcp
    SA_tools -.->|"可以引用"| PLG_exec
    
    style MD fill:#e8eaf6
    style CMD fill:#e3f2fd
    style SKL fill:#e8f5e9
    style SA fill:#fff3e0
    style PLG fill:#f3e5f5
    style SA_tools fill:#ffe0b2
    style SA_memory fill:#ffe0b2
    style SA_hooks fill:#ffe0b2
    style SA_skills fill:#ffe0b2
    style SKL_refs fill:#c8e6c9
    style SKL_ref_files fill:#a5d6a7
    style IND1 fill:#ffcdd2
    style IND2 fill:#ffcdd2
    style IND3 fill:#e1bee7
    style PLG_mcp fill:#ce93d8
    style PLG_exec fill:#ce93d8

关键洞察

  • Sub Agent 是最复杂的组件,可以包含 tools(权限控制)、memory(持久化)、hooks(工具拦截)、skills(预加载知识)
  • Skill 可以包含 references/ 目录下的多个参考文档文件
  • Plugins 是独立部署的工具,包括 MCP Servers 和可执行程序,可以被 Sub Agent 的 tools 字段引用
  • CommandsCLAUDE.md 是独立的配置文件,不包含其他组件

Plugins 的特殊地位

  • Plugins 是唯一需要独立部署和运行的组件(其他都是 Markdown 配置文件)
  • Plugins 提供了扩展 Agent 能力的机制,通过 MCP 协议或标准输入输出与 Agent 通信
  • Sub Agent 可以通过 tools 字段声明可用的 Plugins,实现权限控制

四种机制的协同工作

以一个完整的场景说明四种机制如何协同:在电商项目中为订单模块添加退款 API。

sequenceDiagram
    participant U as 用户
    participant M as 主 Agent
    participant C as CLAUDE.md
    participant CMD as Command
    participant SK as Skill
    participant SA1 as Sub Agent: reviewer
    participant SA2 as Sub Agent: developer
    
    Note over M,C: 第一层: CLAUDE.md 自动加载
    C-->>M: 项目技术栈、规范、架构信息
    
    Note over U,CMD: 第二层: 用户触发 Command
    U->>M: /create-api POST /api/v1/orders/{id}/refund
    CMD-->>M: 展开为完整的任务指令
    
    Note over M,SK: 第三层: Skill 自动匹配
    M->>SK: 加载 api-design Skill
    SK-->>M: REST 规范 + 错误码 + 示例
    
    Note over M,SA2: 第四层: Sub Agent 并行执行
    M->>SA1: 审查现有退款相关代码(只读权限)
    M->>SA2: 实现退款 API(读写权限, 预加载 api-conventions Skill)
    SA1-->>M: 审查报告摘要
    SA2-->>M: 实现完成摘要
    
    M->>U: 综合报告 + 最终结果

CLAUDE.md 提供"我是谁、我在哪"的环境上下文,Commands 提供"做这件事"的触发入口,Skills 提供"怎么做好这件事"的专业知识,Sub Agents 提供"让专人去做"的执行隔离。

工具扩展层:MCP 协议

前文提到 Agent 通过 JSON Schema 描述工具接口,LLM 基于 Schema 生成结构化的工具调用参数。但在实际的多工具、多环境生态中,一个关键问题浮现:如何让 Agent 以标准化的方式发现和调用异构的外部工具?

MCP(Model Context Protocol)正是为解决这个问题而设计的标准化工具调用协议。它的核心设计原则是:强制工具提供者以运行时可发现的方式声明自身能力,而非依赖静态文档。

三层架构

MCP 集成涉及三个职责分离的组件:

flowchart LR
    subgraph Client["LLM Client (如 Claude Code)"]
        C1["接收用户自然语言"]
        C2["调用 tools/list 获取工具定义"]
        C3["将 Schema + 用户消息发送给 LLM"]
        C4["执行 tools/call"]
        C5["将结果回传 LLM"]
    end
    
    subgraph Server["MCP Server (适配层)"]
        S1["声明能力 (JSON Schema)"]
        S2["接收结构化调用"]
        S3["调用上游 API"]
    end
    
    subgraph Upstream["上游 API (如 Sentry REST)"]
        U1["实际业务逻辑"]
    end
    
    Client -->|"JSON-RPC"| Server
    Server -->|"HTTP/SDK"| Upstream
    
    style Client fill:#e3f2fd
    style Server fill:#fff3e0
    style Upstream fill:#e8f5e9

完整交互流程:用户输入自然语言请求 → Client 调用 MCP Server 的 tools/list 获取工具定义(JSON Schema)→ Client 将 Schema + 用户消息发送给云端 LLM → LLM 生成结构化的 tool_use 指令 → Client 执行 tools/call 将参数发给 MCP Server → MCP Server 调用上游 API 并返回结果 → Client 将结果回传 LLM 生成最终回复。

关键澄清:MCP Server 完全不需要理解自然语言。它只处理结构化 JSON-RPC 消息,职责是声明能力(Schema)和执行调用。自然语言理解完全由 LLM 负责。

为什么需要 MCP 而非直接使用 OpenAPI

表面上看,Client 与 MCP Server 之间只是 JSON 输入输出,似乎直接调用带 OpenAPI 文档的 REST API 即可。但 MCP 提供了几个关键的抽象价值:

维度 OpenAPI MCP 差异
抽象层级 资源抽象(GET /issues/{id} 动作抽象(get_issue_details(id) LLM 无需理解路径模板、查询参数等传输层细节
发现方式 静态文档(设计时阅读) 运行时调用 tools/list 可根据权限/环境动态返回不同工具集
生态标准化 每个 API 各自集成 统一接入层 多个 LLM 客户端可复用同一套工具
交互原语 无标准机制 流式结果、进度通知、操作取消 原生支持有状态交互

类比:OpenAPI 如同每个电器自带不同规格的电源线(两脚/三脚/欧标),MCP 则是 USB-C 标准——所有设备统一接口,主机无需关心设备细节。

MCP 与 RAG、GraphQL 的关系

  • MCP vs RAG:RAG 是知识增强(向量检索 → 注入上下文 → 只读),MCP 是能力增强(工具调用 → 执行 → 可写)。MCP 可以封装 RAG 作为工具(如定义 search_knowledge_base),但 RAG 无法替代 MCP 的动作执行能力
  • MCP vs GraphQL:GraphQL 是查询语言(幂等、无副作用),面向开发者手写查询;MCP 是动作协议(可能有副作用),面向 LLM 自动生成调用。两者都有 Schema 和自省能力,但语义目标不同

MCP 的本质是为 LLM 提供标准化的"手",使其能安全操作外部世界。当面对单一稳定 API 时,MCP 确实显得冗余;但当需要管理多工具、多环境、多客户端的异构生态时,MCP 通过运行时能力发现、传输层解耦和标准化交互原语,提供了不可替代的互操作性价值。

配置化 vs 编程化:两种构建 Agent 的路径

至此,Markdown 配置体系的四种机制及其工具扩展层已经完整呈现。一个自然的问题是:这种配置化的方式与传统的编程化方式相比,各有什么优劣?

基于 YAML frontmatter 的 Markdown Sub Agent 和基于 Claude Agent SDK 构建的 Agent,代表了构建 Agent 的两条不同路径:配置化路径编程化路径

配置化路径:Markdown Sub Agent

这是本文重点讨论的方式,通过在 Markdown 文件中编写 YAML frontmatter 来定义 Agent。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
---
name: code-reviewer
description: 代码审查专家
tools: Read, Grep, Glob
memory: project
skills:
- api-conventions
---

作为代码审查专家,执行以下审查流程:
1. 读取待审查的代码文件
2. 检查代码质量:命名规范、函数长度、复杂度
3. 检查安全性:SQL 注入、XSS、敏感信息泄露
...

核心特点:

  • 声明式配置:用自然语言 + YAML 描述"是什么"和"怎么做"
  • 无需编程:不需要写代码,只需要写文档
  • 即时生效:保存文件即可使用,无需编译部署
  • 平台依赖:依赖于特定平台(Claude Code、Aone Copilot 等)对这些配置的解析
  • 能力受限:只能使用平台提供的工具和功能

适用场景:

  • 在 IDE 内开发,需要 AI 辅助编程
  • 定义一个专业角色供团队共享
  • 封装重复性工作流
  • 快速实验和迭代
  • 个人或团队内部使用

编程化路径:基于 SDK 构建的 Agent

这是通过编程语言(Python/TypeScript/Java 等)直接调用 Claude API 来构建 Agent 的方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
from anthropic import Anthropic

class CodeReviewerAgent:
def __init__(self, api_key: str):
self.client = Anthropic(api_key=api_key)
self.memory = []

def review_code(self, code: str) -> str:
response = self.client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=4096,
system=self._build_system_prompt(),
messages=[
{"role": "user", "content": f"请审查以下代码:\n\n{code}"}
]
)
return response.content[0].text

def _build_system_prompt(self) -> str:
return """代码审查专家。执行以下审查流程:
1. 检查代码质量:命名规范、函数长度、复杂度
2. 检查安全性:SQL 注入、XSS、敏感信息泄露
..."""

def save_to_memory(self, review: str):
"""持久化记忆到数据库"""
pass

agent = CodeReviewerAgent(api_key="your-key")
result = agent.review_code("def foo(): pass")
agent.save_to_memory(result)

核心特点:

  • 编程式构建:用代码实现 Agent 的每一行逻辑
  • 完全控制:可以精确控制 Agent 的推理流程、工具调用、错误处理
  • 平台无关:可以集成到任何系统(Web 应用、CI/CD、微服务)
  • 无限扩展:可以调用任意 API、数据库、外部服务
  • 可部署:独立部署为服务,提供 API 对外访问

适用场景:

  • 构建面向最终用户的 AI 产品(如代码审查 SaaS 平台)
  • 需要将 AI 能力集成到现有业务系统
  • 需要 API 对外提供服务
  • 需要复杂的业务逻辑和持久化
  • 需要独立的部署和运维

关键对比表

维度 Markdown Sub Agent SDK Agent
构建方式 写 Markdown 文档 写代码(Python/TS/Java)
入门门槛 低(会写文档即可) 中(需要编程能力)
部署方式 保存在特定目录 部署为服务/应用
执行环境 特定 IDE/平台内 任意环境(浏览器、服务器、容器)
能力范围 平台提供的工具 无限(API、数据库、网络、第三方服务)
可集成性 仅限平台生态 可集成到任何系统
定制灵活性 受限于配置字段 完全自定义逻辑
迭代速度 快(改文档即生效) 中(需重新编译部署)
维护复杂度 中高(代码维护、依赖管理)
持久化 平台提供的 memory 完全自定义(数据库、文件等)
权限控制 平台的 tools 白名单 完全自定义权限体系
错误处理 平台默认处理 完全自定义错误处理
监控和日志 平台提供 需要自行实现

深度对比:从第一性原理理解

1. 表达能力的差异

flowchart TB
    subgraph 配置化路径
        A1["YAML frontmatter"] --> A2["有限的结构化字段"]
        A2 --> A3["声明式描述"]
        A3 --> A4["平台解释执行"]
    end
    
    subgraph 编程化路径
        B1["编程语言"] --> B2["完整的图灵完备性"]
        B2 --> B3["命令式逻辑"]
        B3 --> B4["自定义执行引擎"]
    end
    
    A4 -->|"适用场景:快速配置、标准化"| C[选择配置化]
    B4 -->|"适用场景:复杂逻辑、深度集成"| D[选择编程化]
    
    style A3 fill:#fff3e0
    style B3 fill:#e8f5e9
  • 配置化:通过预定义的字段(name、description、tools、skills 等)表达意图。这种方式简洁但受限,适合表达"是什么"和"怎么做",但难以表达复杂的分支逻辑、异常处理、状态机等。
  • 编程化:通过编程语言的完整表达能力(条件判断、循环、异常、类、函数等)实现任意逻辑。这种方式灵活但复杂,适合实现复杂的业务流程、多轮对话、状态管理等。

2. 集成能力的差异

flowchart LR
    subgraph 配置化集成
        A1["平台工具集"] --> A2["文件读写<br/>代码搜索<br/>命令执行"]
        A2 --> A3["有限集成"]
    end
    
    subgraph 编程化集成
        B1["任意集成"] --> B2["数据库<br/>外部API<br/>消息队列<br/>文件系统<br/>第三方服务"]
        B2 --> B3["无限集成"]
    end
    
    style A3 fill:#ffe0b2
    style B3 fill:#c8e6c9
  • 配置化:只能使用平台提供的工具(Read、Grep、Bash 等)。无法直接连接数据库、调用外部 API、发送消息等。
  • 编程化:可以调用任何 Python/TypeScript/Java 库,连接任何服务,实现任何集成需求。

3. 部署和运维的差异

flowchart TB
    subgraph 配置化部署
        A1["保存文件"] --> A2["平台自动加载"] --> A3["立即可用"]
    end
    
    subgraph 编程化部署
        B1["编写代码"] --> B2["编译打包"] --> B3["部署服务"] --> B4["配置监控"] --> B5["运维保障"]
    end
    
    A3 -->|"适合:个人/团队内部<br/>快速迭代"| C[配置化]
    B5 -->|"适合:产品化<br/>生产环境"| D[编程化]
    
    style A2 fill:#e8f5e9
    style B3 fill:#fff3e0
  • 配置化:零运维成本,平台负责加载、执行、监控。适合个人和团队内部使用。
  • 编程化:需要完整的软件工程实践(版本控制、CI/CD、监控、日志、错误追踪、负载均衡等)。适合产品化和生产环境。

如何选择

选择配置化路径(Markdown Sub Agent)的场景:

  1. 需求是"辅助编程"而非"构建产品"——在 IDE 中开发,需要 AI 帮助写代码、重构、调试;定义一个专业角色供团队成员共享使用
  2. 需要快速迭代和实验——不断调整 Agent 的行为和工作流,不希望每次修改都重新部署
  3. 使用场景是团队内部——不需要对外提供 API,不需要复杂的权限控制和用户管理
  4. 不需要深度集成外部系统——只需要文件读写、代码搜索、命令执行等基础能力

选择编程化路径(SDK Agent)的场景:

  1. 需求是"构建产品"而非"辅助编程"——开发一个 AI 应用(如代码审查 SaaS、智能客服平台),需要将 AI 能力集成到现有产品中
  2. 需要对外提供服务——提供 REST API 让第三方调用,需要用户认证、权限控制、计费等
  3. 需要深度集成业务系统——连接公司数据库、调用内部 API,与现有业务流程深度集成
  4. 需要复杂的功能——多轮对话、状态管理、工作流引擎、自定义的监控和错误处理

混合使用:两全其美的方案

实际上,这两种方式并不是互斥的。成熟的 AI 应用往往会采用混合策略:

flowchart TB
    A[用户需求] --> B{是否需要<br/>对外提供服务?}
    B -->|否| C[使用配置化路径<br/>Markdown Sub Agent]
    B -->|是| D{是否需要<br/>复杂集成?}
    D -->|否| E[使用 SDK Agent<br/>简单封装 API]
    D -->|是| F[混合方案<br/>SDK 调用 + 配置化定义]
    
    F --> G["底层:SDK 实现 API 和集成"]
    F --> H["上层:配置化定义业务逻辑"]
    
    style C fill:#e8f5e9
    style E fill:#fff3e0
    style F fill:#e1bee7

混合方案示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class CodeReviewService:
"""SDK 层:提供 API 和集成能力"""

def __init__(self, claude_api_key: str, db_connection):
self.client = Anthropic(api_key=claude_api_key)
self.db = db_connection

def load_sub_agent_config(self, agent_name: str) -> dict:
"""从数据库加载 Sub Agent 配置"""
config = self.db.query(
"SELECT * FROM agents WHERE name = ?",
(agent_name,)
)
return {
"name": config.name,
"description": config.description,
"system_prompt": config.system_prompt,
"tools": json.loads(config.tools)
}

def execute_agent(self, agent_name: str, input_data: str):
"""执行 Sub Agent"""
config = self.load_sub_agent_config(agent_name)
response = self.client.messages.create(
model="claude-sonnet-4-20250514",
system=config["system_prompt"],
messages=[{"role": "user", "content": input_data}]
)
return response.content[0].text

这种混合方案结合了两者的优势:

  • SDK 层:提供稳定的 API、数据库集成、认证授权、监控日志
  • 配置化层:让非程序员也能通过 UI 或 API 动态定义和修改 Agent,无需重新部署

模式提炼:配置化 vs 编程化

模式公式选择 = f(是否产品化 × 集成复杂度 × 迭代速度需求)

迁移表

场景 推荐路径 理由
个人开发辅助 配置化 快速迭代,无需部署
团队共享 Agent 配置化 简单易用,通过 Git 协作
AI 辅助插件 编程化 需要深度集成 IDE
代码审查 SaaS 编程化 需要用户体系、计费、监控
内部 AI 平台 混合 SDK 提供平台能力,配置化让用户自定义
CI/CD 集成 混合 SDK 接入 CI 系统,配置化定义审查规则

核心洞察:配置化和编程化不是对立的,而是互补的。配置化路径适合"快速定义和使用",编程化路径适合"深度集成和产品化"。理解两者的区别,才能在合适的场景选择合适的工具。


更广泛的视角:Rules 和 Specs

在不同的 AI 编码平台中,还存在 RulesSpecs 这样的概念。它们本质上是对上述体系的变体或补充。

Rules:CLAUDE.md 的跨平台等价物

平台 配置文件
Claude Code CLAUDE.md
Cursor .cursorrules
Windsurf .windsurfrules
GitHub Copilot .github/copilot-instructions.md
Aone Copilot Skills (SKILL.md)

不同的名字,相同的本质:告诉 Agent 所处的环境和应该遵循的规范。如果项目需要支持多个 AI 编码工具,可以维护一份核心规范文档,然后通过符号链接或脚本同步到各平台的配置文件中。

Specs:面向任务的需求文档

Specs 与上述所有机制都不同。它们不是 Agent 的"行为定义",而是 Agent 工作时需要参考的"需求文档"——API 设计规格、数据库 schema 说明、产品需求文档等。

flowchart LR
    subgraph Agent行为定义
        A[CLAUDE.md / Rules]
        B[Commands]
        C[Skills]
        D[Sub Agents]
    end
    
    subgraph 任务需求
        E[Specs / 需求文档]
    end
    
    Agent行为定义 -->|"定义 Agent 怎么工作"| F[Agent]
    任务需求 -->|"定义 Agent 要完成什么"| F

模式提炼:分层配置

模式公式Agent 配置 = 环境上下文(始终) + 触发入口(按需) + 专业知识(按需) + 执行隔离(按需)

迁移表

场景 环境层 触发层 知识层 执行层
个人项目 简单的 CLAUDE.md 不需要 不需要 不需要
团队项目 详细的 CLAUDE.md 常用 Commands 领域 Skills 审查 Sub Agent
大型企业项目 分层 CLAUDE.md 标准化 Commands 多领域 Skills 多角色 Sub Agents
开源项目 贡献者指南 PR 模板 Command 代码风格 Skill CI 审查 Sub Agent

核心洞察:当听到"Agent 不遵循项目规范"时,问题往往不在 Agent 的能力,而在于规范没有被正确地放入 Agent 的上下文中。分层配置体系的核心价值是:确保正确的信息在正确的时机出现在 Agent 的上下文中。


实操方法论:从原理到实践

理解了第一性原理和配置体系之后,接下来是最关键的部分:如何在日常工作中高效地使用 Agentic Coding。

flowchart TB
    subgraph 实操方法论
        A[对话管理] --> B[知识沉淀]
        B --> C[工程约束]
        C --> D[AI 友好设计]
        D --> E[刻意练习]
    end
    
    F["第一性原理"] -->|"指导"| 实操方法论
    G["配置体系"] -->|"支撑"| 实操方法论
    
    style A fill:#e3f2fd
    style B fill:#e8f5e9
    style C fill:#fff3e0
    style D fill:#fce4ec
    style E fill:#f3e5f5

对话管理:短对话优于长对话

这是从上下文窗口约束直接推导出的最重要的实操原则。

为什么长对话会失败

flowchart TB
    A["开始: 新对话<br/>上下文清晰"] --> B["第5轮: 上下文开始积累<br/>仍然高效"]
    B --> C["第15轮: 上下文大量积累<br/>注意力开始分散"]
    C --> D["第25轮: Lost in the Middle<br/>Agent 开始遗忘早期信息"]
    D --> E["第35轮: 上下文污染严重<br/>Agent 可能前后矛盾"]
    E --> F["第45轮: 接近上下文上限<br/>性能急剧下降"]
    
    style A fill:#c8e6c9
    style B fill:#dcedc8
    style C fill:#fff9c4
    style D fill:#ffe0b2
    style E fill:#ffccbc
    style F fill:#ffcdd2

Agent 的表现与对话长度之间存在一个"中间区域"的性能退化:

  • 简单任务(1-5 轮):表现优异,上下文清晰
  • 中等任务(5-15 轮):表现良好,但需要注意上下文管理
  • 复杂任务(15+ 轮):表现开始退化,需要主动干预
  • 超长对话(30+ 轮):高风险区域,建议拆分为多次对话

任务分解策略

面对复杂任务,核心策略是分解为多个短对话,每个对话完成一个独立的子任务:

flowchart TB
    A["复杂任务:<br/>重构订单模块"] --> B["拆分"]
    
    B --> C["对话1: 分析现有代码结构<br/>输出: 架构分析文档"]
    B --> D["对话2: 设计新的领域模型<br/>输入: 架构分析文档<br/>输出: 领域模型定义"]
    B --> E["对话3: 实现核心实体<br/>输入: 领域模型定义<br/>输出: 实体代码"]
    B --> F["对话4: 迁移服务层<br/>输入: 实体代码<br/>输出: 服务层代码"]
    B --> G["对话5: 编写测试<br/>输入: 服务层代码<br/>输出: 测试代码"]
    
    C --> D
    D --> E
    E --> F
    F --> G
    
    style C fill:#e3f2fd
    style D fill:#e3f2fd
    style E fill:#e3f2fd
    style F fill:#e3f2fd
    style G fill:#e3f2fd

每个对话之间的"接力"通过以下方式实现:

  1. 文件系统:前一个对话的输出(代码文件)自然成为下一个对话的输入
  2. CLAUDE.md:在 CLAUDE.md 中记录当前工作进度和决策
  3. Memory:利用 Sub Agent 的持久记忆跨对话传递经验

何时开始新对话的判断标准

flowchart TB
    A{Agent 是否开始<br/>重复之前的搜索?} -->|是| NEW[开始新对话]
    A -->|否| B{Agent 的回复是否<br/>与之前矛盾?}
    B -->|是| NEW
    B -->|否| C{当前任务是否<br/>已经完成一个里程碑?}
    C -->|是| NEW
    C -->|否| D{对话是否已经<br/>超过 15 轮?}
    D -->|是| NEW
    D -->|否| E[继续当前对话]
    
    style NEW fill:#e3f2fd
    style E fill:#c8e6c9

知识沉淀:从会话记忆到持久记忆

Agentic Coding 中最容易被忽视的一个维度是知识的沉淀与复用。每次与 Agent 的对话都会产生有价值的知识——项目的隐性约定、踩过的坑、有效的解决方案——但如果这些知识只存在于对话历史中,它们会随着对话的结束而消失。

知识沉淀的三个层次

flowchart TB
    subgraph 层次1["第一层: 会话内记忆"]
        A1["上下文窗口中的信息"]
        A2["生命周期: 单次对话"]
        A3["容量: 有限"]
    end
    
    subgraph 层次2["第二层: 项目级记忆"]
        B1["CLAUDE.md / Rules"]
        B2["生命周期: 项目存续期"]
        B3["容量: 中等(需精炼)"]
    end
    
    subgraph 层次3["第三层: 持久化记忆"]
        C1["Sub Agent Memory"]
        C2["生命周期: 跨项目/跨对话"]
        C3["容量: 较大(文件系统)"]
    end
    
    层次1 -->|"手动提炼"| 层次2
    层次2 -->|"自动积累"| 层次3
    
    style 层次1 fill:#ffecb3
    style 层次2 fill:#c8e6c9
    style 层次3 fill:#b3e5fc

经验复利效应

知识沉淀的真正价值在于复利效应。每一次修复、每一次审查、每一次教训都在为未来投资:

flowchart LR
    A["第1次: 手动告诉 Agent<br/>'这个项目用 Result 包装返回值'"] --> B["沉淀到 CLAUDE.md"]
    B --> C["第2次: Agent 自动遵循<br/>无需再次提醒"]
    C --> D["第3次: Agent 在审查中<br/>主动指出不符合规范的代码"]
    D --> E["第N次: Agent 基于积累的经验<br/>主动提出改进建议"]
    
    style A fill:#ffecb3
    style B fill:#dcedc8
    style C fill:#c8e6c9
    style D fill:#a5d6a7
    style E fill:#81c784

具体的沉淀实践:

1. 及时更新 CLAUDE.md

每当发现 Agent 犯了一个"本不该犯"的错误时,问自己:这个信息是否应该写入 CLAUDE.md

1
2
3
4
5
6
# CLAUDE.md 中的经验沉淀示例

# 已知陷阱
- 订单状态机不允许从 CANCELLED 回到 PAID,见 OrderStateMachine.java
- Redis 缓存的 key 前缀必须包含环境标识,如 prod:order:123
- 不要使用 @Transactional(readOnly=true) 在写操作方法上

2. 利用 Memory 机制

对于 Sub Agent,启用 memory 字段后,Agent 会自动将工作中发现的模式和教训记录到 MEMORY.md 中:

1
2
3
4
5
6
7
8
9
# MEMORY.md (由 code-reviewer Sub Agent 自动维护)

## 发现的模式
- 该项目倾向于使用 Builder 模式构造复杂对象
- 异常处理统一在 GlobalExceptionHandler 中,不要在 Service 层 catch

## 踩过的坑
- PR #234: 变量命名不一致导致审查遗漏
- PR #241: 缺少空指针检查导致生产事故

3. 将经验编码为 Skills

当某类问题反复出现时,将解决方案编码为 Skill:

flowchart TB
    A["问题反复出现:<br/>'如何正确处理分布式事务?'"] --> B["提炼为 Skill"]
    B --> C["distributed-transaction Skill"]
    C --> D["包含:<br/>- 事务模式选择决策树<br/>- Saga 模式实现模板<br/>- 补偿逻辑示例<br/>- 常见陷阱清单"]
    D --> E["下次遇到类似问题<br/>Agent 自动加载 Skill"]

工程约束:用代码而非文字约束 Agent

这是一个反直觉但极其重要的原则:工程约束比 Prompt 指令更可靠

Agent 有时会试图走捷径,绕过 Prompt 中设定的规则。与其在 Prompt 中反复强调"不要跳过测试",不如用工程手段来强制执行。

借助 Linters、Formatters 和 Git Hooks

flowchart TB
    A["Agent 尝试提交代码"] --> B["pre-commit hook 拦截"]
    B --> C{类型检查通过?}
    C -->|否| D["拒绝提交<br/>Agent 必须修复类型错误"]
    C -->|是| E{Lint 检查通过?}
    E -->|否| F["拒绝提交<br/>Agent 必须修复 Lint 错误"]
    E -->|是| G{测试通过?}
    G -->|否| H["拒绝提交<br/>Agent 必须修复测试"]
    G -->|是| I["允许提交"]
    
    style D fill:#ffcdd2
    style F fill:#ffcdd2
    style H fill:#ffcdd2
    style I fill:#c8e6c9

一个典型的 pre-commit hook:

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/bash
# .git/hooks/pre-commit

echo "Running type check..."
npm run typecheck || exit 1

echo "Running linter..."
npm run lint || exit 1

echo "Running tests..."
npm test || exit 1

echo "All checks passed!"

拦截 Agent 的"偷懒"行为

Agent 有时会发现测试一直失败后,尝试使用 --no-verify 绕过 pre-commit hook。解决方案是用一个 git 命令 wrapper 脚本拦截这种行为:

1
2
3
4
5
6
7
8
9
10
11
$ git commit --no-verify
------------------------------------------------------------------
ERROR: Commit Rejected.
------------------------------------------------------------------

GUIDANCE FOR THE AI AGENT:
You have attempted to bypass the required pre-commit verification.
All code must pass quality checks before it can be committed.

DO NOT BYPASS THE CHECKS. YOU MUST FIX THE UNDERLYING ERRORS.
------------------------------------------------------------------

这个技巧的本质是:把对 Agent 的指导嵌入到工具的输出中。Agent 会读取命令执行的结果,所以错误信息本身就是最好的 Prompt 注入点。

flowchart LR
    A["Prompt 指令:<br/>'不要跳过测试'"] -->|"Agent 可能忽略"| B["效果不稳定"]
    C["工程约束:<br/>pre-commit hook"] -->|"Agent 无法绕过"| D["效果稳定"]
    E["工具输出中的指导:<br/>错误信息包含修复建议"] -->|"Agent 必然读取"| F["效果最佳"]
    
    style B fill:#ffcdd2
    style D fill:#c8e6c9
    style F fill:#a5d6a7

模式提炼:工程约束优于提示

模式公式可靠性 = 工程强制(hook/lint/CI) > 工具输出引导(错误信息) > Prompt 指令(文字约束)

迁移表

约束需求 Prompt 方式 工程方式 推荐
代码风格 “请遵循 ESLint 规范” .eslintrc + pre-commit hook 工程
测试覆盖 “请确保测试通过” CI pipeline + 覆盖率门禁 工程
提交规范 “请写好 commit message” commitlint + husky 工程
安全检查 “请注意 SQL 注入” SAST 工具 + pre-commit 工程
架构约束 “请遵循分层架构” ArchUnit 测试 工程

核心洞察:当听到"Agent 总是不遵守规范"时,首先应该检查的不是 Prompt 是否写得够清楚,而是是否有工程手段来强制执行。每当 Agent 发明新的"偷懒"方式,就需要堵上这个漏洞——但总体来说,工程约束比 Prompt 指令更可靠。

AI 友好设计:对人难的事,对 AI 也难

有一个简单但常被忽视的事实:如果一个任务对人类开发者来说很难,那么它对当前的 AI 来说大概率也很难。 这个推论的深远意义在于:所有能提升人类开发者体验的工作,对 AI 同样有价值。

flowchart TB
    A["对人难的事<br/>对 AI 也难"] --> B["文档缺失"]
    A --> C["架构混乱"]
    A --> D["测试缓慢"]
    A --> E["错误信息模糊"]
    
    B --> B1["人: 花大量时间读源码猜意图<br/>AI: 反复搜索, 消耗上下文, 可能理解错误"]
    C --> C1["人: 不知道该改哪里<br/>AI: 改错文件, 遗漏同步修改"]
    D --> D1["人: 倾向于跳过测试<br/>AI: 等待超时, 浪费上下文"]
    E --> E1["人: 需要额外调试<br/>AI: 盲目搜索, 效率低下"]
    
    style A fill:#fff3e0

双重投资回报

当投资于更好的文档、更清晰的架构、更快的测试时,获得的回报是双倍的:

flowchart LR
    subgraph 投资
        A["更好的文档"]
        B["更清晰的架构"]
        C["更快的测试"]
        D["更好的错误信息"]
    end
    
    subgraph 回报
        E["人类开发者更高效"]
        F["AI 助手更高效"]
    end
    
    A --> E
    A --> F
    B --> E
    B --> F
    C --> E
    C --> F
    D --> E
    D --> F
    
    style E fill:#c8e6c9
    style F fill:#b3e5fc

更好的文档:好的文档不仅帮助新人上手,也帮助 AI 快速建立正确的心智模型。架构决策记录(ADR)解释"为什么这样设计",避免 AI 做出违背设计意图的修改。API 使用示例比纯粹的类型定义更有效。已知陷阱和常见错误直接告诉 AI 什么不该做。

更清晰的代码结构:清晰的命名(processUserData() 而非 doStuff())、单一职责(一个做一件事的函数)、显式依赖(依赖注入而非全局变量)——这些对 AI 的帮助和对人类一样大。

更快的反馈循环:这可能是最容易被低估的一点。Agent Loop 的每一轮都需要等待工具执行完成。秒级的单元测试让 Agent 可以频繁验证、快速迭代;快速的增量构建让改动能立即得到反馈。

专门为 AI 设计的优化

反过来的推论并不总是成立:对人来说简单的事,对 AI 未必简单。有时候需要专门为 AI 设计工具和接口。

1. LLM 需要专门的信息架构

LLM 是在现有的 CLI 工具上训练的,但这些工具是为人类设计的。需要为 Agent 增强这些工具,提供对 LLM 更有用的上下文:

1
2
3
4
5
6
# 在 .zshrc 中添加,帮助 Agent 快速定位
command_not_found_handler() {
echo "zsh: command not found: '$1'"
echo "zsh: current directory is $PWD"
return 127
}

很多命令行工具都提供了 --json--porcelain 选项,在给 Agent 使用的工具中优先使用这些格式——人类喜欢格式化的输出,但 AI 更擅长解析结构化数据。

2. API 设计:在信息量和上下文消耗之间取得平衡

为 Agent 设计工具接口时,需要在两个目标之间取得平衡:提供足够的信息(减少工具调用次数)和避免填满上下文(不要返回过多无关信息)。

一个好的实践是:提供便捷函数和底层函数两套 API,并通过工具描述引导 Agent 优先使用便捷函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def get_global_variable_at(address: str) -> dict:
"""
获取指定地址的全局变量值。自动识别类型并返回最佳字符串表示。
这是读取全局变量的首选方法。
"""
# 智能的、高层的实现
...

def data_read_byte(address: str) -> int:
"""
读取指定地址的 1 字节值。
仅在 get_global_variable_at 失败时使用此函数。
"""
# 底层的、更通用的实现
...

3. 显式优于隐式

1
2
3
4
5
6
7
8
9
10
11
# 有状态的 API: AI 需要跟踪隐含状态变化
client.connect()
client.authenticate(user, password)
client.query("SELECT * FROM users")

# 无状态的 API: AI 更容易正确使用
result = db.query(
connection={"host": host, "port": port},
auth={"user": user, "password": password},
sql="SELECT * FROM users"
)

4. 结构化的错误信息

1
2
3
4
5
6
# 对人足够,对 AI 可能困惑
Error: Something went wrong. Please try again later.

# 对 AI 更友好
Error [AUTH_TOKEN_EXPIRED]: Token expired at 2024-01-15T10:30:00Z.
Call refreshToken() to obtain a new token. See: docs/auth.md#token-refresh

顺应 Agent 的统计直觉

一个更微妙的发现:AI 认为合理的代码结构和命名,可能和人类的直觉不一致。

Agent 的命名"直觉"来自训练数据的统计概率。例如,present() 是 Flutter 等框架中双缓冲交换的常见命名,对于 Agent 来说是"最可能"的名字。当开发者用自己的命名 swapScreens() 覆盖它时,实际上是在对抗 Agent 的统计直觉——Agent 不能再从权重中找到答案,它必须记住人类的特殊习惯。

flowchart TB
    A["Agent 的统计直觉:<br/>present() 是双缓冲交换的常见命名"] --> B{开发者干预?}
    B -->|"改为 swapScreens()"| C["Agent 后续反复寻找 present()<br/>找不到后才找到 swapScreens()<br/>浪费 token 和时间"]
    B -->|"保留 present()"| D["Agent 后续直接找到<br/>高效继续工作"]
    
    style C fill:#ffcdd2
    style D fill:#c8e6c9

实用的权衡策略:

  1. 不要过度干预:如果频繁地因为"觉得这个名字更好"而覆盖 Agent 的决策,可能反而在降低效率
  2. 注意"找不到"的信号:如果 Agent 反复在某个地方"找不到"东西,考虑是否是命名和它的预期不一致
  3. 拥抱常见模式:使用广泛使用的设计模式和命名约定,AI 的训练数据中更可能包含这些
  4. 模块级的风格隔离:在 Agent 主导开发的模块中,可以考虑让 Agent 保持它自己的风格

模式提炼:统计直觉对齐

模式公式Agent 效率 ∝ 代码库命名与训练数据统计分布的对齐程度

迁移表

场景 人类偏好 Agent 统计偏好 推荐策略
函数命名 团队自定义术语 业界通用术语 优先使用通用术语
设计模式 自创模式 标准 GoF 模式 优先使用标准模式
文件布局 团队约定 框架默认布局 CLAUDE.md 中明确说明
错误处理 自定义异常体系 标准异常体系 在 Skills 中提供映射

核心洞察:当听到"Agent 总是用错误的方式做事"时,考虑是否是项目的约定与 Agent 的统计直觉不一致。解决方案不是反复纠正 Agent,而是要么对齐约定,要么在 CLAUDE.md 中显式说明。

刻意练习:像学乐器一样学习 AI

为什么有些人说"AI 对我不起作用",而另一些人却能用 AI 完成大量的工作?

这个问题需要区分来看。如果只在公司的大型私有代码库中使用过 AI,体验可能确实不好——那些代码库可能有古老的架构和专有模式,AI 的训练数据中根本没有这些。但关键问题是:有没有在个人项目中尝试过 AI?有没有进行刻意的、有意识的练习?

flowchart TB
    A["刻意练习的五个维度"] --> B["创造干净的实验环境"]
    A --> C["从失败中提取教训"]
    A --> D["观察和模仿高手"]
    A --> E["建立肌肉记忆"]
    A --> F["持续投入时间"]
    
    B --> B1["启动个人项目<br/>没有历史包袱"]
    C --> C1["Prompt 是否清晰?<br/>上下文是否充分?<br/>任务是否过大?"]
    D --> D1["关注公开分享工作流的开发者<br/>复制他们的技巧"]
    E --> E1["何时开始新对话?<br/>如何组织复杂 Prompt?<br/>哪种工具组合最有效?"]
    F --> F1["每天 30 分钟<br/>坚持几个月<br/>远超每周一次几小时"]

AI 工具就像一件乐器。那些从 AI 中获益最多的人,都投入了刻意练习。他们不会因为一次失败就下结论说"它不行",然后假设这将是常态体验。他们会实验、会失败、会从失败中学习。

从失败中提取教训的检查清单

  • Prompt 是否足够清晰?
  • 是否提供了足够的上下文?
  • 是否在一个对话里塞了太多任务?
  • 这个错误是否揭示了 AI 的某个系统性弱点?
  • 是否应该将这个教训沉淀到 CLAUDE.md 或 Skills 中?

每次失败都是一次学习机会。把它记录下来,下次避免同样的陷阱。


本质洞察:从"编写程序"到"编写职位描述"

回到最初的问题:为什么一份 Markdown 就能定义一个智能体?

因为 LLM 改变了"智能"的供给方式。在传统范式下,智能需要被编程——用代码一行一行地告诉计算机如何思考。在 LLM 范式下,智能是预训练好的——模型已经具备了通用的推理、规划和执行能力,只需要用自然语言告诉它"你是谁、该做什么、不该做什么"。

flowchart LR
    subgraph 传统范式
        A1["开发者<br/>(编程能力)"] --> A2["编写代码<br/>(编译部署)"] --> A3["Agent<br/>(运行)"]
    end
    
    subgraph LLM范式
        B1["任何人<br/>(表达能力)"] --> B2["编写文档<br/>(.md 文件)"] --> B3["Agent<br/>(即时生效)"]
    end
    
    style A1 fill:#e8eaf6
    style B1 fill:#e8f5e9
维度 传统范式 LLM 范式
创建者 程序员 任何能清晰表达的人
载体 代码 文档
生效方式 编译部署 保存即生效
迭代速度 天级别 秒级别
门槛 会编程 + 会部署 会写文档

这就是为什么定义一个 Agent 的过程,从"编写程序"变成了"编写职位描述"。而 Markdown,恰好是编写职位描述的最佳格式。

但这并不意味着"随便写写就行"。正如一份好的职位描述需要精确地定义岗位职责、能力要求、工作流程和权限边界,一份好的 Agent 定义也需要精心设计:

  • 关注点分离CLAUDE.md 管环境、Commands 管触发、Skills 管知识、Sub Agents 管执行
  • 最小权限原则:Sub Agent 的 tools 白名单和 Hook 机制确保每个 Agent 只能做它该做的事
  • 上下文隔离:Sub Agent 的独立上下文窗口防止信息泄漏和上下文污染
  • 渐进式复杂度:从简单的 CLAUDE.md 到复杂的带 Hook 的 Sub Agent,根据需要选择合适的抽象层级

模式速查表

听到的需求关键词 对应模式 方案 口诀
“AI 给了错误答案” 概率性推理 检查上下文是否充分,补充 CLAUDE.md 垃圾进垃圾出
“Agent 做了很多无用功” 感知-行动循环 用 Commands/Skills 减少摸索轮次 预定义工作流
“长对话中 Agent 越来越差” 上下文即工作记忆 拆分为多个短对话,使用 Sub Agent 短对话优于长对话
“Agent 不遵循项目规范” 分层配置 将规范写入 CLAUDE.md 或 Skills 信息在正确时机出现
“不知道规范该写在哪一层” 三层提示机制 L1 安全/L2 项目/L3 任务,各司其职 内核-配置-输入分离
“需要集成多个外部工具” MCP 协议 通过 MCP Server 标准化工具接入 USB-C 统一接口
“每次都要重复告诉 Agent” 知识外化 沉淀到 CLAUDE.md / Memory / Skills 写一次用无数次
“改了文档/架构后 Agent 更好用了” 双重投资回报 持续投资开发者体验 帮人也帮 AI
“Agent 总是跳过测试” 工程约束优于提示 用 Git Hooks / CI 强制执行 代码约束不可绕过
“Agent 总是用错误的命名” 统计直觉对齐 使用通用命名,或在 CLAUDE.md 中说明 顺应而非对抗
flowchart TB
    subgraph 模式组合示例
        A["大型项目重构"] --> B["分层配置<br/>+ 上下文隔离<br/>+ 知识外化"]
        C["日常功能开发"] --> D["感知-行动循环<br/>+ 分层配置<br/>+ 双重投资回报"]
        E["代码质量治理"] --> F["工程约束优于提示<br/>+ 统计直觉对齐<br/>+ 知识外化"]
    end

总结

AI 正在以惊人的速度发展。本文讨论的许多"限制"——上下文窗口的约束、会话间的失忆、中间区域的性能退化——很可能在未来几年内被大幅改善。但这并不意味着应该等待那一天的到来。

恰恰相反,正是这个充满限制的阶段,给了工程师极大的探索和成长空间。那些现在就开始深入理解 LLM 工作原理、积极实践最佳方法、在限制中寻找创造性解决方案的人,将在 AI 能力进一步释放时获得最大的杠杆效应。

从第一性原理理解 LLM 的本质,理解它们如何"思考"、如何受到上下文的限制、如何在 Agent Loop 中发挥作用——这些知识不会随着具体工具的迭代而过时。无论使用的是哪个 Coding Agent,无论模型如何更新换代,这些基础原理都将帮助更好地与 AI 协作。

而 Markdown 配置体系的分层设计——CLAUDE.md 管环境、Commands 管触发、Skills 管知识、Sub Agents 管执行——这种关注点分离的思想,同样具有跨平台、跨时代的迁移价值。更进一步,理解三层提示机制(System Prompt 定内核、项目配置定规范、用户输入定任务)如何各司其职,理解 MCP 协议如何像 USB-C 一样标准化工具接入,这些认知框架将帮助在任何 AI 平台上快速建立有效的工作模式。

这个范式转换带来的深远影响是:Agent 的创建门槛从"会编程"降低到了"会写文档"。 任何能清晰描述一个工作流程的人——产品经理、项目经理、领域专家——都有能力定义一个专业的 AI 智能体。而对于程序员来说,理解这套 Markdown 配置体系的分层设计,就像理解 DDD 的战略设计一样重要——它决定了能否有效地组织和管理一个由多个 AI Agent 协作的复杂工作流。

去实验,去失败,去学习。像学习乐器一样学习 AI。这个过程本身,就是价值所在。