4.1
Part IV / Specialize · The agent type that changed what agents are for

代码智能体(code agent):让智能体真正落地的形态。

代码智能体 — Claude Code、Cursor、Aider、Cline、Agent SDK — 是证明了这一媒介可行性的智能体(agent)类别。它们产出真实的工作成果(可交付的代码),能够自我验证(测试要么通过要么失败),并在状态丰富的环境(文件系统)中运行,而非聊天的无状态轮次世界。其架构与研究型或聊天型智能体差异显著,导致技术迁移时若不加适配效果往往很差。本章讲授代码智能体的独特之处、其实际操作空间(文件系统操作,而非"编写代码")、为何验证循环(verification loop)才是核心所在,以及 Anthropic 的 Agent SDK 与 Skills 系统如何相互配合。学完本章,你将建立起设计代码智能体的可用心智模型,并清晰了解何时应自行构建、何时应直接采用现有方案。

STEP 1

代码智能体的独特之处。

聊天智能体回答问题。研究型智能体查找信息并加以综合。代码智能体则修改系统。这一角色转变以四种具体方式改变了架构,这些方式相互叠加——也使其设计问题有别于我们迄今所讨论的一切。

特性 1:持久化的文件系统状态

在聊天智能体中,状态存在于对话历史里。智能体的"世界"在每轮之间重置;它所产出的一切仅以文本形式存在于消息流中。而在代码智能体中,状态存在于磁盘上的文件里。智能体在第 1 轮编辑一个文件,该文件在第 2 轮仍处于已编辑状态——不是因为智能体记得,而是因为文件系统记得。智能体读取自己(或前一次会话)写入的内容,在其基础上继续工作,用户可以通过 lsgit diff 或直接在编辑器中打开文件来查看结果。

这就是产出对话式输出(读一次、立即使用、随后丢弃)的智能体与产出制品(artifact)(保存、持有、日后修改,可能由人类操作)的智能体之间的区别。对设计的启示:智能体的工作不是生成用户阅读的文字——而是将项目的文件系统收敛到期望状态。代码智能体任务的"输出"不是它在聊天中说了什么;而是现在存在哪些之前不存在的文件、哪些文件发生了变化,以及它们处于什么状态。

特性 2:可编程的验证

代码区别于散文的关键:你可以执行它。一个写了一段关于 Postgres 的文章的研究型智能体,在没有 LLM 作为评判者(LLM-as-judge)的情况下无法检验那段文字是否正确。而一个写了一个函数的代码智能体可以针对该函数运行测试。如果测试通过,函数就能正确运行;如果失败,则不能。评判者是确定性的,反馈是即时的,标准是不可协商的。

这是代码智能体相对所有其他智能体类型所具有的最大架构优势。第 3.3 章的整套机制——LLM 作为评判者、校准集、位置偏差——之所以存在,是因为大多数智能体的输出无法被确定性地验证。代码可以。测试套件(eval suite)是研究型智能体梦寐以求的那种评判者:它极少与人类意见相左,运行只需数秒,调用成本低廉,且从不存在自我偏好偏差。

设计启示:你的代码智能体应围绕验证循环而非生成步骤来构建。关键问题不是"Claude 写了代码吗?"——而是"代码通过测试了吗?"一个写了 500 行重构代码却从不运行测试的代码智能体,不过是一个碰巧输出 Python 的研究型智能体。而一个写了 20 行代码、运行测试、发现一个失败、修复它、再次运行测试直到全部通过的代码智能体,则是完全不同的形态。

特性 3:有界的行动空间

研究型智能体的行动空间在理论上是整个互联网。每次工具调用(tool calling)都可能产生任意新内容。推断它们接下来会做什么,或上周做了什么,都很困难。

代码智能体在已知文件系统的已知代码仓库内运行。行动空间是可枚举的:每个动作都是读取文件、写入文件、编辑文件、运行命令、搜索目录树的某种组合。文件集合是有限的。智能体能运行的命令集合就是你授予它的那些。这种有界性带来两个结果:

其一,你可以预测并审计智能体的所做所为git diff 会逐文件展示每一个字节的变化。没有"智能体决定给某人发邮件"这种游离于代码仓库之外的分支。智能体的影响是可检视的,细粒度的,使用用户已经熟悉的工具即可。

其二,你可以通过控制允许的工具和命令来精确约束智能体的能力。只读模式(智能体只能浏览,不能修改);仅写入子目录模式(智能体只能修改 src/,不能触及仓库其余部分);无网络模式(智能体无法 curl 任何地址)。这些约束在通用型智能体上很难强制执行;而对代码智能体来说,只需一行配置即可实现。

特性 4:紧密的反馈循环

代码智能体的每个动作都能被快速检验。语法错误在保存时立即触发(文件甚至无法解析)。类型错误在类型检查时触发(秒级)。测试失败在测试运行时触发(秒级到分钟级)。运行时错误在执行时触发(秒级)。每一层检查都比下一层更便宜、更快速,它们共同构成一个智能体可以有意识地逐级攀登的反馈阶梯。

与研究型智能体相比:它产生一个答案,唯一的反馈要么是"用户接受了它"(智能体在同一会话中永远不会看到),要么是"评估(eval)评判者给它打了分"(在运行结束后,在一个单独的进程中)。智能体本身在单次任务中无法学习;它无法自我纠错,因为它没有信号表明自己出了问题。

代码智能体持续拥有信号。它可以编写函数、运行测试、看到失败信息、修复函数、重新运行测试、看到另一个失败、再次修复——全部在一次任务中完成。善用这一反馈的智能体远比那种只管生成、听天由命的智能体高效得多。

四个特性的共同作用

这四个特性——持久化状态、可编程验证、有界行动空间、紧密反馈循环——结合形成了其他智能体类型所不具备的特定形态。代码智能体是收敛型系统:不断迭代直到确定性检查通过。研究型智能体是发散型系统:不断探索直到时限或预算耗尽。

┌─────────────────────────────────────────────────────────────┐ │ RESEARCH AGENT vs. CODE AGENT │ │ │ │ State: in conversation State: on disk │ │ Verification: LLM judge Verification: tests/build │ │ Action space: open-ended Action space: bounded │ │ Feedback: end of task Feedback: continuous │ │ │ │ Optimizes: helpful synthesis Optimizes: green build │ │ Failure mode: hallucination Failure mode: red test │ │ Recovery: re-search Recovery: re-edit + rerun │ └─────────────────────────────────────────────────────────────┘

