AI 协作工程(四):从执行链到治理链

本文承接前面对 Prompt、Coding Agent Anatomy 与 Context Engineering 的讨论,但将视角从执行机制进一步上移到协作治理层。前面分别解决了三个问题:模型行为如何被 prompt 影响、agent 如何通过 tools 与 loop 完成执行闭环,以及在复杂任务中,spec、context 与 workflow 为什么不可或缺。

在此基础上,一个更现实的问题开始出现:当 agent 已具备较强执行能力时,开发者应赋予它多少自主权?又该如何在放权与控制之间建立边界?因此,本文不再讨论“如何让 agent 做事”,而是讨论:如何让 agent 在可控范围内做事

全文将按照以下路径展开,先说明问题为何会从执行链(execution chain)转向管理链(governance chain);紧接着讨论 agent 的自治等级为何必须与任务风险进行匹配;再拆解 human-in-the-loop 的具体作用及几种典型的人机协作模式;最后引出 agent manager 这一新的工作方式,作为协作结构在工程中的落地形态。

引言

如果把这个系列前几篇连起来看,主题其实是在一层一层向前推进。

  • 第一篇讨论的是输入设计,也就是 prompt 为什么会影响模型行为
  • 第二篇讨论的是执行结构,也就是 agent 为什么不只是一次回答,而是一套 判断 -> 执行 -> 回调 -> 再判断 的闭环
  • 第三篇则进入复杂工作流,关注 当 agent 进入真实代码库之后,为什么上下文、spec 和 workflow 会变成核心问题
  • 到了本文,一个新的问题出现:既然 agent 已经能够承担越来越多的执行任务,那么接下来真正重要的,就不再只是它“能不能做”,而是 “应该让它做多少”

这个变化表面上看像是效率问题,实际上更接近一个 管理问题。过去的讨论往往围绕模型能力展开,例如推理能力的提升、可调用工具的扩展、上下文窗口的增长。但当这些能力真正进入工作流之后,我们首先面对的,往往不再是“它还能不能更强”,而是 “这个任务我能不能放心让它一路执行到底”“在什么节点必须介入确认”“哪些决策必须由人来完成”。也就是说,问题的核心已经从“系统能否执行”,转向了 系统应被授予怎样的行动边界

当 research -> plan -> implement 这条链路已经成立之后,关键问题不再是“能不能持续推进任务”,而是 “链路中的哪些阶段可以委派给 agent,哪些阶段必须由人接管”。例如,agent 已经完成了 research summary 和 draft plan,此时是否可以直接进入代码修改;修改完成之后,是不是可以直接进入 PR,还是必须由人先确认实现范围与潜在风险。这类问题在第三篇中还只是隐含前提,而到了本文,就成了主要问题。因此,本文是在为既有 workflow 补充权限设计与检查点设计

这一变化也在直接重塑开发者的工作方式。过去,工程师更像是直接的实现者,主要精力集中在理解需求、手工编码与修正结果上;而现在,越来越多的工作正在转向另一种形态:组织任务、管理上下文、设定自治等级、设计检查点、审核阶段产物。开发者并没有退出流程,而是从“亲自完成每一步”,转向 “管理整条执行链”。关注点不再只是“如何构建 agent”或“如何使用 agent”,而是在进一步追问 “如何管理 agent”。而本文所要回答的,正是这一层面的问题。

---
title: 从 Workflow 到 Governance 的迁移
---

