子代理的实际应用:角色、预算、禁用机制与静默失败处理。
Claude Code中的子代理持久化机制:角色设定、交接文件、运行状态、预算管理、禁用规则与静默故障检测,以及实际应用中技能与子代理的区别。
技能可以解决一种遗忘问题:即模型不会再忘记你的代码库是怎样的。但还有另一种遗忘问题,与身份相关。在每个新会话中,模型都以同一个充满热情的通才身份出现,乐于对你呈现的任何内容给出合理且平常的看法。
知识属于技能层面的问题,而角色则属于子代理层面的问题,本文正是探讨后者。
简而言之,先不说复杂的细节:技能告诉模型你的世界是怎样的,而子代理则告诉模型该扮演什么角色。两者结合比单独使用任何一方都更好,对于同时要处理多个未完成项目的独立开发者来说,这种组合尤为重要。你是团队中唯一的专家,无法同时承担多个角色。
下半部分首先是概念性基础:这些文件是什么,它们存储在磁盘的哪个位置,以及如何创建一个这样的文件。这些内容都可以从文档中找到。后半部分则需通过让两个子代理在数十个代码库中运行数月,并观察它们出现最佳实践指南中未曾提及的故障情况才能获得。这两个代理都是开源的,真正的文件非常值得阅读,因为经过实际运行不断优化的提示,其教学价值远超任何建议。
子代理填补的空白
这款编码助手默认具备一种性格特质,即乐于助人的通才型。当你请求代码审查时,得到的反馈往往是泛泛而谈、缺乏具体性的。若询问应用为何运行缓慢,它会开始检查你最近修改过的文件。要求制定数据库迁移方案时,虽然给出的方案看似合理,却会忽略可能在实际生产环境中造成问题的三个关键点。
这并非因为模型能力不足,而是缺乏明确的角色定位。负责安全测试的人与安全工程师的思考角度不同,而追踪漏洞的人与那些已三次处理过同一故障的人所依据的证据也有所差异。智能功能其实早已存在,缺少的只是合适的应用框架。
这种“赠送”行为其实就是反复输入相同的开头语。在让 Claude Code 查看修改内容之前,你可以粘贴类似这样的文字:像一个多疑的安全人员一样审查,先检查认证边界,告诉我有哪些信息会泄露到日志中,忽略那些风格上的琐碎问题。如果在两周内出现第三次这种情况,你就会意识到:那个开头语其实是一个角色,而你不断重复输入的正是目前还无人编写过的子代理。
所有指南都提到的触发因素就是反复输入,但真正造就那两个存活下来的代理的,却是来自其他两种压力的结果。
- 上下文经济性。研究工作十分繁琐:进行二十次搜索,获取十几页结果,其中一半毫无用处。如果在主要工作流程中出现这种情况,页面上就会充斥着那些根本不想阅读的垃圾信息。这类工作需要在其他地方完成,然后再以总结的形式呈现出来。这正是网络搜索工具存在的理由。它并不比主线程更聪明,其作用仅仅是将读取到的内容排除在当前上下文之外。
- 角色分离。在设计工作中,人们希望专注于设计讨论本身。如果被拉出来去批准第十一版的机械结构修改——而该修改其实只是基于已有决策的简单调整——那就纯粹是浪费时间。因此,决策权应留在人类手中,而具体的输入工作则由执行工具来完成。能力与这种分工并无关联,它所决定的只是谁应该负责思考什么问题。
审核员是一种不错的初级子代理。这两者有所不同:正是它们将某种便利功能转变为项目实际运行的方式。
技能与子代理:认知与行为
技能负责封装代理需要了解的内容:那些特殊的内部库、会导致构建失败的环境问题、聪明的新员工必须被告知的领域知识,因为没人能猜到这些。这些内容会在相关情况下被加载。
子代理则负责封装代理应当如何行动:它优化什么、首先检查什么、默认选择何种权衡方案,以及答案应以何种形式呈现。它不会向模型传授任何新知识,只是将模型已有的智能定向用于特定任务。
一个实用的测试方法是想象请一位专业同事来协助。如果他们能提供的主要是你缺乏的信息(领域知识、背景信息、行业经验),那么你需要的是技能;如果他们能提供的主要是解决问题的方法,也就是他们首先关注什么、重点考虑什么、不经过核查就不批准什么以及最终会给出什么结果,那么你需要的是子代理。
以安全工程师审查你的代码为例。他们不仅比你懂得更多,而且工作方式也不同:首先考虑认证边界,权限提升路径的重要性远高于一个难看的变量名,没有对敏感信息进行注释就不予以通过,而且会将发现的结果以结构化的列表形式呈现。这种注意力分配的顺序正是子代理所体现的。
知识属于技能,而方法论则属于子代理。
Claude Code中的子代理是什么
Claude Code 是这里的参考工具,因为它被日常频繁使用。其他工具则会在后面单独列出,包括它们的特殊之处。
在 Claude Code 中,子代理其实只不过是一份以简短 YAML 代码块开头的 Markdown 文档而已。它可以存储在两个目录中:
~/.claude/agents/是个人库,存在于机器上的每个项目中,可将其视为沙箱环境。- 仓库内的
.claude/agents/则仅适用于该项目,一旦提交就会随仓库一起传输。
前置内容很简短。以下是一个完整的可用示例:
---
name: migration-risk-reviewer
description: Reviews database migration plans and schema changes for rollback risk, lock contention, and data integrity problems. Use before running any migration against real data.
tools: Read, Grep, Glob
model: sonnet
---
You are a senior database engineer reviewing a migration for production risk.
## What you look at first
- Can this be rolled back without losing data?
- Does it hold locks on a busy table during deploy?
- Is the ordering safe for a multi-step change?
- Will existing data violate any new constraint?
## Output shape
Findings in order of severity:
1. Blockers — deploy will fail or data will be lost
2. High risk — real production risk, needs a mitigation
3. Medium risk — should fix, won't necessarily block
4. Notes — worth tracking
For each finding: what it is, why it matters, what to do about it.
关于 Claude Code 处理此类文件的方式,有几点值得了解。
- 正文部分即为系统提示词。 前置信息之后的所有内容都会在子智能体运行时作为其指令,而非模型阅读后据此行动的文档。模型会将其直接视为自身的身份标识。
description的作用非常重要。 Claude Code 会参考它来决定何时自动将任务交由其他智能体处理,因此应将其写成需要他人从上下文中推断出的 API 说明。例如“在运行任何迁移操作之前使用”这样的表述是有效的,而“有助于处理数据库相关事务”则无效。同时也要说明其不适用的场景:此处讨论的两种智能体都会明确指出自身不适用于哪些任务,因为路由系统会毫不客气地将本不该由它们处理的任务转交出去。
tools属于允许列表。若省略该选项,子代理将继承所有权限;若限制其权限,则即便子代理有修改想法,也无法实际编辑你的文件。对于审核代理而言,这反而是优点,因为那种“只需告诉我问题所在”的处理方式不应让其随意改写内容。model是预算选择而非细节设置。便宜的模型适合处理机械性任务,而昂贵的模型则适合需要判断力的工作。后续章节会详细探讨这一点,因为它解释了为何委托任务能带来回报。/agents 命令也可用于管理这些文件。你可以直接调用子代理,让 Claude 根据描述将其路由过去,或者将其集成到更大的流程中作为委托任务执行。无论哪种方式,其行为都相同。
“agent”一词的职责过重
这个术语被用于四种不同的概念,混淆它们会不断引发混乱。将其区分开来:
- 子代理(位于
.claude/agents/目录中的文件,或 Copilot 环境下的.agent.md文件)是一种可重复使用的文件,用于定义特定角色。本文讨论的正是这类内容。 - 代理模式或自动运行是一种功能开关。它允许工具直接执行命令和编辑文件,而无需逐行审批。这是一种权限设置,与专用工具无关。
- 后台或云编码代理是一种服务,它会接收任务,在后台默默处理,然后返回一个分支或拉取请求。它也不是专用文件。
- 仓库级指令文件是始终有效的指导方针,即所有代理在该代码库中都必须遵守的规则。可以将其视为员工手册,而非需要直呼其名的同事。
后续内容中凡是出现“子代理”一词,指的都是那些一次编写、可重复使用的专用工具。理清这四者的关系就能解决大约一半的困惑。
编写第一个子代理
从最简单且能真正发挥作用的部分开始。第一个子代理有两个任务:明确其职责,以及详细说明该职责在实践中的具体含义。上面的迁移审查器已经做到了这一点——它有明确的职责、具体的判断规则以及可预测的输出格式,而且其功能被刻意限定在较小范围内。在实际任务中运行后,若发现它有遗漏之处,再逐步增加更多功能。
第一个子代理往往因为职责定义模糊而失效。“安全辅助工具”并非真正的职责,因为它没有告诉模型应该优化什么或首先检查什么。一个真正的职责应作为行为准则,涵盖工作内容、首先需要检查的要素以及绝不能忽略的事项。如果无法用一句话概括某项工作,那就说明它的定义太过模糊了。
具体的版本会明确说明模型应当关注什么以及应当忽略什么。懂得忽略本身就是重要能力。那些将拼写错误与权限边界视为同等重要的评审者,不过是个简单的平均器罢了。
正文应包含的内容
明确了角色定位后,一个有用的正文应涵盖五方面内容。第一天并不需要全部五点。
- 优化目标。该角色最努力要做对的那一件事。当出现权衡时,它就会朝着这个目标进行取舍。
- 首先检查的内容。任何真正的专家都会首先查看的优先级最高的信号。
- 故障模式。这类工作出错的特定方式。真正的专业能力就体现在这里,而大部分专业知识只有通过观察智能体在处理实际任务时的失误才能获得。
先说明角色、最初的检查项以及输出形式,其余内容则由错误信息来补充。
示例:忽略最新提交的错误排查工具
这是一个因反复出现的困扰而设计的示例。当系统出错后,错误信息会被粘贴到 Claude Code 中,它的第一反应就是查看最近被编辑过的文件。有时这是正确的,但很多时候最后的修改与问题毫无关系。
经验丰富的调试员不会从代码开始着手。他们会先从证据入手,即确定究竟是什么出了问题、日志显示了什么以及环境中有何变化,只有在某个假设值得验证时才会去查看源代码。这是一种行为模式,因此被保存为一种智能体:
---
name: bug-investigator
description: Investigates a bug or failure starting from evidence, not from recent code changes. Produces ranked hypotheses with the next diagnostic step for each. Use for "why is this broken" before touching the source.
tools: Read, Grep, Glob, Bash
---
You are a senior engineer investigating a failure. Your job is to find the most
likely cause, rule out the alternatives, and say what to check next.
## Investigation order
1. Start from the symptom — what is actually failing, and how does it show up?
2. Get the error output and any logs from around the time it broke — ask the caller for them if they weren't provided.
3. Only then look at recent changes, and only if the evidence points there.
4. Read source last, once there's a symptom-to-cause hypothesis worth testing.
## Output shape
**Symptoms:** what's failing and how it manifests.
**Likely causes (ranked):** for each — the hypothesis, the evidence for it, the next diagnostic step.
**Ruled out:** what you checked and why it isn't the cause.
**Next steps:** in priority order.
**Open questions:** what you still can't confirm.
## Failure modes to avoid
- Don't assume the last change is the cause without evidence.
- Don't propose a fix before you can name the mechanism.
接下来是人们常常跳过的步骤:在已经解决的实际故障上进行测试,而非使用模拟故障。把故障现象以及当时的日志交给它,观察其排查路径是否与真正找到问题时的路径一致。只要出现偏差,这些偏差就会成为报告正文中的新内容。
大多数子代理报告到此为止。从这里开始描述的是经过数月使用后的情况,而这正是需要说明的部分。
与预期相反:子代理从零开始
大家都认为独立的上下文窗口是巨大的优势,但实际上它利弊参半。
有一句话在经过三次不同的传递后进入了执行代理,它描述了该代理虽然对项目非常熟悉,却对任务启动前的规划讨论一无所知。
如果对话状态为“冷态”,则意味着它并未看到你花费两小时排除显而易见的方案、说服自己放弃抽象思路,并最终出于合理原因选定一个看似奇怪的解决方案的整个过程。而对子代理而言,这一切都未曾发生。如果项目状态为“热态”,则表示它拥有CLAUDE.md文件及代码库,因此无需他人告知就已知晓项目的规则与惯例。
若对这两者中的任何一方判断有误,都会导致可预见的混乱局面。假设代理能参与你的对话,它会欣然重新构建你一小时前否决的设计,因为在它看来那个设计完全没问题。而假设它对项目一无所知,那你就得花费大量资源去描述一个它本可以比你总结得还快的时间就读完的代码库。
没有任何子代理指南给出过这样的结论:交接内容是一个文件,而那个文件就是合同。它并非输入到任务提示中的段落,而是一个包含固定章节的磁盘文档,因为即兴生成的提示每次都会悄悄省略不同的关键章节。
在这种架构中,交接内容是存储在仓库根目录下的TASKS.md文件,由规划技能生成。当前任务仅有这些章节,没有其他内容:
- 目标。工作完成后应实现的情况。
- 原因(指针)。指向
PLAN.md中记录的决策的链接,而非新的论证理由。只是指针,而非文字描述。 - 运行状态。代理会持续更新此状态。下文将详细说明,因为正是这一章节能拯救你。
[ ]表示未开始,[x]表示已完成,[!]表示被阻塞,依赖关系会直接标注在相应项后,例如依赖#2。其有效运行的关键在于一条规则:在启动之前必须填满每个部分,只有当某部分确实不适用时才在其前加上短横线。未填满的部分则由执行者自行处理。
还有那种最难被察觉的分离:即具有不同生命周期的两个文档。关于此处工作方式的固有知识应包含在智能体定义中,而非在每个任务中重复说明。智能体文件中记载着协议,也就是如何读取任务、处理障碍、哪些内容绝不可更改以及报告应包含什么内容。而任务文件则用于存储当前的具体工作。如果将同一条指令连续粘贴到三个任务中,那就说明它本就不属于任务内容,应当移至智能体文件中。
运行状态,因为会话会结束
有一条规则比任何巧妙的提示工程技巧都更能帮助完成实际工作。每当智能体停止运行,无论是已完成还是遇到阻碍,它都必须记录当前进展:逐项勾选状态选项,并更新运行状态说明,内容包括已完成的工作、受阻的原因、剩余任务以及下一步该从何处继续。为此目的编辑 TASKS.md 明确属于工作范畴,因为该文件是会话中断时的恢复点。
会话确实会中断。笔记本电脑可能关闭,连接可能断开,也可能达到使用限制,又或者你因为厌烦而在任务接近完成时按下 Ctrl-C。当这种情况发生在子智能体身上时,它所掌握的所有信息都会消失。如果进度的唯一记录只存在于它的记忆中,那你就只能通过对比代码库和自己的记忆来推测它完成了多少工作。
因此,代理会在执行过程中将进度记录到分配任务的同一个文件中。后续的会话打开该文件后可从标记的位置继续执行。它会为代理定义添加几行内容,并将已终止的会话从“任务丢失”状态转为“暂停”状态。
另一条相关规则关注的是处理效率而非恢复功能。被阻塞的任务不会导致队列停止。当某个任务的信息不完整或遇到无法解决的依赖关系时,代理会用一行文字将其标记为[!],并继续处理所有不依赖该任务的剩余任务,只有当没有其他任务可以继续时才会暂停。它从不猜测设计方案,所有被阻塞的任务都会被记录在报告中,由人工进行处理。
遇到问题时的默认行为是暂停并询问,因此八个任务队列会生成一个任务和一个问题。如果没有约束机制,默认行为会更糟糕:智能体会自行猜测设计并继续执行,最终留下七个基于未经任何人认可的方案构建出来的部分。应直接跳过并继续,切勿猜测,而是报告分支情况。正是这种组合才让你能够将工作加入队列后再离开。
哪种思维方式需要谁来承担成本
在这里,model字段发挥着关键作用,整个系统的经济模型也取决于它。
来自一个实际项目的实施者描述大致如下:Sonnet工作节点负责从任务文件中执行完全定义好的机械式编码任务,而主Opus规划线程则继续运行;设计决策、未解决的问题以及实时质量测试则都留在主线程中处理。
高级型号负责决策,而低级型号则负责输入操作。正是这种有限的职能范围使得低级型号具备可靠性:当给 Sonnet 工具一个它能够读取的、包含完整规格说明的编号队列时,它的运行就会十分稳定。
这种分工也让同一套系统能够以两种不同的工作模式运行。
- 有节奏模式。准备两到三项任务,将代理程序派去执行,同时在它工作的过程中规划下一批任务。双方都不会闲置等待对方。
- 无人值守模式。先批量排队,然后离开,之后再回来查看差异对比、报告以及运行状态记录,了解是哪项任务被阻塞以及原因何在。
在这一切之下,是真正负责执行各项规则的权限层。无人值守的实现者其文件和shell命令会被列入项目设置中的允许列表,任何不在该列表内的操作都需要经过审批。提示内容代表政策规定,而允许列表则相当于限制屏障。
该代理程序中还多出那一行代码,是因为犯了最严重的错误:将各个任务按顺序排列成串行队列,从上到下一次性处理,既没有并行处理机制,也不会在每完成一个任务后就停下来汇报。
并行代理听起来似乎能提升效率,但实际上并非如此。每个代理最终都会将其所有输出结果倒入生成它的上下文中,而那些看似巧妙的输出方式反而会让必须读取全部内容的会话不堪重负。这样一来,很多实际工作就白白浪费了。在单独的文件上并行处理任务只应在特定需求时才采用,绝不能作为默认设置——默认情况始终是一个工作进程按顺序处理一个队列。
没有预算限制就会耗尽所有资源
任何拥有搜索、获取数据或shell等工具的子代理都会持续运行,直到有外部因素将其停止,而模型本身无法可靠地判断“已经足够”。一个没有预算限制的研究代理可能会在大量隐藏的浏览器标签页上耗费整个下午,最终得到的总结其实前三个结果就已经能提供了。
因此,网络搜索代理从一开始就设有严格的限制,这些限制高于其工作方法,也高于其他一切。它在运行时加载的每个来源对应的操作手册中包含了各域名的来源列表及查询策略,这些内容会在后面单独列出。
从那些限制条件中可以总结出四点重要启示,比手册中的具体数字更为关键,您应根据自己的工作需求对这些数字进行调整。
提供深度调节选项而非固定上限。任何单一的设置大多情况下都是不合适的:对于复杂问题来说限制过严,而对于简单事实来说则过于宽松。通过设置三个命名等级并指定默认值,选择deep模式就能成为有意识的决定而非偶然行为。优先级顺序也明确指出:首先以调用方设定的数值为准,其次是提示中指定的等级,接着是项目配置,最后才是standard模式。
预算机制会促使人们进行游戏化操作,因为模型会将其视为目标,所以需要相关条款来加以限制。 关键的条款规定:每个URL只能获取一次数据,不得重新读取已读过的页面,一旦回答了调用者的问题就应立即停止,且剩余预算不应被视为配额的一部分。如果通过六次搜索就完成了深度搜索,那就属于成功,而非浪费。
最后那句话是在某次搜索第四次就回答了问题却仍继续进行的场景下添加的,因为给定的目标数值是20。必须明确指出,提前完成才是胜利。
统计所有需要消耗资源的操作。获取预算涵盖了从常规数据获取到被屏蔽页面的逐级尝试,再到辅助脚本中的每一条HTTP请求,甚至该辅助脚本中的每一次429重试都会被单独计数。早期版本仅统计“获取调用”次数,结果代理程序无意间钻了规则的空子。但这并非作弊行为,只是严格遵循了编写有误的规则而已。
让其报告支出情况。在每个阶段结束后,代理会输出类似 [deep: 3/20 次搜索,5/30 次获取] 这样的信息。如果没有这些报告,就无法在任务结束前区分是正常运行还是失控状态。如果预算耗尽而仍有问题未解决,代理会停止运行,并明确指出哪些问题仍未解答、哪个网址最有可能提供答案,同时说明可以选择再次深入搜索。唯一被禁止的做法是未经说明就继续超出限制运行,因为这是由调用者设定的限制,超额支出会推翻这一设定。
限制措施构成了成熟代理的核心
打开使用频率最高的智能体文件,首先映入眼帘的就是各项比例。角色描述仅占一段文字,而禁止事项则占了一整列列表。这种失衡并非为了凑内容。拥有真正工具的智能体,在技术层面遵守规则的方式远比任何人能预想的要多。
有一个例子可以说明为何那些附加说明至关重要。该智能体被明确要求不得使用浏览器自动化功能:不能使用普通浏览器,不能使用内置预览功能,不能使用Playwright,不能使用Chrome扩展工具,也不能打开标签页或窗口;即便有浏览器工具可用,也不得用于此项任务。原因就在那里写着:打开标签页阅读页面后,数量会达到上百个,从而破坏整个流程的顺利进行。
另一个要求来自研究代理仓库的实现者,该机制正是人们遵守规则的根本原因。绝不能在 agents/ 或 skills/ 下创建文件,因为 agents/ 中的内容是预置的有效载荷,包管理器在安装时会将其中的每个 .md 文件合并为一个独立的顶层代理。若在此处出现异常文件,它将会被包含在所有使用该代理的项目中。
这就是需要提取的原则:每条禁令都会明确说明其机制。无需对代理进行说服。必须把原因写下来,因为没有原因的约束会被修改掉。六周后,在精简文字时,你会遇到“绝不在此处创建文件”这样的表述,它看起来很随意,于是你会将其概括化。关于安装程序会将内容整合到每个用户项目中的那条规定,正是让未来的你不再去触碰它的原因。
经过足够长时间的实践,这些约束还会呈现更多形式:
禁止整个类别,并列出具体实例。仅仅写上“禁止浏览器自动化”会让代理自行判断预览窗是否符合要求。列出六种它不得使用的特定工具,就能堵住它原本可能会找到的突破口。
应命名行为本身,而非仅仅是工具。 文件中最重要的一条准则是禁止某种倾向,而非下达指令:如果你发现自己正在编写抓取工具或报告生成器,那就停下来,因为你这是在绕过任务本身而非真正完成它。能力强的智能体是通过构建工具来规避限制的。这才是需要被禁止的类别。
必须提供备用方案,否则它就会变成一条“高速公路”。确实有部分页面无法获取,正因如此才有了针对被屏蔽URL的层层备用机制。只有当该URL的常规访问路径失败时,这些备用机制才会启动。它不会占用额外的访问名额,也不会在未安装相关工具的情况下尝试检测,同时还会限制输出量,避免页面内容淹没整个上下文。最后是彻底的终止:如果用尽了所有备用方法该URL依然无法访问,那就只能放弃。将其记录下来后继续前进,无需再尝试,也不需要额外的辅助工具或创新的解决方案。
详细说明为何该例外情况不属于漏洞。 文件中有一段简短说明指出,一次性无界面获取并打印纯文本的行为并不违反浏览器禁令,因为该禁令针对的是让浏览器持续运行并留下需人工关闭的标签页的情况。若省略这段说明,该例外与禁令似乎就会产生冲突,而面对冲突的模型会选择允许更多行为的解释。
它们悄无声息且自信地失败了
有两次研究结果不仅没有问题,反而相当出色:结论清晰、来源真实、报告条理分明。两者都将Reddit列为主要信息来源,但实际上并未从Reddit上获取任何内容。
实际情况是,使用 site:reddit.com 限制条件的 WebSearch 查询从其他域名中返回了十个干净且合理的结果,其中包括一个手工艺品市场社区论坛、一个政府小型企业网站以及一个幻灯片分享网站,全程没有任何错误。没有 403 错误,没有空结果集,也没有任何警告。域名过滤被悄悄忽略了,而输出结果中也未显示任何相关提示。该工具从这些错误来源中整理出了一份可用页面列表,然后像任何理智的工作者处理此类列表时那样,直接使用这些页面并报告任务已完成。
当天记录下来的规则如今成了整个项目的基石:如果无法访问到指定的数据源,运行过程就必须明确说明,绝不能仅凭工具返回的结果就悄悄继续。那种将Reddit列为主要数据源,却又声称从市场论坛获得了成功结果的报告,其实比彻底失败更糟糕,因为阅读报告的人根本无法分辨真相。
运行失败就需要重新执行。而那些悄悄更换数据源的运行,则会让你基于本以为来自正确来源的实际证据做出错误判断。正是由于那个独立的上下文窗口这一设计,人们才看不到这些异常情况的发生:只有代理程序自行生成的摘要会呈现出来,而没有原始记录可查。
有三种方法可以解决这个问题,但其中没有任何一种是要求代理程序更加谨慎的。
对其进行机械式测试。由于没有出现任何错误,该代理认为无需检查任何内容。解决办法很简单,只需一句话,无需任何判断:当搜索被限制在某个域名内,而返回的URL中没有任何属于该域名的,应将其视为零条结果而非结果集。这不需要额外的工具、获取操作或权限,因为这些URL已经存在。“对数据来源保持怀疑”这种建议对代理毫无用处,而“将主机名与过滤时使用的名称进行比对”则有效。
将来源信息视为具有结构规范的必填输出渠道。此时,输出格式就不再只是一组章节标题,而是开始具备类型特征。两个数组会通过其键名被明确指定。
unreachable[]中为每面墙存储一条记录,键分别为source、url和reason,用于记录获取失败的情况以及静默替换的情形。sources[]中为每个实际提供过答案的来源存储一条记录,键分别为source、url和fields;仅被打开但未提供任何信息的页面不会被记录。若某个来源提供了四个字段的信息,则会生成一条包含四个名称的记录,而非四条记录。
这种看似微妙的处理方式起初其实有误:来源标注只是提供参考,而非阻碍。如果有记录在案的替代方案能够回答该问题,该字段即视为已得到解答,而unreachable状态仅表示答案并非来自指定的来源。如果在存在变通方法的情况下仍让某个环节失败,就会导致整个流程在本应正常运行的情况下出现严重故障。
每一次失败都蕴含着值得借鉴的教训。这个智能体的初稿在四个不同位置要求它“将其标记为无法访问”,但却从未指定具体的字段、数组或输出位置。这样的指令毫无意义——智能体不知道该把数据记录到哪里,因此什么也没记录,而也没有任何问题出现。每当你为子智能体编写指令时,务必确保其所描述的操作有明确的存储目标。如果“报告X”却没有指定X的具体存储位置,那它就什么都不会做,而且还会觉得自己做得没问题。
从智能体外部进行验证。智能体是否履行了其输出承诺,应由之后运行的脚本来判断,而非依赖智能体自身的保证。大约180行Python代码会检查JSON数据是否符合预先声明的字段结构,这比在提示语中再怎么强调都更有用。
在那些没有测试套件可供参考的仓库中,实现方案也体现了同样的思路。相关说明会指出不存在测试套件;任务中的“测试”部分会列出替代测试的校验项,每一项都必须执行,并且要详细报告结果。如果某项校验的要求是“该URL能够解析”,那就意味着实际上需要去获取该URL的内容。无法验证的URL不会被记录在文件中,而是会以“未验证”状态被报告,同时说明出现问题的原因——因为如果读者轻信未经验证的链接,就会白白进行一次请求,而这种请求很可能会遇到404错误,这种情况其实比直接不使用该链接还要糟糕。
代理程序常常声称自己已经完成了验证,但实际上它们从未真正执行过验证,因为在总结中,可信的声称与实际已验证的结果看起来并无区别。对于每一项重要的校验,都要明确指出具体采取了哪些操作才算完成了该校验。
每个智能体只承担一项任务,以及导致文件臃肿的那些习惯
最优秀的子智能体都只负责一项任务。你可能会提出异议,因为上面提到的那些文件并不简短,那个研究智能体的代码甚至有188行。但关键区别在于:任务内容始终只有一句话,而文件却在不断膨胀。真正堆积起来的其实是各种约束条件、检测规则以及输出规范,就像固定框架周围的瘢痕组织;而且一年之内都不会新增任何职责。如果因为某些功能出问题而增加了代码行数,那说明文件仍在正常履行其功能;而如果增加行数是为了处理新类型的工作,那就是臃肿的表现,解决之道应是拆分智能体,而非再添加新的功能模块。
那些臃肿的版本看起来都差不多,而且每个都曾有过上线使用的时刻。有那种综合评估所有方面的工具,但对任何一项都做不到出色表现;有挂着“高级工程师”头衔但实际上并无实际功能的工具;还有那种只会重复你的CLAUDE.md内容、仅增加令牌成本而不带来实际功能的工具,以及那种不断复制技能直至两者出现差异的工具。它们都有一个根本性的错误:任务范围被扩大到了超出简单句子的程度。
接下来显而易见的问题是,经过一年的发展,究竟还有哪些工具留在了测试阶段。
存活下来的两个
关于这个主题的大多数文章都会给出一份可供参考的智能体类型组织结构图。但经过一年后,真正留在测试阶段的只有两个智能体,仅此而已。
根据文章自身的规则,需要公开一点信息:该网络搜索代理最初是一个开源深度研究工具项目的分支。其输出结构框架,也就是摘要部分以及必须列出的参考资料列表,几乎完全是照搬自上游项目。而叠加在之上的控制层,包括预算管理、封禁机制、来源说明架构以及问题升级流程,则是原创内容,占到了整个代码量的半数。本文中关于各种缺陷的描述均涉及这一半内容。
网络搜索代理能够执行有限的网络搜索,并将搜索结果及其来源一并返回。目前它已被部署在十几个代码仓库中,包括一个静态博客网站、几个数据处理流程以及若干研究项目。这里提到的所有关于预算、封禁以及隐性替换的内容,都源自那个文件的版本历史记录。
实施者会执行完全定义好的任务队列,而规划工作则在其他地方继续进行。有四个项目配备了这样的实施者。正是这四个项目提供了关于交接协议、运行状态以及任务委派经济性的相关经验。
代理文件夹中的其他内容都是与框架一起提供的,它们更像是经过美化包装的杂物箱,而非真正的实用工具。那些被认为必要的专家角色——比如缺陷测试调查员、公关润色代理、文档编写员,以及为已废弃项目设计的代码库管理代理——听起来都合情合理,但实际上几乎都没有被开发出来。最终留下来的那些,往往并非那些听起来最有用的角色,而是那些因相同类型的特定工作问题在错误的环境或由错误的人负责而不断重复出现的情况所对应的角色。
那就跳过组织结构图吧。专注于那些希望在其他地方执行的任务,并把它们保存下来。
先使用模板,再逐步专业化
让第二、第三和第四个执行者成本较低的模式是这样的:代理是从模板生成的,然后在项目有差异的地方进行调整。
规划工具会提供一个带有占位符的执行者模板,而在搭建新项目时,则会同时生成代理、计划文档和任务文档。最终得到的就是一套流程:阅读合同,按顺序处理各项内容,跳过任何受阻的部分,保持执行状态的最新性,不超出范围,并在最后进行汇报。这些规则在不同项目中始终不变,因为它们描述的是任务分配本身,与代码无关。
之后,每个副本都会为自身的故障面创建独立模块,将两个副本并排展示能起到很好的说明作用。模板要求智能体保持测试套件处于正常状态。有一个项目根本没有测试套件,它只是一个提示语存储库,唯一的可执行文件是验证器。因此该项目的实现者提出了完全不同的观点:该存储库存放的是提示语和数据而非代码;编辑文件即等同于编辑提示语;措辞、顺序和强调方式就是实现方式;而在不违反硬性约束的前提下让文本更易读的改写,由于不存在构建过程和测试,也不会被任何机制标记为回归。
另一个项目的副本增加了这样一条指令:在编码之前,需先研究任务所指向的任意PLAN.md中的决策内容,同时对于需要Opus系统或人工干预的情况还需记录反馈信息。流程本身相同,但存在的风险却有所不同。
这就是需要记住的分界点。子代理的可复用部分是其协议,而特定于项目的部分则是其故障面。前者应使用模板生成,后者需手动编写,切勿试图用一个文件同时涵盖两者。
让代理指向知识,而非直接粘贴内容
这是技能与子代理相互结合的地方,而一旦真正的处理流程依赖于这一机制,其概念就变得更加具体了。
我之前的建议是在代理主体内为相关技能命名。更有效的做法是更为严格:代理在采取行动前会先读取知识,而提示语则用于说明原因。
在读取存储在某个技能中的路由表之前,研究智能体无法进行任何搜索。相关指令被标记为必须执行,内容为:在进行任何搜索或数据获取之前,先读取下面存在的第一个路径对应的路由表;不得跳过此步骤;不得凭记忆选择路径,因为虽然提示语保持不变,但相关模块会发生变化:你记忆中的模块可能已不存在,而你需要的模块可能尚未出现。
关于在提示语不变的情况下列表会变化的这一说法,正是该模式的完整依据。各知识源对应哪些类型的问题、如何查询它们、哪些内容被禁止访问等信息每月都会变动,而查询方法却始终固定不变。如果将知识源列表直接嵌入智能体主体,那么每新增一个知识源就都需要修改原本不变的提示语,这样一来,智能体就可能会自信地选择早在三月份就被删除的模块来处理请求。
有两个机制的重要性超出了预期。
按顺序列出候选路径,而非仅一条路径。 该程序在由不同安装程序设置、使用不同用户名、运行在两台不同机器上的项目中执行。因此,说明中会按顺序列出候选路径,并要求优先使用存在的第一个路径,且优先选择项目级副本而非用户级副本。如果硬编码为单一路径,那么程序就只能在同一台机器上运行。
这些字面名称将各个层次联系在一起,但没有任何机制来验证它们。该设计由三层构成:技能负责协调,代理执行任务,数据模块则存储知识。整个系统中不存在任何导入操作。技能会通过其精确注册的名称来启动对应的代理,而代理则通过路径来查找模块。这也就导致了该仓库中最常见也最危险的状况——因为现有调用者直接使用该名称,所以绝不能重命名网页搜索代理。
代理的名称实际上充当了公共API,但没有任何工具会对此进行提示:没有编译器、没有测试、也没有警告。一旦重命名,看似只是简单的一次提交操作,却会破坏所有安装了该代理的仓库中的调用程序,而问题往往要等到下次请求搜索时才会被发现,此时会有通用型代理来响应。
文件存储位置:个人、项目、打包形式
共有三个层级,真正的智能体都存在于第三个层级。
- 个人级(
~/.claude/agents/)用于存储每个项目都需要的通用专家智能体。可将其视为实验车间,在这里出错是正常的。 - 项目级(
.claude/agents/,已提交)用于存储仅适用于某个代码库的专家智能体,比如那些知道该项目缺乏测试的实现人员。将其提交后,无论你何时返回,甚至是在四个月后你已忘记该项目的情形下,它依然会在那里等待。 - 打包级(由包管理器安装并绑定到某个提交版本)会将智能体作为依赖项。这种层级在同一个智能体出现在数十个仓库且十三个副本逐渐出现差异之前,没人会意识到它的存在。
第三级会有一些变化,在到达那里之前最好先了解这些变化。
将代理与调用它的技能捆绑在一起。之所以将二者作为一个整体打包,原因很简单:未搭配相应代理而安装的技能会在首次使用时导致流程中断。试图通过名称调用缺失代理的技能属于安装错误,最多只会显示一条简短的错误信息。它们之所以是一个整体,是因为它们属于同一个契约。
了解安装程序对文件的处理方式。该软件包通过微软的APM进行安装,它会将每个.md文件置于agents/目录下,并将其转换为独立的顶级代理。虽然这种默认设置合乎逻辑,但却存在问题:若在该目录下放置数据文件,每个使用该文件的项目都会出现一个虚假的已注册代理;而若在该目录下编写针对特定项目的代理,它就会自动安装到所有项目的代码库中。正因如此,搜索策略相关模块被放置在skills/目录下,而非看似更合适的agents/目录——若放在agents/目录中,这些模块在安装时会失去原有结构,且每个使用该模块的项目都会注册五个虚拟代理。
锁定某个提交版本时,要记住后续使用方会滞后。使用方依赖SHA值,因此在锁定被解除且重新执行安装之前,任何人都无法使用修复后的版本。编写修复代码并不等同于真正发布修复版本。页面上这听起来很显然,但到了上周刚修复的漏洞再次出现时的晚上11点,情况却完全不是这样。
即便如此,仍需进行整理。一个充斥着重复、几乎被遗忘的脚本的文件夹只会产生噪音,而非实用工具。将两个被用于相同任务的脚本合并。删除那些数月未运行的脚本,因为如果删除操作出错,git历史记录仍会保留它们。这其实也是不错的生活建议,但在面对家中的“杂物抽屉”时却总被忽视。
其他工具中的相同理念
Claude Code并非唯一具备此功能的工具。即便你在不同工具之间切换,这些理念依然会随之传递,即便文件格式不同也是如此。
GitHub Copilot 和 VS Code将这类工具称为自定义智能体,它们以.agent.md文件的形式存储,通常位于.github/agents/目录下,用户也可以选择将其存放在个人目录中的专用库中。与Claude相比,这类智能体的前置信息包含更多字段:除了之前的name、description和tools之外,还新增了argument-hint、model、user-invocable、disable-model-invocation,最棒的是还有handoffs功能。完成任务的智能体可以显示一个按钮,将工作交给另一个智能体,并自动填入下一个提示词,从而实现规划、执行和审核的紧密衔接,而不会让所有步骤混杂在同一个智能体中。 如果参考旧版本示例,就会遇到一个问题:原来的infer字段被拆分成了两个独立的字段,即user-invocable和disable-model-invocation,从后看这种改动很合理,但最初会让人感到困惑。
另一个常见的说法是VS Code能够识别.claude/agents/目录下的文件并自动转换工具名称,这样同一个智能体就可以免费同时使用多种工具。虽然这确实有一定道理,但容易产生误解——因为不同工具的词汇表并不一致,而且像这里所有成熟的智能体那样通过工具名称来限制使用的提示语,也无法在自动翻译过程中完好无损地传递。
实际上采用的是一个标准提示语,再加上针对每种工具的简易本地适配层。该包中整个Copilot端的智能体代码仅有11行:
---
name: Web Research Writer
description: "Use for bounded web research that must read a local schema, search and fetch current sources, write one designated result file, and validate it with a local command."
tools: [read, search, web, edit, execute]
user-invocable: false
disable-model-invocation: false
---
Load the installed canonical research prompt from the first existing candidate:
1. `.github/agents/web-search-agent.agent.md`
2. `.claude/agents/web-search-agent.md`
Follow its search budgets, module routing, source standards, tool discipline, output, and validation rules. Ignore that file's incompatible `tools` frontmatter; this wrapper's Copilot-native tool categories govern this session.
这就是完整的模式。原生 frontmatter 允许主机正确注册代理,路径层级结构用于定位真实的提示词,而一句简短的句子则指定哪种工具的词汇表具有优先权。所有188行累积的行为规则都存储在同一个文件中,第二个工具只能获得指向而非独立的副本。同一个提示词出现两次就会形成两个独立的提示词,一旦其中一个得到修复而另一个没有,它们就会开始误标自己为同一个代理。
该项目的 README 中指出了一个需要注意的问题:如果将加载器指向 .github/agents/,那么标准提示词就会作为独立的代理被注册,从而导致 Copilot 显示重复的名称——即“Web Research Writer”这个临时脚本,以及其下对应的标准文件。建议为每个工具选择一个唯一的存储位置,或者将标准文件保留在 .claude/agents/ 目录下,这样 Copilot 的自动检测就会忽略它。
其他所有内容,包括光标和帆板功能,都归类在规则、模式或代理等标签下,最终指向同一个目标。相关细节变化频繁,与其笼统地说上个月有什么改动,不如直接引导你查看各工具当前的文档。核心思路始终未变:即创建一个可重复使用的文件来定义特定角色,并有针对性地调用它。只需掌握这一点,剩下的就是找出每个工具存放其文件夹的位置。
优质工具的真正构建方式
这里的每个子代理最初都设计得过于宽泛。有个“审核员”负责审核所有内容,有个“规划师”却对计划应具备的形式毫无见解,还有个“调查员”处理每个漏洞时都沿用之前的方法。这并非失败,而是起点。
真正有用的部分,也就是一年前就能派上用场的部分,并非“迭代”本身。大家都在强调迭代,但迭代其实正是由这些要素构成的。
重新阅读上面引用的代理文件,注意那些文字出现在何处:一段描述角色的内容,一页列出约束条件的内容,一份写明关键字段名称的架构说明,一条用于检测的机械规则,一个带有防止作弊条款的数值预算,一条要求必须公开有偿获取信息的备注,以及一条说明安装程序如何处理异常情况的禁令。几乎所有内容都在记录某个特定时间点出现的错误。
在经历一次令人失望的表现后,关键问题不在于如何更好地描述该智能体的功能,而应探究它究竟做了什么、文件中的哪部分内容允许它这么做,以及下次哪些规则会排除它的使用。上百个浏览器标签页,十个无错误的域外结果,一条找不到答案的指令,一次声称已完成却从未真正完成的验证——所有这些都会成为记录中的一行,而这一行会比生成它的会话更长久存在,这正是将其保存在文件中的意义所在。
在保留某个智能体之前,与保留某个技能之前需要进行的测试是相同的:能否用一句话概括它的功能?如果不能,说明其定义过于宽泛,相当于让两个较小的智能体共用同一件风衣。要判断它是否真正优秀,则需要另一项测试:当它出错时,能否指出导致错误的那一行代码?如果无法做到,那就还不存在真正的专家,只有带有前置信息的普通模型而已。
将它们结合起来,就无需再反复向那位能力出众的通才发起请求。你可以保留一小群已经了解你的领域及自身工作的专家。对于那些希望完成比单人合理工作量更多任务的人来说,这几乎等同于无需真正雇佣他人就能获得帮助。
看看那些你希望由其他地方完成的工作——那便是仍在等待被激活的子代理。而上次某个子代理让你失望,那只是因为还有某行代码有待编写而已。
相关阅读
- 实用笔记:开发个人智能代理的分步指南 — 该指南的详细操作流程说明,包括适用于采用该模式的团队的合约、校验规则以及可直接插入的代码片段。
- 实用笔记:Qwen3.8–27B作为SGLang/vLLM框架下的新首选编码智能体 — 该指南的详细操作流程说明,包括适用于采用该模式的团队的合约、校验规则以及可直接插入的代码片段。