这就是为什么对研究型智能体有效的模式(LLM 作为评判者的评估、以检索增强(retrieval-augmented)为核心的设计)在应用于代码时需要适配,而对代码智能体有效的模式(测试驱动验证、文件级状态)也无法直接移植到研究场景。本章从此处开始专注于代码特有的形态;Part IV 后续章节将涵盖计算机操作(computer use)、研究型和多智能体(multi-agent)变体。

Question
"代码智能体"与 Copilot 这样的"AI 配对程序员"有什么不同?

有本质区别。Copilot(以及 2021–2023 年那一代"AI 编程助手")是类固醇版的自动补全:模型在你打字时内联建议补全内容。用户编写大部分代码;AI 填充繁琐的部分。这是一个有用的工具,但它不是智能体。用户仍然是驾驶者——决定构建什么、在代码库中导航、运行测试。

代码智能体颠覆了这种关系。用户陈述目标("给这个应用添加 OAuth 登录"),由智能体来驾驶——它读取现有代码以理解结构,编辑多个文件,运行测试,修复失败,迭代。用户审阅结果而非亲自编写。底层是相同的模型,但围绕它们构建的系统截然不同。

本步骤中的四个特性正是使其成为智能体而非补全工具的原因。持久化文件系统状态、自我验证、有界行动空间、反馈循环——Copilot 一项都不具备;Claude Code、Cursor 的 agent 模式、Aider 和 Agent SDK 则全部具备。

Question
"通过测试进行自我验证"理论上听起来很好。但当没有测试,或测试质量很差时,这不就失效了吗?

确实如此。代码智能体相对研究型智能体的架构优势是真实存在的——前提是验证阶梯正常运转:测试存在、运行迅速、覆盖你正在进行的变更,并能准确反映正确性。当任何一条失效时,代码智能体就会失去大部分优势。一个没有测试的代码库,就是一个让智能体像研究型智能体一样只能生成代码、听天由命的代码库。

实际的推论:代码智能体在测试完善的代码库上最为有效;在将代码智能体放到项目上运作之前,最有价值的事情之一就是夯实测试套件。反之,贡献一个功能的智能体也需要为该功能贡献测试——否则下一个在上面工作的智能体(或人类)就没有任何信号。

本章第 3 步讨论了测试薄弱时的应对方案:更轻量的检查(类型检查器、代码风格检查器、构建本身)构成验证阶梯的较低阶级,在完整测试缺失时仍能提供有用信号。

Question
"有界的行动空间"听起来很受限。那些需要调用外部 API、查询数据库等操作的智能体怎么办?

有界性是指你授予的工具,而非理论上的可能性。代码智能体完全可以拥有 curl 工具、psql 工具、HTTP fetch 工具——但这些都是配置中的显式授权,而非默认值。重点不在于代码智能体不能触达文件系统之外;而在于这种触达是可枚举的,并且可以按任务配置。

对比自由形态的研究型智能体:它默认拥有网络搜索、网页获取,以及可能的其他跨越未知领域的"通用"工具。将研究型智能体约束为"只访问这些域名"比将代码智能体配置为"只编辑这些文件"要困难得多,因为约束面更开放。

STEP 2

行动空间:编辑文件、运行命令、读取自己写入的内容。

构建代码智能体时最常见的错误:把工具面视为"生成代码"。实际上并非如此。正确的工具面是开发者在项目上执行的操作集合——读取文件、编辑文件、跨文件搜索文本、浏览目录树、运行命令。模型将代码作为编辑操作的内容来产出,但操作本身是文件系统动作。这一区别将真正在真实项目上运作的智能体与只是美化版单提示词(prompt)代码生成器区分开来。

覆盖 95% 代码智能体工作的六个工具

几乎所有现代代码智能体——Claude Code、Aider、Cline、Agent SDK 参考实现——都收敛于大致相同的工具集。具体名称有所不同,但形态几乎一致。

工具
用途
说明
read_file
将文件内容加载到上下文窗口(context window)中
支持可选行范围,对超大文件的部分读取成本较低。
write_file
创建新文件或覆盖现有文件
仅用于新文件。用此工具"编辑"现有文件是反模式(见下文步骤)。
edit_file (str_replace)
将一段子字符串替换为另一段
主力工具。原子性强、可审查,最大限度减少上下文窗口消耗。
glob
按文件名模式查找文件
"查找 src/ 下所有 *.test.ts 文件"。成本低廉,信号价值极高。
grep
搜索文件内容
"在整个代码库中查找 OAuthProvider 的引用。"支持正则表达式。
bash / run_command
运行 shell 命令
测试、构建、类型检查、包安装、git 操作。验证阶梯的核心。

生产环境的智能体中你还会看到其他工具——任务追踪、子智能体调度、结构化搜索——但这六个是基础。其他一切都是在此之上的优化或专门化。

为什么 str_replace 优于"重写整个文件"

有一个设计决策值得单独讨论,因为朴素实现常在此出错,而正确答案并不显而易见:智能体如何编辑现有文件

朴素做法:智能体读取文件,生成新版本,写回。随之而来的是三种失败模式。

失败 1:上下文成本(context cost)。读取一个 500 行的文件消耗约 3K 令牌(token)。写回又消耗约 3K 输出令牌。在单次智能体运行中对 5 个文件做 10 次编辑,会烧掉 3 万多令牌的工作量,而这些工作本可以用几百个令牌的 str_replace 操作完成。规模化之后这是真实的成本(cost)。

失败 2:隐性回归(regression)。智能体读取文件,完成预期的编辑,重新输出文件。但它同时也重写了导入顺序,删掉了它认为多余的注释,或"为了一致性"修改了一个不相关的函数。用户打开 git diff,看到了没有要求的改动。即便这些改动在技术上是改进,信任损耗也极为严重。

失败 3:审查负担。"旧文件(500 行)→ 新文件(497 行)"的 diff 无法被细致审查。用户无法在不自己做 diff 的情况下判断改了什么。相比之下,一个 5 行 old_str、7 行 new_str 的 str_replace——那是一个原子性的、范围明确的、可审查的变更。代码审查工具能原生展示它。用户清楚知道该看什么。

解决方案是 str_replace 操作:智能体传入要查找的精确子字符串和要替换成的精确子字符串。工具实现找到该子字符串(若不唯一则报错),替换它,并写回文件。由此产生三个特性:

  • 原子性。要么子字符串匹配并应用了替换,要么操作以报错失败。没有部分更新。
  • 最小上下文。智能体只需读取足够的文件内容以在变更处定位一个唯一的子字符串。通常 20 行的上下文窗口就足够了;完整文件读取变得罕见。
  • 可审查性。操作的 old_str 和 new_str 就是 diff。代码智能体运行的审计日志是可读的;改了什么一目了然。