flowchart LR
    subgraph W3["第三篇:Workflow(执行链)"]
        A["research
信息收集"] --> B["plan
任务规划"] --> C["implement
代码实现"] end subgraph W4["本文:Governance(治理链)"] D["autonomy
自治程度"] --> E["checkpoint
检查点"] --> F["review
结果审核"] end C --> D

1. 自治等级为什么不是越高越好

当开始讨论 agent 时,很容易把“更高自治”理解成一条天然正确的升级路径:随着模型更强、工具更多、上下文更长,系统理应从辅助模式逐步走向完全负责,把越来越完整的任务交给 agent 自主完成。但这个直觉的问题在于,它默认 所有任务都适合用同一种放权方式推进。而在真实开发环境中,任务的风险、验证难度与返工成本上的差异非常大。

一个直观的对比,当任务只是批量修改注释、修复格式或更新低风险样板代码时,更高自主权通常是可接受的。因为 结果容易检查、错误容易回滚、出错成本较低,即使 agent 出现偏移,也不会造成严重后果。但当任务涉及跨模块改动、架构 trade-off、接口边界变化、权限逻辑、数据一致性或可能产生性能风险时,情况就完全不同。此时真正的风险,不是多做了几步,而是 在错误方向上不断累积工作量,直到工程师在 review 阶段不得不进行一次系统性的高成本修复。

因此,更合理的理解方式并不是“agent 应不应该不断获得更多自由”,而是 “不同任务应匹配不同层级的自主权”

  • 对于低风险、高可验证、低回滚成本的任务,可以给予更高自治;
  • 对于中等风险任务,更适合让 agent 推进到 research、plan 或初稿阶段;
  • 而对于高风险、难验证、涉及关键取舍的任务,则应让 agent 参与但不主导

这里需要的,并不是一条线性的能力升级路径,而是一套 基于任务属性进行分流的机制

从这个角度看,自治等级衡量的并不是“能力有多强”,而是 “针对不同能力的 agent,应当赋予多大权限”。它回答的不是“系统最强能做到什么”,而是 “在当前任务中,系统应该被允许做到什么”。这也是本文的一个关键的判断前提。只有当 autonomy 被理解为一种任务匹配问题,后续关于 checkpoint、review 与协作模式的讨论才有稳定基础。否则,人机协作很容易滑向两个极端:要么 过度保守,几乎不敢放权;要么 过度乐观,把本不适合一次性委派的任务也一路放到底。

2. Human-in-the-loop 是质量把控

一提到 human-in-the-loop,很多人会下意识把它理解成一种保守做法,好像只要还需要人工确认,就说明系统还不够成熟。这种理解在简单任务里也许说得通,但在复杂任务中恰恰相反。引入人工参与,并不是因为系统能力不够,而是因为一旦出错,代价可能非常高。也正因为如此,checkpoint 的意义不是限制 agent,而是防止高自治在高风险任务中演变为高成本的失控执行

一个常见误区,是把 checkpoint 理解成“每一步都要人工审批”。如果真这么设计,系统自然会变得笨重,也没有效率优势。但合理的做法并不是这样。更稳的方式,是只在关键节点进行人工确认,比如:任务理解是否准确、计划与改动范围是否合理、实现方向是否偏离 spec、最终结果是否达到 merge 标准。这些都不是日常干预,而是整条执行链上的关键质量把控点

从工程角度看,这样的设计反而能降低整体成本。因为很多高成本返工,并不是结果完全不可用,而是系统在错误方向上推进了太久。例如,agent 一开始就误解了需求,却连续修改了多个模块,直到最后才发现方向错误。此时损失的不只是时间,还包括人重新建立上下文、评估影响范围、回滚修改以及重新规划的成本。相比之下,如果在最初的理解或计划阶段加入一次低成本确认,往往更加可行。

因此,human-in-the-loop 既不意味着“所有步骤都要人工干预”,也不意味着“只要模型足够强就可以完全取消人工参与”。它本质上是一种工程上的安排:在关键节点引入人工,用最小的介入成本,换取整体流程的稳定性和可控性。换句话说,人工参与不是在降低效率,而是高自治系统能够长期稳定运行的前提

---
title: checkpointed run 与 autonomous run 对比图。
---

flowchart TB

    %% 上:无 checkpoint
    subgraph A["Autonomous Run(无检查点)"]
        direction LR
        A1["research
理解任务"] --> A2["plan
制定方案"] --> A3["implement
修改代码"] --> A4["继续推进
跨模块改动"] --> A5["发现问题
方向错误"] --> A6["大规模返工"] end %% 下:有 checkpoint subgraph B["Checkpointed Run(关键节点确认)"] direction LR B1["research
理解任务"] --> B2["checkpoint
确认理解"] --> B3["plan
制定方案"] --> B4["checkpoint
确认边界"] --> B5["implement
修改代码"] --> B6["review
结果校验"] --> B7["收敛完成"] end

3. 三种典型协作模式:研究伙伴、初稿生产者、执行委派者

人机协作这样说有点抽象,我们可以把它拆成几种具体的协作形态。在开发场景中,可以归纳为三种模式,它们对应不同类型的任务,也对应不同的分工方式。

  • 第一种是 Research Partner(研究助手)。在这种模式下,agent 不直接写代码,它会去读代码、定位文件、梳理调用关系、整理问题背景,或者对比几个可能的修改点。它的价值在于降低理解任务的成本,尤其是在大型项目中,那些耗时但不需要太多判断的前置工作,可以明显加快。在这个阶段,人通常不需要盯着 agent 的每一步操作,而是在结果出来之后判断方向是否靠谱

  • 第二种是 Draft Producer(草稿生产者)。这时 agent 不再只是提供线索,而是开始产出阶段性结果,比如实现初稿、测试初稿、文档初稿,甚至一个 draft PR。它适合那些边界已经比较清楚,但仍然需要人来做最终判断的任务。这里的关键不在于替代工程师,而在于把“从 0 到 1”的搭框架和铺细节提前完成,让人可以直接在更高层面上做取舍和优化。

  • 第三种是 Execution Delegate(执行者)。这是放权程度最高的一种模式,指的是在明确边界内,把一段完整的多步任务交给 agent 自主推进。它适合低风险、容易验证、可以快速回滚的任务,或者那些目标和限制已经写得很清楚的工作。这里真正重要的不是“agent 已经足够强”,而是任务边界足够清晰,关键检查点设置合理

这里需要区分下,Research Partner / Draft Producer / Execution Delegate 描述的是协作模式,也就是 agent 在任务中扮演什么角色;而自治等级描述的是放权的程度,也就是它可以独立推进到哪一步。为了方便讨论:

  • 可以把偏建议和草稿的形态看作 Assistive(辅助)
  • 把多步执行但关键节点需要人工确认的形态看作 Supervised(监督)
  • 把在明确边界内完成整段任务的形态看作 Delegated(委派)

这样的划分不是为了贴标签,而是为了更清楚地讨论 “应该放权到哪一步”

把这三种模式放在一起看,会发现不是所有协作都要从研究阶段一路“升级”到完全委派,也不是自治越高就越好。真正关键的是任务匹配:有些任务更适合先做研究,有些任务适合先出初稿,有些任务才适合整体委派。好的协作方式,不是固定使用某一种模式,而是根据任务选择合适的模式,并配套相应的自治等级

不过,上面讨论的仍然是单个 agent 在任务中的角色。而当一个任务本身就包含研究、规划、实现、测试和修复多个阶段时,问题还会进一步变化:是否还应该让同一个 agent 一路做到底,还是更适合拆成多个 agent,按阶段串联起来执行。也正是在这里,讨论会从单一协作模式,到 multi-agent workflow 与 agent orchestration(协同)

4. 一个最小可行的多 Agent 开发流水线

前面几章讨论了 agent 应该如何放权,接下来当开发任务本身就包含研究、规划、实现、测试和修复多个阶段时,怎样组织多个 agent 才更稳定。对于非常简单的任务,让一个 agent 从头做到尾或许没有问题;但一旦任务接近真实的软件开发场景,把流程拆开,往往比一路放权更可靠

更稳的方式,并不是让一个 agent 同时承担理解问题、制定方案、写代码、跑测试和判断失败原因这些不同职责。而是把这些阶段拆开,由不同 agent 分别负责。这样做并不只是“多用几个 AI”,而是让每个阶段都有清晰的目标、明确的输入输出,以及天然可插入检查点。软件开发也因此不再只是“一个人完成所有步骤”,或者“一个 agent 执行到底”,而更像一条可以被分工、检查和回退的执行链路

一个最小可行的多 agent 开发流水线可以表示为:

1
2
3
4
5
6
7
8
9
10
11
Ticket / Spec

Research Agent

Planning Agent

Coding Agent

Testing Agent

Human Review

在这条链路中:

  • Research Agent 的职责不是写代码,而是先搞清问题:定位相关文件、梳理依赖关系、识别约束和潜在风险,并整理出问题空间的摘要。
  • Planning Agent 接手后,需要把这些信息收敛成可执行方案,例如要改哪些文件、边界在哪里、测试如何准备。
  • 到了 Coding Agent,它面对的就不再是原始需求,而是一份已经被整理过的计划,因此可以更直接进入实现和初版测试编写
  • 最后,Testing Agent 负责运行测试、整理失败信息、判断是否存在回归风险,并在必要时把问题回退到前一阶段继续修正

要让这条链路真正跑起来,关键不在于选择哪个 agent ,而在于阶段之间的交接是否清楚。一个更稳定的做法,是要求每一步都输出可以被下一步直接使用的中间产物。例如:

  • Research Agent 输出相关文件、约束、风险和未决问题;
  • Planning Agent 输出修改方案、文件清单和测试计划;
  • Coding Agent 输出代码变更、实现说明和待验证项;
  • Testing Agent 输出测试结果、失败原因和修复建议。

只有当这些产物被明确写清楚,多 agent 协作才会从“角色划分的想象”,变成真正可执行的流程

这里的核心不是“把开发交给 AI”,而是把开发过程拆成多个可以被 AI 协作推进的阶段。传统开发中,人往往同时承担研究、规划、编码和测试;而在多 agent 工作流中,这些职责被显式拆开,开发者则更多地处在链路之外,负责分派任务、设置检查点以及做最终判断

为了更容易理解这种分工,举一个例子,给现有 API 增加分页参数,并补充分页测试。

  • Research Agent 会先定位 controller、service 和已有测试文件,确认当前接口的取数方式、分页参数应在哪一层透传,以及是否已有类似实现可以复用。
  • Planning Agent 基于这些信息,写出修改方案,例如新增 page 和 pageSize 参数、需要修改的文件列表、以及需要覆盖的正常与边界场景。
  • 随后 Coding Agent 按计划完成代码修改和测试初稿。
  • 最后 Testing Agent 运行测试,检查是否存在问题,比如默认分页值是否生效、越界参数是否被正确处理。如果测试失败,流程不会中断,而是根据问题类型回退到对应阶段继续修正

这个例子说明了多 agent workflow 最关键的一点:每个 agent 并不是自由发挥,而是在消费上一阶段交付的上下文产物。因此,第三篇中讨论的 context engineering 并没有消失,而是进一步演变成一个新的问题:上下文如何被维护、如何在阶段之间传递、以及如何在传递过程中不失真。上一阶段关注的是“如何准备上下文”,而到了这里,问题变成了:谁来负责这些上下文,以及如何把它可靠地交到下一个 agent 手中

从实践角度看,这还会引出一个很重要问题:并不是所有任务都值得拆成多 agent pipeline。更合理的做法通常是根据任务复杂度、风险和验证成本来选择协作结构。低风险、影响范围小、结果容易验证的小任务,单 agent 往往已经足够;中等复杂度、涉及多个文件但边界仍然清晰的任务,更适合单 agent 配合关键 checkpoint;而那些天然分阶段、持续时间更长、涉及 research、planning、implementation 和 testing 多轮往返的任务,才真正值得拆成多 agent workflow。

同样,回流规则也需要尽量明确,而不是测试一失败就“全部重来”。更清晰的做法是:实现错误回退到 Coding Agent,边界问题回退到 Planning Agent,问题定义不稳定则回退到 Research Agent 或直接交还给人判断。只有当这些回流路径被事先设计清楚,多 agent workflow 才会变成真正可运行的工程流。

---
title: 最小可行多 Agent 开发流水线图
---

flowchart LR
    A["Ticket / Spec"] --> B["Research Agent"]
    B --> C["Research Summary
files / constraints / risks"] C --> D["Planning Agent"] D --> E["Plan
change list / test plan"] E --> F["Coding Agent"] F --> G["Code Diff
tests / notes"] G --> H["Testing Agent"] H --> I{"Tests Pass?"} I -- "No" --> F I -- "Yes" --> J["Human Review"] J --> K["Merge"]

5. Agent orchestration (智能体协同)真正解决了什么

为什么这种拆分在工程上更有价值,而不仅仅是“更自动化”。agent orchestration 真正解决的,不是表面的并行或流程感,而是如何把原本混在一起的开发活动,拆成一组更可管理的阶段。研究、规划、实现、测试,本质上是不同类型的认知任务。如果把它们压在同一个 agent 里,很容易出现一种情况:一边理解问题,一边写方案,一边改代码,同时还在为自己的路径辩护,结果就是错误假设一路被带到最后。

多 agent 编排首先带来的,是职责的显式拆分。研究阶段关注信息是否完整,规划阶段关注边界是否清晰,实现阶段关注落地是否正确,测试阶段关注结果是否可靠。拆开之后,每个阶段的目标都会更单纯,产出的内容也更容易被检查。这样一来,人不再只面对一个最终结果,而是可以在研究摘要、执行方案、代码初稿、测试结果这些节点上有选择地介入

第二个变化,是减少上下文被“带偏”。同一个 agent 从头做到尾,看起来信息最完整,但也最容易把早期的假设、偏好甚至错误一路延续下去。研究阶段的猜测可能影响后续方案,方案中的倾向又可能影响测试判断,最后变成一条不断自我强化的路径。通过阶段拆分,可以在每一站更多地围绕当前产物做判断,而不是被之前的思路牵着走

第三个变化,是让回退变得可控。单 agent 流程一旦走偏,通常只能在最后整体推倒重来;而在多 agent 流程中,问题更容易被定位到具体阶段。例如测试不通过时,如果只是实现错误,可以直接回退到 Coding Agent;如果是边界设计问题,则需要回到 Planning Agent;如果连问题本身都不稳定,甚至要退回到 Research Agent 或重新交由人判断。也就是说,编排的意义不在于避免错误,而在于让错误更早暴露,并且可以被定向修正

从实践角度看,这也意味着不是所有任务都值得拆成多 agent 流程。低风险、影响范围小、结果容易验证的任务,单 agent 往往已经足够;中等复杂度的任务,更适合单 agent 配合关键检查点;而那些天然分阶段、持续时间较长、需要多轮往返的任务,才真正适合拆成多 agent workflow。

同样,回流规则也需要尽量明确,而不是测试一失败就“全部重来”。更清晰的做法是:实现错误回退到 Coding,边界问题回退到 Planning,问题定义不稳定则回退到 Research 或交还给人判断。只有把这些路径提前设计好,agent orchestration 才会从一组角色划分,变成一条真正可运行的工程流程。

6. 一个案例,如何使用多 Agent 工作流

这个案例是一个很轻量的 developer’s command center 原型。仓库里有两类核心对象:NotesAction Items。前者用来记录标题和正文,后者用来记录待办事项和完成状态。技术栈也比较简单:FastAPI、SQLite、静态前端和 pytest。

这个案例里预留了一组待完善任务:

  1. 为 notes 增加搜索能力
  2. 完善 action item 完成流程
  3. 增强提取逻辑
  4. 补齐 notes 的 CRUD
  5. 增加请求校验和错误处理
  6. 建立文档一致性校验机制

下面以 任务 1:为 notes 增加搜索能力 为例,说明在一个真实环境中,人和 Agent 各自应该做什么,以及为什么这套流程比“直接让 AI 写代码”更好。

这类任务之所以适合作为案例,是因为它天然包含了一条完整链路:先定义接口边界,再补测试,再改后端,再接前端,最后验证和同步文档。它不是一句 prompt 就能结束的小改动,而是一个需要拆解、编排、检查和沉淀的工程任务。

先看人类工程师要做什么。在这个任务里,人最重要的工作不是直接写代码,而是先把任务边界固定下来。比如这次先明确了:

1
2
3
4
5
6
7
- `q` 为空时返回全部 notes
- 前端使用搜索按钮触发,不做输入即搜
- 搜索标题和正文两个字段
- 搜索结果顺序保持和列表接口一致
- 搜索后不刷新下方 notes 列表
- 搜索结果需要直接提示给用户
- 搜索结果除了弹框,还要在页面上显示一条状态提示

这些都需要写进 README.md,变成了后续实现必须遵守的边界。人类工程师真正负责的是三件事:定义目标、定义边界、定义验收标准。如果这一步不做,后面的 Agent 就只能边猜边写,最后做出来的东西很容易和需求跑偏。

边界清楚之后,第一步交给 TestAgent。它的任务是把前面已经定下来的要求翻译成可执行标准。这个任务里,测试明确覆盖了三件关键事:第一,搜索必须大小写不敏感;第二,标题和正文都要能命中;第三,空查询要返回全部 notes。对应代码如下:

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
def test_create_and_list_notes(client):
payload = {"title": "Test", "content": "Hello world"}
r = client.post("/notes/", json=payload)
assert r.status_code == 201, r.text
data = r.json()
assert data["title"] == "Test"

r = client.get("/notes/")
assert r.status_code == 200
items = r.json()
assert len(items) >= 1


def test_search_notes_is_case_insensitive_and_checks_title_and_content(client):
notes = [
{"title": "Sprint Plan", "content": "Prepare Release notes"},
{"title": "Backend", "content": "hello from API"},
{"title": "Unrelated", "content": "Nothing to match here"},
]

for payload in notes:
r = client.post("/notes/", json=payload)
assert r.status_code == 201, r.text

r = client.get("/notes/search/", params={"q": "release"})
assert r.status_code == 200
items = r.json()
assert [item["title"] for item in items] == ["Sprint Plan"]

r = client.get("/notes/search/", params={"q": "HELLO"})
assert r.status_code == 200
items = r.json()
assert [item["title"] for item in items] == ["Backend"]


def test_search_notes_with_empty_query_returns_all_notes(client):
notes = [
{"title": "First", "content": "Alpha"},
{"title": "Second", "content": "Beta"},
]

for payload in notes:
r = client.post("/notes/", json=payload)
assert r.status_code == 201, r.text

r = client.get("/notes/")
assert r.status_code == 200
all_notes = r.json()

r = client.get("/notes/search/", params={"q": ""})
assert r.status_code == 200
searched_notes = r.json()

assert [item["id"] for item in searched_notes] == [item["id"] for item in all_notes]

这一步涉及的知识点很明确:接口行为建模、测试用例设计、边界条件覆盖。也就是说,TestAgent 在这里扮演的不是“辅助写代码”的角色,而是先把正确行为写死。只有先把“什么叫完成”写成测试,后面的实现才有清晰目标。

有了测试之后,再交给 CodeAgent 实现功能。后端部分在 backend/app/routers/notes.py,核心改动如下:

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
32
33
34
35
36
37
38
39
40
41
@router.get("/", response_model=list[NoteRead])
def list_notes(db: Session = Depends(get_db)) -> list[NoteRead]:
rows = db.execute(select(Note)).scalars().all()
return [NoteRead.model_validate(row) for row in rows]


@router.post("/", response_model=NoteRead, status_code=201)
def create_note(payload: NoteCreate, db: Session = Depends(get_db)) -> NoteRead:
note = Note(title=payload.title, content=payload.content)
db.add(note)
db.flush()
db.refresh(note)
return NoteRead.model_validate(note)


@router.get("/search/", response_model=list[NoteRead])
def search_notes(q: Optional[str] = None, db: Session = Depends(get_db)) -> list[NoteRead]:
if not q:
rows = db.execute(select(Note)).scalars().all()
else:
rows = (
db.execute(
select(Note).where(
or_(
Note.title.ilike(f"%{q}%"),
Note.content.ilike(f"%{q}%"),
)
)
)
.scalars()
.all()
)
return [NoteRead.model_validate(row) for row in rows]


@router.get("/{note_id}", response_model=NoteRead)
def get_note(note_id: int, db: Session = Depends(get_db)) -> NoteRead:
note = db.get(Note, note_id)
if not note:
raise HTTPException(status_code=404, detail="Note not found")
return NoteRead.model_validate(note)

这里把原来的简单匹配改成了 ilike + title/content 双字段查询,同时保留了 q 为空时返回全部 notes 的逻辑。这里涉及的知识点主要是 FastAPI 路由处理、SQLAlchemy 查询表达式、接口行为与测试对齐

前端部分在 frontend/index.htmlfrontend/app.jsfrontend/styles.css。首先是页面结构,给 Notes 区域补了搜索表单和状态提示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<section>
<h2>Notes</h2>
<form id="note-search-form">
<input id="note-search-query" placeholder="Search notes" />
<button type="submit">Search</button>
</form>
<p id="note-search-status"></p>

<form id="note-form">
<input id="note-title" placeholder="Title" required />
<input id="note-content" placeholder="Content" required />
<button type="submit">Add</button>
</form>

<ul id="notes"></ul>
</section>

然后是前端逻辑。这里的关键点不是简单“把搜索框接上”,而是要满足前面定义好的边界:搜索后只提示结果,不刷新下方原始列表。对应实现如下:

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
async function fetchJSON(url, options) {
const res = await fetch(url, options);
if (!res.ok) throw new Error(await res.text());
return res.json();
}

function formatSearchResults(query, notes) {
if (notes.length === 0) return `No notes matched "${query}".`;
return [`Search results for "${query}":`, ...notes.map((n) => `- ${n.title}: ${n.content}`)].join('\n');
}

function setSearchStatus(message) {
const status = document.getElementById('note-search-status');
status.textContent = message;
}

async function loadNotes() {
const list = document.getElementById('notes');
list.innerHTML = '';
const notes = await fetchJSON('/notes/');
for (const n of notes) {
const li = document.createElement('li');
li.textContent = `${n.title}: ${n.content}`;
list.appendChild(li);
}
}

window.addEventListener('DOMContentLoaded', () => {
document.getElementById('note-search-form').addEventListener('submit', async (e) => {
e.preventDefault();
try {
const query = document.getElementById('note-search-query').value.trim();
const search = new URLSearchParams();
if (query !== '') search.set('q', query);
const url = search.size ? `/notes/search/?${search.toString()}` : '/notes/search/';
const notes = await fetchJSON(url);

setSearchStatus(`Found ${notes.length} result(s) for "${query || 'all notes'}".`);
window.alert(formatSearchResults(query || 'all notes', notes));
} catch (error) {
setSearchStatus('Search failed.');
window.alert(`Search failed: ${error.message}`);
}
});

document.getElementById('note-form').addEventListener('submit', async (e) => {
e.preventDefault();
const title = document.getElementById('note-title').value;
const content = document.getElementById('note-content').value;
await fetchJSON('/notes/', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title, content }),
});
e.target.reset();
loadNotes();
});

loadNotes();
setSearchStatus('');
});