这就是为什么每个现代代码智能体都将 str_replace(或等价物,通常叫 edit_fileapply_diff)作为其主要编辑原语,并将 write_file 保留给新文件专用。

最小骨架:150 行代码的代码智能体

将全部六个工具与第 1.1 章的代理循环(agent loop)组合在一起,一个可用的代码智能体在最精简时是什么样子。这不是 Claude Code,也不是 Agent SDK——而是它们底层的形态。自己动手构建这个,是理解设计的正确练习。

# agent/code_agent.py
import os, subprocess, glob as globmod, re
from anthropic import AsyncAnthropic

REPO_ROOT = os.environ.get("AGENT_REPO_ROOT", os.getcwd())
client = AsyncAnthropic()

# --- Tool definitions (full descriptions omitted for brevity; see chapter 0.3) ---
TOOLS = [
    {"name": "read_file",    "description": ..., "input_schema": {...}},
    {"name": "write_file",   "description": ..., "input_schema": {...}},
    {"name": "str_replace",  "description": ..., "input_schema": {...}},
    {"name": "glob",          "description": ..., "input_schema": {...}},
    {"name": "grep",          "description": ..., "input_schema": {...}},
    {"name": "bash",          "description": ..., "input_schema": {...}},
]

# --- Handlers ---
def safe_path(path: str) -> str:
    """Resolve path within the repo root; refuse to escape it."""
    full = os.path.realpath(os.path.join(REPO_ROOT, path))
    if not full.startswith(os.path.realpath(REPO_ROOT) + os.sep):
        raise ValueError(f"Path {path!r} escapes repo root")
    return full

async def read_file(path: str, start_line: int = 1, end_line: int = -1) -> str:
    with open(safe_path(path)) as f:
        lines = f.readlines()
    end = len(lines) if end_line == -1 else end_line
    return "".join(f"{i:5d}  {ln}"
                    for i, ln in enumerate(lines[start_line-1:end], start_line))

async def write_file(path: str, content: str) -> str:
    p = safe_path(path)
    if os.path.exists(p):
        raise ValueError(f"{path} exists; use str_replace to edit")
    os.makedirs(os.path.dirname(p), exist_ok=True)
    with open(p, "w") as f: f.write(content)
    return f"Created {path} ({len(content)} bytes)"

async def str_replace(path: str, old_str: str, new_str: str) -> str:
    p = safe_path(path)
    with open(p) as f: text = f.read()
    count = text.count(old_str)
    if count == 0:
        raise ValueError(f"old_str not found in {path}")
    if count > 1:
        raise ValueError(f"old_str matches {count} times; add context to make it unique")
    with open(p, "w") as f: f.write(text.replace(old_str, new_str))
    return f"Replaced 1 occurrence in {path}"

async def glob_files(pattern: str) -> str:
    matches = globmod.glob(os.path.join(REPO_ROOT, pattern), recursive=True)
    return "\n".join(os.path.relpath(m, REPO_ROOT) for m in matches[:200])

async def grep_files(pattern: str, glob_filter: str = "**/*") -> str:
    rx = re.compile(pattern)
    hits = []
    for path in globmod.glob(os.path.join(REPO_ROOT, glob_filter), recursive=True):
        if not os.path.isfile(path): continue
        try:
            for i, line in enumerate(open(path), 1):
                if rx.search(line):
                    rel = os.path.relpath(path, REPO_ROOT)
                    hits.append(f"{rel}:{i}: {line.rstrip()}")
                    if len(hits) >= 100: break
        except UnicodeDecodeError: continue
    return "\n".join(hits) or "(no matches)"

async def run_bash(command: str, timeout: int = 120) -> str:
    p = subprocess.run(command, shell=True, cwd=REPO_ROOT,
                       capture_output=True, text=True, timeout=timeout)
    return f"exit={p.returncode}\nstdout:\n{p.stdout}\nstderr:\n{p.stderr}"

HANDLERS = {"read_file": read_file, "write_file": write_file,
            "str_replace": str_replace, "glob": glob_files,
            "grep": grep_files, "bash": run_bash}

# --- The loop (the same shape from chapter 1.1) ---
async def run_code_agent(task: str, max_steps: int = 40):
    messages = [{"role": "user", "content": task}]
    for _ in range(max_steps):
        response = await client.messages.create(
            model="claude-sonnet-4-5",
            max_tokens=4096,
            system=SYSTEM_PROMPT,   # <-- conventions, test commands, project layout
            tools=TOOLS,
            messages=messages,
        )
        messages.append({"role": "assistant", "content": response.content})

        if response.stop_reason != "tool_use":
            return response

        results = []
        for block in response.content:
            if block.type != "tool_use": continue
            try:
                output = await HANDLERS[block.name](**block.input)
                results.append({"type": "tool_result",
                                "tool_use_id": block.id, "content": output})
            except Exception as e:
                results.append({"type": "tool_result",
                                "tool_use_id": block.id,
                                "content": f"Error: {e}",
                                "is_error": True})
        messages.append({"role": "user", "content": results})

    raise RuntimeError("step budget exceeded")

这就是一个可用的代码智能体。大约 100 行加上工具描述;其结构与"Build"部分中的代理循环完全相同,只是换成了代码特有的工具。加入流式传输层(第 2.4 章)、对系统提示词的提示词缓存(第 2.2 章)和可观测性(observability)(第 2.1 章),即可达到生产形态。这些添加不会改变核心架构;只是让它运转得更好。

系统提示词承担了大部分工作