这里涉及的知识点主要是 表单交互、前后端请求联动、最小可用 UI 改动。也可以看出 CodeAgent 在这里不是从零猜测怎么做,而是围绕测试和边界去把后端和前端都补齐。

样式上也做了最小增强,让搜索区域更明显,而不是混在新增 note 的表单里不容易看出来:

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
#note-search-form {
padding: .75rem;
border: 1px solid #d7e3f4;
border-radius: 6px;
background: #f3f8ff;
margin-bottom: .75rem;
}

#note-search-query {
border-color: #8bb2e8;
background: #fff;
}

#note-search-form button {
border-color: #2f6fda;
background: #2f6fda;
color: #fff;
font-weight: 600;
}

#note-search-status {
min-height: 1.25rem;
margin: .25rem 0 .75rem;
color: #2f6fda;
font-size: .95rem;
}

实现完成以后,还要做两件事。第一,重新运行测试,确认功能真的符合定义好的边界。这次实际运行的是:

1
PYTHONPATH=. python3 -m pytest -q backend/tests/test_notes.py

结果是:

1
3 passed in 0.04s

第二,把任务结果同步回文档。在 README.md 里把任务 1 标成 [Done],并写清楚“当前决策”和“完成情况”;同时在docs/API.md里同步 notes search 的接口行为。新增的接口说明大致是这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
### `GET /notes/search/`

作用:
- 按关键词搜索 notes

查询参数:
- `q`:可选关键词

当前行为:
- `q` 为空时返回全部 notes
- 搜索范围是标题和正文
- 搜索大小写不敏感

成功响应json:
[
{
"id": 1,
"title": "Example",
"content": "Hello world"
}
]

这里涉及的知识点不是写代码本身,而是 验证闭环文档同步。如果没有这一步,任务虽然“做完了”,但仓库本身不会留下稳定记录,后面的人还是得重新翻聊天记录才能知道到底发生了什么。

如果把这次 notes search 的流程压缩一下,其实就是:

1
2
3
4
5
6
7
8
9
10
11
Human boundary

TestAgent 先补搜索测试

CodeAgent 修改后端搜索与前端交互

Run pytest 验证结果

Docs sync 更新 README / API 文档

Human review 确认行为与边界一致

这个案例最值得强调的一点是:多 Agent 工作流的价值,不在于让 AI 一次性写出多少代码,而在于把原本要靠人来回切换的开发过程,整理成一条可重复执行的工程链路。 如果没有这套流程,开发者往往得自己在几个角色之间来回切换:先想需求,再写测试,再改后端,再改前端,再跑验证,再补文档。每一步都可能遗漏,也很难复用。现在这条链路把角色分清楚了:

  • 人负责定义边界和最终验收
  • TestAgent 负责把边界翻译成测试
  • CodeAgent 负责围绕测试实现功能
  • 最后再把结果沉淀进文档和仓库状态。