有一点值得注意:在代码智能体中,系统提示词(system prompt)承担着往往不被注意的繁重工作。它告诉智能体:

  • 项目使用的约定(TypeScript strict 模式、倾向函数式风格、conventional commits 等)
  • 如何运行测试(npm testpytest 还是 cargo test
  • 如何运行类型检查器、代码风格检查器、构建
  • 哪些目录禁止访问
  • 遵循什么工作流程(小提交、每次变更后运行测试等)

Claude Code 从仓库根目录的 CLAUDE.md 文件读取这些信息。Aider 从 .aider.conf.yml 和约定文件中读取。Agent SDK 允许你通过 system= 参数传入。机制各异,目的相同:告诉智能体这个特定项目希望如何被处理。没有项目约定的代码智能体,就像一个被空降到你代码库的通用 Python 开发者;有了约定,它就是一个了解团队规范的贡献者。

关键纪律:将你的 CLAUDE.md(或等价物)视为不断积累价值的文档。每当你需要纠正智能体在某个反复出现问题上的行为时("我们用 2 格缩进,不是 4 格"),就往约定文件里加一行。几周之后,智能体首次尝试的命中率会从勉强可用提升到出色。

Question
为什么要把 grep 和 glob 作为单独的工具?用 bash 加上正确的命令不就能同时做到吗?

可以做到。但将它们作为一等工具暴露出来,而不是让智能体通过 bash 运行 find/grep,有三个原因。

第一,结构化输出:glob 和 grep 处理器可以以一致的格式(路径:行号: 文本)返回结果,模型易于解析和处理。模型不必判断 bash 输出是否有标题行、尾随换行符等。

第二,可控性:处理器可以强制执行结果数量限制、屏蔽敏感路径,并自动过滤二进制文件。原始的 bash grep -r 在模式过宽时可能产生数兆字节的输出;封装后的处理器将其限制在 100 条结果或 50KB 以内。

第三,更安全:授予 bash 访问权限是一个安全决策(智能体现在可以运行任意命令);单独授予 glob 和 grep 不会扩大攻击面。许多生产环境的部署正是出于这个原因,在不开启 bash 的情况下启用了读取工具。

Question
str_replace 在 old_str 不唯一时会失败。如果智能体需要在多处做相同的编辑,该怎么办?

有三种方案,按生产级智能体的处理顺序排列。

最常见:让智能体使 old_str 更具体以达到唯一性。如果你想重命名一个出现在 5 处的变量,你不会只替换变量名本身——而是替换每处实例周围的 2-3 行,逐一处理。智能体很快就能从失败信息中习得这种模式。

有时有用:一个单独的 str_replace_all 工具,带一个标志表示"我知道这会匹配多次,全部替换"。有风险,因为可能误匹配;通常不值得引入这个隐患。

专门针对重命名:通过 bash 使用真正的重构工具(基于语言服务器的重命名、rg --replace)。智能体应该学会,重命名是一种结构化操作,而非文本替换。

Question
那跨越大段代码块的编辑呢——比如替换一个 200 行的函数?

str_replace 完全可以处理这种情况——old_str 和 new_str 可以是数百行。限制在于 old_str 必须在文件中只出现一次(200 行的代码块几乎肯定满足这个条件)。

但令牌成本的问题是真实存在的:替换 200 行意味着智能体在单次工具调用的输出中有 200 行 old_str 和 200 行 new_str。对于非常大的重构,有时更划算的方式是在旧文件旁边写一个新文件,然后更新导入。或者写一个小脚本来完成转换,再通过 bash 运行。只要系统提示词给予智能体足够的自由度,它会选择合适的方式。

STEP 3

验证循环才是核心所在。

如果你从本章只带走一个教训,那就是这个:一个不验证自身工作的代码智能体,就不是代码智能体。它是一个产出看似合理的代码的研究型智能体。"智能体生成了这个函数"与"智能体生成了这个函数、运行了测试、看到失败、修复了它、最终达到全部通过"之间的差别,正是代码智能体在编码上优于聊天智能体的全部原因所在。

本步骤讲解如何构建验证循环,以及如何确保智能体真正使用它。

验证阶梯

并非所有验证的成本都相等。类型检查很快;测试运行可能需要一分钟;完整的集成测试可能需要五分钟。智能体应使用能对其刚刚完成的操作提供信号的最廉价检查,只在必要时才攀登更高的阶梯。

┌─────────────────────────────────────────────────────────────┐ │ THE VERIFICATION LADDER (cheap → expensive) │ │ │ │ Rung 0: File written successfully (str_replace returned) │ │ Rung 1: Syntax / parser check (cheapest signal) │ │ ─ Python: `python -c "import ast; ast.parse(...)"`│ │ ─ TS/JS: `tsc --noEmit` │ │ ─ Rust: `cargo check` │ │ Rung 2: Linter / formatter │ │ ─ ruff, eslint, golangci-lint │ │ Rung 3: Type check (when separate from compile) │ │ ─ mypy, pyright, flow │ │ Rung 4: Unit tests (fast subset first) │ │ ─ pytest tests/unit/ │ │ ─ vitest run --reporter=basic │ │ Rung 5: Full test suite │ │ Rung 6: Integration / e2e tests (slowest, most signal) │ │ │ │ Each rung is cheaper than the next AND catches different │ │ bugs. Skipping rungs means catching bugs later, when │ │ the feedback loop is slower. │ └─────────────────────────────────────────────────────────────┘

大多数开发者的本能——也是智能体的正确本能——是"运行最廉价的、理论上能捕获我刚刚破坏的东西的检查"。编辑函数签名后:运行类型检查。编辑测试设置后:运行一个测试。多文件重构后:运行完整的套件。善用这一点的智能体比那些每次变更后都运行完整套件的智能体快 3–5 倍。

系统提示词应该传授这一点。具体来说:

# From CLAUDE.md, system prompt for the agent

# Verification workflow

After making any change, run the appropriate verification:

- **Single-function change in a typed language**: `tsc --noEmit` (or
  `mypy src/`).  Type errors here block everything else.
- **Behavioral change to a single module**: run that module's tests.
  `pytest tests/path/to/module/ -x`
- **Cross-cutting change** (touches more than 3 files): run the full
  unit suite. `npm run test:unit`
- **Before declaring "done"**: full test suite passes, including
  integration. `npm test`

Always run the verification before reporting the task complete.
If verification fails, fix the failure and verify again.  Do not
report success on the basis of "the code looks right".

最后那句话——"不要以'代码看起来没问题'为由报告成功"——发挥着真实的作用。它明确堵住了智能体生成代码、什么都不运行就报告"完成"的失败模式。一个听起来合理但未经验证的完成,是无人监督代码智能体最常见的失败模式。

跳过验证时会出什么问题

需要识别的三种失败模式,都是"智能体说完成了但实际没有"的变体:

自信但有问题的编辑。智能体读取了相关文件,仔细地进行了编辑,然后报告完成。用户拉取变更,运行测试,看到四个失败。智能体的编辑方式从运行测试的角度来看显然是错误的——但智能体没有运行测试。这正是验证阶梯纪律旨在防范的失败。

编辑了错误的地方。智能体搜索了一个函数名,在两个文件中找到了它(其中一个是测试文件),然后编辑了错误的那份。运行测试本可以证明实际的函数并未被修改。跳过测试运行则掩盖了这个错误,直到用户自己发现。

忘记了副作用。智能体修改了一个函数,测试通过了——但类型检查器会标记出一个需要更新的下游调用方。智能体运行了测试(很好!)但没有运行类型检查器(不好!)。整个阶梯都很重要。

规律在于:阶梯的每一阶捕获不同类型的缺陷。跳过某些阶级,缺陷就会漏过去。只运行最廉价检查的智能体交付快但交付有问题;每次微小变更后都运行所有阶级的智能体极其缓慢但可靠。正确的行为是根据每次变更适当地攀登阶梯——这是系统提示词可以编码、工具描述可以强化的纪律。

快速反馈循环实战

验证循环在真实智能体追踪中的样子——这是你希望在智能体行为中看到的模式:

══ TASK: Fix the bug where the date parser crashes on empty strings ══

[turn 1: agent]
→ grep "def parse_date" --glob "**/*.py"

[result]
src/datetime/parser.py:12: def parse_date(s: str) -> date:
tests/datetime/test_parser.py:34: def test_parse_date_iso():

[turn 2: agent]
→ read_file src/datetime/parser.py

[result]
[20 lines of code, parse_date function visible]

[turn 3: agent]
→ read_file tests/datetime/test_parser.py 30 60

[result]
[shows existing tests; notes no empty-string test]

[turn 4: agent]
→ bash "pytest tests/datetime/test_parser.py -x"

[result]
3 passed in 0.4s
  // agent confirms baseline is green before changing anything

[turn 5: agent]
→ str_replace tests/datetime/test_parser.py
   old_str: def test_parse_date_iso():\n    assert parse_date("2025-01-01") == ...
   new_str: def test_parse_date_iso():\n    assert parse_date("2025-01-01") == ...\n\n
            def test_parse_date_empty_string_raises():\n    with pytest.raises(ValueError):\n
                parse_date("")

[result]
Replaced 1 occurrence in tests/datetime/test_parser.py

[turn 6: agent]
→ bash "pytest tests/datetime/test_parser.py::test_parse_date_empty_string_raises -x"

[result]
FAILED tests/datetime/test_parser.py::test_parse_date_empty_string_raises -
IndexError: string index out of range
  // agent: good — test fails for the right reason. now fix it.

[turn 7: agent]
→ str_replace src/datetime/parser.py
   old_str: def parse_date(s: str) -> date:\n    year = int(s[0:4])
   new_str: def parse_date(s: str) -> date:\n    if not s:\n        raise ValueError(
            "empty date string")\n    year = int(s[0:4])

[result]
Replaced 1 occurrence in src/datetime/parser.py

[turn 8: agent]
→ bash "pytest tests/datetime/test_parser.py -x"

[result]
4 passed in 0.4s
  // agent: green. one more check.

[turn 9: agent]
→ bash "mypy src/datetime/"

[result]
Success: no issues found in 1 source file
  // agent: clean. done.

[turn 10: agent]
"Done. Added empty-string validation to parse_date with a test that
verifies the ValueError is raised. Tests pass, mypy clean."

注意智能体没有做什么:它没有读取整个代码库,没有编写 500 行的重构,没有在运行任何检查之前就跨文件进行多处编辑。它做了一次有针对性的变更,验证了它,观察到失败(这正是新测试预期的失败),再做了一次有针对性的变更,验证了它,然后停止。

这是高效代码智能体工作的质感——小步迭代、快速反馈、发现失败-修复的循环。系统提示词和工具设计都在推动这种节奏。

测试优先作为智能体的工作流程选择

还要注意,智能体是在编写修复代码之前就写了会失败的测试。这并非偶然——这是将测试驱动纪律应用于智能体。这样做的优势,对智能体来说都被放大了:

  • 测试即规格说明。先写测试迫使智能体精确地明确行为。如果智能体写不出测试,它就没有充分理解需求,也就写不好代码。
  • 完成标准是机械性的。当新测试通过且旧测试仍然通过时,任务完成。关于智能体是否"真的"完成了,没有任何歧义。
  • 回归防护是自动的。刚刚通过的测试,就是缺陷再次出现时能捕获它的测试。无需单独的"添加回归测试"步骤。

你不必普遍强制推行 TDD——对于简单变更来说那是额外负担。但专门针对 bug 修复,"复现为失败测试,修复直至通过"的模式是最干净的纪律,也是最容易在系统提示词中编码的做法。

陷阱:伪装验证

最隐蔽的失败模式:智能体运行了某些东西,它返回了成功,但实际上并未验证你想要的内容。

你会看到的例子:

  • 智能体运行 pytest -k test_nothing_relevant 并报告"测试通过"。
  • 智能体运行 npm test,由于 matcher 配置错误,失败的测试被静默跳过。
  • 智能体在测试命令不可用时将 true 作为占位符运行,得到退出码 0,宣告成功。
  • 智能体修改测试来让它通过,而不是修复代码。测试"通过"了,但并不测试它应该测试的内容。

针对这些问题的防御措施:

检查输出,不要只信任退出码。智能体的提示词应要求报告运行了什么以及输出是什么,而不仅仅是"测试通过"。审阅者(人类或 LLM)应该能够验证运行了正确的内容。

在 CLAUDE.md 中明确写出验证命令。不要让智能体猜测"这个项目的测试命令是什么?"——告诉它。"npm run test:strict 是验证命令。其他测试命令会跳过慢测试,不应用于验证。"

添加元检查。对于高风险变更,智能体的最后一步应该是对测试输出做 diff:"在我的变更之前,X 通过了。在我的变更之后,Y 通过了。Y 必须包含 X 加上新测试,而不是一个不同的集合。"这迫使智能体对比基线与最终结果,而不仅仅是孤立地看最终输出。

人工审查 diff,始终如此。对于交付到生产环境的智能体工作,在合并之前由人类查看 git diff。不是因为智能体不可信——而是因为信任穿透的代价(一个微妙的测试伪造通过将 bug 带入生产)很高。这不是代码智能体特有的观点,但价值在此叠加:人类审查 30 行 diff 比从头编写这 30 行高效得多。

如果你的代码智能体在追踪记录中没有显示验证步骤就报告"完成",将其视为一个危险信号。要么它没有运行验证(因此你不知道变更是否有效),要么它运行了某些实际上无法验证你所关心内容的东西(因此你不知道它运行了什么)。无论哪种情况,正确的做法是明确要求验证步骤,并将跳过它的智能体视为有问题的——而非"高效的"。

Question
如果运行测试需要 20 分钟以上,该怎么办?验证循环听起来很好,但它不是没有代价的。

根据慢测试是本质性的还是偶然性的,有两种回应。

本质性的慢测试(集成测试、端到端测试、浏览器测试)通常有一个能在数秒内运行的快速单元子集。智能体在内循环中使用这个快速子集(每次变更都对单元测试进行验证),只在"任务完成"关卡才运行慢测试。慢测试仍然捕获它们能捕获的问题;只是不会减慢内循环的节奏。

偶然性的慢测试(因为糟糕的 fixtures、不必要的数据库重建、缺少并行化而变慢)是一个独立的问题。代码智能体不是解决它们的正确工具,但它可能是迫使解决它们的正确压力——"智能体的迭代速度受此制约;我们需要让它更快"是一个合理的工程优先级。

针对慢测试套件有一个具体的技巧:在框架支持的地方并行执行测试。大多数现代测试运行器(pytest-xdist、vitest 等)可以跨核心分发,在不改变行为的情况下将 20 分钟的套件缩短到 3 分钟。

Question
如果代码库完全没有测试,代码智能体就变得没用了吗?

肯定会降低有用性,但并非毫无用处。你失去了最可靠的验证阶级,但其他阶级仍然存在:语法/解析检查、类型检查、代码风格检查器、构建。这些能捕获相当一部分缺陷——任何无法编译或无法通过类型检查的东西,无论是否存在测试都会被拦截在代码库之外。

智能体也可以被安排先创建测试,作为做真正工作之前的一个单独步骤。"添加一个演示当前行为的测试"是在"现在改变这个行为"之前的一个合理的第一步。经过几轮之后,你就有了代码库所缺少的测试脚手架。

有一个值得了解的模式:智能体通常非常擅长编写特征测试(characterization test,捕获代码实际做了什么的测试,不管那是否正确)。这些测试编写成本低廉,并为重构创建了一个安全网。代码智能体可以通过观察批量产出数百个这样的测试,然后由人工审查挑选值得保留的部分。

Question
智能体应该在并行进行更多编辑的同时运行测试,还是严格按顺序进行?

几乎总是严格按顺序。原因:此处的并行化是虚假的节约。智能体在知道第一次编辑是否有效之前,无法安全地进行第二次编辑——如果第一次成功了,第二次可能是多余的;如果第一次失败了,第二次可能是错误的。每个编辑 → 验证 → 反应的循环都是有依赖关系的。

例外情况:当验证很慢,而智能体有一个逻辑上独立的高置信度下一步时。例如,智能体可以在运行一个漫长的测试套件的同时并行阅读文档。但同时进行两处编辑是在自找麻烦——diff 混乱、变更冲突、没有干净的回滚路径。

STEP 4

Skills、Agent SDK 与你将工作于其中的技术格局。

第 1–3 步从架构层面介绍了代码智能体是什么。本步骤介绍你实际会用到的东西:Anthropic 的 Skills 系统(Claude Code 打包程序化知识的方式)、Agent SDK(封装了代理循环的 Python/TypeScript 库)以及 Managed Agents 服务(面向生产环境的托管版本)。还包括:何时自行构建代码智能体,何时采用这些现有方案。

Skills:打包的程序化知识

第 2 步的 CLAUDE.md 捕获项目约定。Skills 是上一层:可移植的指令和资源包,用于教导智能体如何完成某类特定任务,跨项目、跨 Claude 产品面(Claude.ai、Claude Code、API、Agent SDK)均可使用。

一个 skill 就是一个文件夹。文件夹中必须包含一个带有 YAML frontmatter 的 SKILL.md 文件。frontmatter 有两个必填字段:namedescription。其余均为可选,文件夹的其余内容可包含支持该 skill 的任何资源——智能体可执行的脚本、按需加载的参考文档、模板。

# skills/postgres-migration/SKILL.md
---
name: postgres-migration
description: Generate, review, and run PostgreSQL migrations following the
  project's conventions (alembic with autogenerate, named revisions, paired
  upgrade/downgrade). Use when the user asks to add/modify/remove columns,
  tables, indexes, or constraints, or when schema changes are needed.
---

# Postgres migration workflow

This project uses Alembic for migrations.  All schema changes go through it.

## When to use

- Adding/removing/renaming columns
- Adding/dropping tables
- Adding/dropping indexes
- Modifying constraints

## Standard workflow

1. Inspect the current state: `alembic current` and `alembic history --verbose`
2. Generate the migration: `alembic revision --autogenerate -m "<short_desc>"`
3. **Review the generated migration**: autogenerate is imperfect. Specifically check:
   - Does it correctly detect type changes? (Often misses ENUM modifications)
   - Are downgrade() operations the inverse of upgrade()?
   - For renames, does it generate add+drop instead of an actual rename?
     (Fix manually — add+drop loses data.)
4. Run locally: `alembic upgrade head`
5. Run the test suite: migrations must not break tests
6. Test downgrade: `alembic downgrade -1 && alembic upgrade head`

## Special cases

- **Adding a non-nullable column to an existing table**: must include a
  default OR do this in two migrations (add nullable, backfill, alter not null).
  See examples/non_null_column.py for the template.
- **Large table operations**: `ALTER TABLE` on a large table locks it.
  Use the pg_repack pattern documented in references/large_table_ops.md.
- **Index creation**: always use `CREATE INDEX CONCURRENTLY` for production
  tables. The autogenerate skips the CONCURRENTLY hint; add it manually.

## What this skill does not cover

- Data migrations (logic, not schema): write a separate one-off script
- Production deployment: handed off to ops via SECURITY-RELEASE.md

这是一个真实的形态——描述性足够强,Claude 知道何时使用它;立场足够鲜明,足以编码项目的实际工作流程;并有指向更深层文档的引用,仅在相关时才加载。

渐进式披露:为什么 skills 不会让上下文窗口膨胀

让 skills 具备可扩展性的关键设计选择:渐进式披露(progressive disclosure)。智能体不会在启动时将每个 skill 的完整内容加载到上下文窗口中。而是分为三个层级:

┌─────────────────────────────────────────────────────────────┐ │ PROGRESSIVE DISCLOSURE │ │ │ │ Level 1 (always loaded): │ │ Each skill's `name` and `description` from frontmatter. │ │ Tens of tokens per skill. Tells the agent what exists. │ │ Loaded into system prompt at session start. │ │ │ │ Level 2 (loaded on match): │ │ When a user request matches a skill's description, the │ │ agent reads the full SKILL.md body. Hundreds to thousands │ │ of tokens. Loaded as the first action when the skill │ │ becomes relevant. │ │ │ │ Level 3 (loaded on demand): │ │ References (examples/, references/) and executable scripts │ │ are loaded only if the SKILL.md says to. Allows skills │ │ to bundle large reference material without paying for it │ │ on every session. │ │ │ │ Effect: 50 skills total cost ~2K tokens at startup, │ │ not 200K. Each invocation costs only what that skill │ │ actually needs. │ └─────────────────────────────────────────────────────────────┘

这一设计选择使"拥有数百个专业化 skill 的智能体"成为可行。没有渐进式披露,你添加的每个 skill 都会在每次会话中消耗上下文窗口,无论该 skill 是否被用到;有了它,skill 在被调用之前几乎是免费的。

Agent SDK

Agent SDK 是 Anthropic 的库(Python 和 TypeScript),用于构建架构上类似 Claude Code 的智能体——具有相同的 skills 系统、工具循环和约定——但适用于任何任务,而不仅限于编码。SDK 提供:

  • 抽象化的代理循环。你不必自己编写 while True: call_model; run_tools 循环;SDK 负责处理。你定义工具和 skills;SDK 运行对话。
  • 内置工具运行器。SDK 处理工具调度、错误封装、并行执行。你提供处理函数;SDK 将它们连接到模型。
  • Skill 自动发现。放置在 .claude/skills/(项目范围)或 ~/.claude/skills/(用户范围)中的 skills 会被自动发现,并按渐进式披露模式提供给智能体。
  • 流式事件。SDK 的 query() 函数是一个异步生成器,在智能体工作时逐步 yield 事件——适用于第 2.4 章的流式端点模式。

Python 最小用法:

from claude_agent_sdk import query, ClaudeAgentOptions

options = ClaudeAgentOptions(
    cwd=".",
    setting_sources=["user", "project"],
    allowed_tools=["Skill", "Read", "Edit", "Bash", "Glob", "Grep"],
    model="claude-sonnet-4-5",
)

async for event in query(prompt="Add a /health endpoint to the API", options=options):
    # Each event is a streamed update: token, tool_use, tool_result, status
    handle(event)

相比于从头构建(第 2 步的 150 行),SDK 给你带来的优势:经过打磨的工具(与 Claude Code 使用相同形态)、集成的 skills 系统、与 Claude Code 默认值匹配的安全默认设置,以及无需重塑代码就能升级到 Managed Agents 部署的能力。

你需要放弃的是:对循环本身的定制化。如果你需要以不寻常的方式将模型调用与自定义逻辑交织(每步多模型级联、自定义重试策略、循环周围的复杂状态机),SDK 可能会让人感到受限。在这些情况下从头构建;当你的需求符合标准代理循环时使用 SDK,而这覆盖了大多数场景。

Managed Agents:托管形态

这片技术格局中最新的部分(截至 2026 年中仍处于 beta 版):Managed Agents,由 Anthropic 完全托管智能体运行时。你不需要在自己的代码中运行循环,而是创建一个 Agent 配置(系统提示词、工具、模型),并针对它启动 Session。每个 session 获得一个沙箱容器作为工作空间;智能体在服务器端运行,并通过工具在容器上操作。

形态如下:

# 1. Create the agent once
agent = client.beta.agents.create(
    model="claude-sonnet-4-5",
    system="You are a code reviewer for the foo-api project...",
    tools=[...],
    name="foo-api-reviewer",
)

# 2. Start a session per task
session = client.beta.sessions.create(agent_id=agent.id)

# 3. Send messages; the server runs the loop
for event in client.beta.sessions.messages.stream(
    session_id=session.id,
    content="Review PR #123",
):
    handle(event)

定价为标准令牌成本加上每会话小时运行时 $0.08(仅在主动运行期间计费)。当在服务器端运行代理循环的运营复杂度不值得你的时间投入时,或者当你希望将 Anthropic 托管的沙箱作为默认设置时,这种方式很有用。如果你有托管运行时无法满足的特定基础设施需求,则价值较低。

决策树:

  • 从头构建:当你的代理循环具有不寻常的控制流,当你需要最大程度的定制化,或者当你处于学习阶段时。第 2 步的 150 行是正确的起点。
  • 在自有基础设施上使用 Agent SDK:生产环境智能体的默认选择。标准循环,完全掌控部署,在你的 VPC 中运行,与你的可观测性工具集成。
  • Managed Agents:当你希望零基础设施时,当沙箱执行是价值所在时,当你需要快速原型验证而不想构建工作进程时。

何时自行构建代码智能体,何时使用 Claude Code

Claude Code 是 Anthropic 交付的打磨精良、有鲜明立场的代码智能体。Agent SDK 让你能为自己的需求构建类似系统。两者之间的选择:

使用 Claude Code 的情形:你的需求是"帮助工程师在自己的机器上更快地交付代码"。Claude Code 是个人开发者生产力、探索性任务和密切监督工作的正确工具。它本地安装、与编辑器集成,并为交互式使用提供了正确的默认设置。

使用 Agent SDK 构建的情形:你的需求是"具有代码智能体形态的自动化,但不仅仅是工程师在终端前的工作"。示例:自动修复不稳定测试的 CI 机器人、针对用户问题自动起草修复 PR 的服务、运行夜间维护任务的内部工具。这些在 UX、安全边界、利益相关方方面都与 Claude Code 的设计目标不同。

两者是互补的,而非竞争的。使用 Claude Code 提升个人生产力的团队,同时也可能构建基于 SDK 的 CI 自动化智能体;底层技术相同,但服务于不同的场景。

WORKED EXAMPLE

端到端:代码智能体修复真实缺陷。

为了将本章锚定在具体事物上,这里提供一个代码智能体修复典型缺陷的完整追踪。不是玩具示例——而是人类需要 20 分钟、智能体可能只需 90 秒的那类缺陷。智能体使用第 2 步的工具、第 3 步的验证阶梯,以及第 4 步的项目级 skill。

背景设置

一个 Python Web API。端点 GET /users/{user_id}/orders 返回用户订单的分页列表。用户反馈:"传 page=1 时 API 返回了我的订单;传 page=0 时响应为空但没有报错。"查看规格说明,page=0 应该是一个 400 错误——该 API 使用以 1 为起始的页码。

给智能体的任务:"修复 /users/{user_id}/orders 端点,使 page=0 返回带有清晰错误信息的 400 响应,并添加回归测试。"

追踪记录

══ Turn 1 — orient ═════════════════════════════════════════════════
agent → glob "**/orders.py"
result:
  src/api/routes/orders.py
  tests/api/routes/test_orders.py

agent → read_file src/api/routes/orders.py
[shows the get_user_orders handler, ~30 lines]

agent → read_file tests/api/routes/test_orders.py
[shows existing tests, ~80 lines]

══ Turn 2 — baseline ═════════════════════════════════════════════════
agent: "Confirming current state. Run the existing tests to be sure
they're green before my changes."

agent → bash "pytest tests/api/routes/test_orders.py -x"
result: 6 passed in 0.31s

══ Turn 3 — reproduce ═══════════════════════════════════════════════
agent: "Writing the failing test that captures the bug. page=0 should
400 with a clear error message."

agent → str_replace tests/api/routes/test_orders.py
   old_str: [last existing test, used as anchor]
   new_str: [same test + new test_page_zero_returns_400]
result: Replaced 1 occurrence

agent → bash "pytest tests/api/routes/test_orders.py::test_page_zero_returns_400 -x"
result:
  FAILED ... assert 200 == 400
  E       AssertionError: response status 200 (expected 400)
  E       Response body: {"orders": [], "page": 0, "total": 0}

agent: "Confirmed — the test fails for exactly the right reason.
Now the fix."

══ Turn 4 — fix ═════════════════════════════════════════════════════
agent → read_file src/api/routes/orders.py 14 24
[shows the handler more precisely]

agent → str_replace src/api/routes/orders.py
   old_str:
     @router.get("/{user_id}/orders")
     async def get_user_orders(user_id: str, page: int = 1, ...):
   new_str:
     @router.get("/{user_id}/orders")
     async def get_user_orders(user_id: str, page: int = 1, ...):
         if page < 1:
             raise HTTPException(
                 status_code=400,
                 detail="page must be 1 or greater (pagination is 1-indexed)",
             )

result: Replaced 1 occurrence

══ Turn 5 — verify ══════════════════════════════════════════════════
agent → bash "pytest tests/api/routes/test_orders.py -x"
result: 7 passed in 0.32s
  // new test passes, no regressions

agent → bash "mypy src/api/routes/orders.py"
result: Success: no issues found in 1 source file

agent → bash "ruff check src/api/routes/orders.py"
result: All checks passed!

══ Turn 6 — done ════════════════════════════════════════════════════
agent: "Done. Added a 400 response for page<1 with a clear message,
and a regression test (test_page_zero_returns_400) that captures the
behavior. All existing tests still pass, mypy and ruff clean.

Diff summary:
- src/api/routes/orders.py: +6 lines (page<1 check)
- tests/api/routes/test_orders.py: +10 lines (new test)

Recommend reviewing the error message text — I went with 'page must
be 1 or greater (pagination is 1-indexed)' but the project might have
a convention for error messages I haven't seen."

这段追踪展示了什么

先为了内容读一遍这段追踪,再为了质感读第二遍。有几点值得明确指出:

智能体先复现,再修复。第 1–3 轮是关于理解和复现;第 4–5 轮是关于修复和验证。这是将测试优先纪律应用于 bug 修复——在改动任何东西之前先确认什么是有问题的。

智能体在改动任何东西之前先确认了基线是绿色的。第 2 轮验证现有测试通过。没有这一步,后来的"测试通过"就不像它听起来那么可信——测试可能原本就在失败。始终检查基线。

智能体攀登了验证阶梯。修复后,智能体运行了针对性的测试(捕获行为正确性),然后是 mypy(捕获类型错误),然后是 ruff(捕获风格/代码风格问题)。三个阶级,各自捕获不同类型的缺陷,在第 5 轮中全部快速完成。

智能体表达了自身的不确定性。结束信息中包含"我选择了 X,但项目可能有我还没看到的错误消息约定"。这是智能体在告诉你审查时应该关注什么。一个自信而错误的智能体会跳过这一点;而标记出真实不确定性的智能体更快地赢得信任。

智能体没有让变更膨胀。涉及两个文件,共计 +16 行。没有顺手的重构,没有"既然都在里面了"的添加。diff 正好是所要求的内容。这在 30 秒内即可审查完毕。

这就是良好的智能体工作的样子。同样的任务,如果由一个不遵循这些模式的代码智能体来完成,diff 会是这里的 5 倍,而代码真正有效的置信度只有这里的 0.3 倍。

追踪记录本身就是交付物

有一个值得点明的微妙之处:追踪记录本身就是工作成果的一部分。阅读这段追踪的审阅者,能够清楚地知道智能体做了什么、为什么、以及应该审查什么。这与"这是一个没有任何上下文的 PR"是截然不同的体验。对于由人类审查的智能体生成代码而言,追踪记录的可读性是价值的一半——这也是为什么系统提示词和验证纪律与代码本身同样重要。

End of chapter 4.1

交付物

对代码智能体作为独特架构的可用心智模型:持久化文件系统状态、可编程验证、有界行动空间、紧密反馈循环。覆盖大部分代码工作的六工具面(read、write、str_replace、glob、grep、bash)。将"智能体写了代码"转化为"智能体交付了可运行代码"的验证阶梯纪律。对 Skills 作为可移植知识打包机制的熟悉。清楚何时使用 Agent SDK 构建,何时采用 Claude Code。你能够构建、交付、评估并推理代码智能体——并且理解架构为何如此设计,而不仅仅是知道该复制什么。

  • 六个核心工具已实现:read_file、write_file、str_replace、glob、grep、bash
  • str_replace 作为编辑原语;write_file 保留给新文件
  • 沙箱化:safe_path 强制执行,不得逃出仓库根目录
  • 系统提示词包含项目约定和明确的验证命令
  • 验证阶梯:语法 → 代码风格检查 → 类型检查 → 单元测试 → 完整套件,按需攀登
  • 系统提示词中为 bug 修复编码了测试优先纪律
  • 追踪记录可读性:智能体报告运行了什么、通过了什么、对什么存在不确定性
  • Skills 文件夹用于带 YAML frontmatter 的可移植程序化知识
  • 渐进式披露:skill 元数据在系统提示词中,主体在匹配时加载
  • 决策:从头构建 vs. Agent SDK vs. Managed Agents 与你的实际形态相匹配
  • 人工审查 diff 作为最终关卡,即便对于经过充分验证的工作也是如此