结果不是人退出流程,而是人从重复执行者变成边界定义者和质量把关者。

后面的任务,比如 action item 完成流程、提取逻辑、notes CRUD、请求校验和文档一致性校验,也都可以继续按同样方式推进:先定义边界,再补测试,再做实现,再同步文档,最后人工验收。而且随着任务继续推进,这条 workflow 也不会一成不变,完全可以一边做功能、一边发现问题,再回头补强流程本身,让这套协作方式越来越稳。

---
title: 多 Agent 实践案例
---

flowchart LR
    A["新增 API 路由需求"] --> B["README.md& AGENT.md
仓库上下文 / 规则"] B --> C["TestAgent
写失败测试"] C --> D["CodeAgent
实现路由"] D --> E["TestAgent
复验测试"] E --> F["/docs-sync
同步 API 文档"] F --> G["Human Review
决定 merge"]

7. 高 ROI 的 Agent 用法:先研究、再起草、再审查

如果只从表面功能来看各类 coding agent,很容易把它们当成更强的终端助手,或者能读文件、改代码、跑命令的 AI 工具。但更值得关注的,其实不是它们“能做什么”,而是它们在整个工作流中适合放在什么位置

从这个角度看,这类工具的高价值用法,往往不在于替我们把任务一口气做完,而在于降低研究、切入和形成初稿的成本

一个更自然的使用方式,是把 agent 放在任务的前半段。拿到 ticket 或 spec 之后,先让它帮助定位相关文件、梳理调用关系、找出潜在修改点、整理已有实现的关键约束。在这个阶段,它更像一个研究助手,作用是帮你更快建立对问题空间的第一轮理解。相比在终端、IDE、文档之间反复切换,这种方式可以显著减少上下文切换成本,让问题更快被看清。

在此基础上,再让它往前走一步,用来产出初稿。当边界已经基本清楚时,可以让它先写实现初稿、测试初稿、文档初稿,甚至整理出一版 draft PR。这里的关键不在于初稿是否完美,而在于把“从 0 到 1”的启动成本提前消化掉。开发者接手时,面对的不是空白页,而是一个已经可以讨论、可以审查、可以修改的对象,精力也可以更集中地放在结构、取舍和质量上。

从更高层看,这类工具的价值还在于把零散的上下文操作收拢成一条连续的执行链。在真实开发中,很多时间并不花在编码本身,而是消耗在找文件、看日志、来回切换工具、重新回忆上下文这些碎片化操作上。如果这些动作可以被整合进一条更连贯的流程,那么带来的提升并不是某个单点功能,而是整体推进效率的提升

因此,更深刻的理解方式不是“某个工具更强”,而是:不同 agent(如 Claude Code、Codex 等)只是这一协作模式的不同实现。真正稳定的,是这条流程本身。

---
title: Coding Agent 在完整工作流中的位置
---
flowchart LR
    A["Ticket / Spec"] --> B["Research
理解问题空间"] B --> C["Draft Plan
形成执行路径"] C --> D["Draft PR / 初稿
实现 + 测试 + 文档"] D --> E["Human Review
架构判断 + 风险控制"] E --> F["Merge"]

8. 工程师为什么正在变成 Agent Manager

如果只看表面现象,很容易把 AI 编码理解成“工程师写得更少,模型写得更多”。这种说法并不完全错误,但过于粗糙,无法解释工程师的工作内容为什么正在发生变化。更贴近现实的描述是:工程师正在逐渐转变为“执行链的管理者”。变化不在于还写不写代码,而在于最有价值的工作,正在从亲自完成细节,转向管理整条任务执行过程

这种转变体现在多个方面。首先,工程师需要决定任务如何拆分,哪些部分适合交给 agent,哪些必须自己完成。其次,需要判断该放多少权,也就是在不同任务中选择更偏辅助、监督推进,还是阶段性委派。再者,需要设计关键检查点,决定 agent 在哪些节点必须停下来交由人确认。同时,还需要持续维护上下文的清晰度和准确性,避免系统在错误假设或模糊目标下偏离方向。最后,无论自动化程度多高,质量判断、关键取舍和最终合并决策仍然必须由人来承担

也就是说,开发者并没有因为 agent 的出现而被替代。真正发生变化的是工作重心:过去的价值更多体现在手工实现细节,而现在越来越多地体现在目标定义是否清晰、上下文是否组织得当、边界是否控制住、风险是否被识别、质量是否被守住。这不是一种能力被削弱,而是关注点整体上移

这种变化并不是突然出现的,而是前面几篇内容自然叠加的结果。prompt 解决的是如何表达任务,tool loop 解决的是如何执行任务,context 和 spec 解决的是如何让任务稳定推进,而到了这一阶段,问题进一步变成:谁来控制这条执行链,以及如何在这条链上分配权限与责任

从这个角度看,agent manager 并不是一个新造的职位标签,而是一种正在发生的工作方式。只要 agent 还在承担部分执行任务,人类就必然要承担更高层的组织和判断职责。真正的问题,不是未来是否会出现这种角色,而是工程师是否已经在用这种方式工作,却还没有意识到这一点

9. 总结

把前三篇和本文放在一起看,会发现一个很清晰的变化:AI 协作工程的重心,正在从“如何让系统执行”,转向“如何管理执行、如何组织执行”。第一篇解决的是任务如何表达,第二篇解决的是系统如何开始执行,第三篇解决的是复杂任务如何稳定推进,而到了本文,问题进一步深入:当系统已经可以持续推进任务时,谁来决定它该走到哪一步,在哪些地方必须停下来,哪些任务不应该一次性放权,以及当任务本身具有多个阶段时,是否应该拆分为多 agent 协作

也正因为如此,本文讨论的重点,从“agent 是什么、怎么用”,转向了 “agent 应该如何被管理,以及如何被组织起来协同工作”。这背后的变化是整个协作系统开始需要权限划分、检查点设计、职责分配以及阶段之间的交接机制。一个再强的 agent,如果放权边界模糊、缺少关键检查点、任务选择不当,最终只会更快放大错误;反过来,即使 agent 并不完美,只要自治程度、检查点、协作方式以及多 agent 流程被设计得足够清楚,它依然可以成为稳定且高价值的工作组件。

从开发者的角度看,这也意味着能力重点正在变化。过去更强调 prompt 写法,后来转向工具使用和上下文组织,而现在则越来越需要掌握另一种能力:设计治理结构与编排结构。也就是要能够判断哪些任务适合辅助完成,哪些适合在监督下推进,哪些适合阶段性委派;要知道哪些节点必须由人确认;要能够选择在什么情况下使用单 agent,什么情况下拆成 Research -> Planning -> Coding -> Testing 这样的多 agent 流程;更重要的是,要让 agent 成为一个被清晰管理、被合理组织的执行单元,而不是一个被无条件信任的黑盒。

顺着这条路径继续往下看,测试、安全、权限控制、代码评审、团队协作乃至组织级流程,都会自然进入视野。因为这些问题本来就不是“AI 之外的工程问题”,而是当 agent 协作开始规模化之后,不可回避的治理问题

10.备注

本文部分观点基于公开资料整理与个人实践总结,如有引用不准确之处欢迎指正。

参考材料: