从零实现 Agent Harness 系列 · 第 09 篇
这一篇做一次阶段性架构回看。
前八篇讲的是 Agent Harness 如何跑起来;这一篇讲的是它要走向产品级,还需要补上哪些工程闭环。
真正可用的 Agent,不只是能调用工具、能完成任务,还要能隔离执行、恢复失败、控制权限、记录状态,并让用户放心交给它长期运行。
前言
到第八篇为止,这个系列已经把一个教学版 Agent Harness 的主线基本走完了。
前八篇分别解决的是这些问题:
| 篇 | 主题 | 解决的问题 |
|---|---|---|
| 01 | Loop 与工具调用 | Agent 怎么从"想"变成"做"? |
| 02 | Plan / TodoWrite | 复杂任务怎么变得可见、可控、可恢复? |
| 03 | Hooks 与权限 | 工具执行怎么拦截、确认和审计? |
| 04 | Skills | 专业能力怎么按需加载? |
| 05 | Context / Memory | 上下文快满了怎么办? |
| 06 | 长任务系统 | 跨轮、后台、定时任务怎么托管? |
| 07 | Multi-Agent | 多个 Agent 怎么分工并收敛? |
| 08 | MCP | 外部能力怎么标准化接入? |
这些内容连起来,回答的是:
text
Agent Harness 的关键机制是什么
也就是:
- Agent Loop 到底是什么
- 工具怎么接
- Plan 为什么重要
- 长上下文怎么处理
- MCP 怎么接进来
这些问题解决的是:
text
Agent 为什么能工作
这一篇不再新增一个单点组件。
它要做的是阶段性回看:把前八篇的机制放到一个更完整的产品级架构里,看看真正可用的 Agent 还差哪些工程层。
因为产品级 Agent 关注的,往往是另一组问题:
- 出错以后怎么办
- 怎么防止误操作
- 多个任务怎么隔离
- 提示词怎么分层
- 状态怎么恢复
- 怎么观测和审计
也就是说,教学版强调的是:
text
机制清楚
产品级更强调的是:
text
系统可靠
所以这篇文章想回答的问题是:
从"一个能讲明白原理的 Agent Harness",到"一个真正能放心交给用户的 Agent",中间还需要补哪些层?
这也是这一篇的定位:
text
前八篇讲 Agent Harness 的机制
这一篇讲产品级 Agent 的工程闭环
一、System Prompt:从一段说明书到运行时上下文骨架
很多教学版代码里,system prompt 都比较短。
这很好,因为主线清楚。
但一到真实系统,prompt 通常要开始分层。
1. 稳定规则层
比如:
- 你是谁
- 你的边界是什么
- 哪些事必须遵守
- 哪些行为永远不能做
这一层最好稳定,不应该每一轮都被临时状态挤来挤去。
2. 动态能力索引层
比如:
- 当前有哪些 skills
- 当前有哪些 tools
- 当前连接了哪些 MCP server
- 当前哪些能力需要审批
这一层会随着运行时变化。
它不是"世界知识",而是 Agent 当前能使用的能力菜单。
3. 运行时状态层
比如:
- 当前任务目标
- 当前 todo
- 当前约束
- 当前环境信息
- 最近一次失败和恢复策略
这一层最容易变化,也最容易膨胀。
如果这三层全糊在一起,prompt 会越来越难维护。更麻烦的是,一旦系统出了问题,你很难判断到底是规则不清、能力暴露错了,还是状态注入乱了。
所以产品级 Agent 很重要的一步,是把 prompt 从"一整段文字"升级成"运行时装配出来的上下文骨架"。
可以把它理解成:
text
System Prompt = 稳定规则 + 动态能力 + 运行时状态
这不是为了把 prompt 写得更长,而是为了让每一部分都有清楚的来源、生命周期和优先级。
4. 现实产品里的例子:Claude Code 和 Codex
这里可以看两个真实产品的公开设计。
注意,我们不是在说它们内部真正的 system prompt 长什么样。那通常不是公开信息。
但从公开文档里,已经能看到一个很清楚的趋势:
text
产品级 Agent 不会只靠一段 system prompt 管住所有行为
而是把规则、项目上下文、工具能力、权限策略、运行环境分层管理
Claude Code:把项目上下文和系统约束拆开
先看 Claude Code。
Claude Code 里有 CLAUDE.md、memory、skills、hooks、permissions 这些机制。它们看起来都在"影响 Agent 行为",但位置并不一样。
可以用一个具体例子理解。
假设一个项目里希望 Agent 遵守这些规则:
- 这个仓库用
pnpm,不要用npm - 修改后要跑
pnpm test - 不要直接提交 commit
- 不允许执行
rm -rf这类危险命令 - 遇到数据库迁移要先让用户确认
如果全部写进 system prompt,大概会变成一段越来越长的说明书:
text
你在这个项目里工作。
请使用 pnpm。
改完请运行 pnpm test。
不要提交 commit。
不要执行危险删除命令。
数据库迁移前请确认。
......
这能起作用,但问题是:它把三类完全不同的东西混在了一起。
第一类是项目知识。
比如:
text
这个项目用 pnpm
测试命令是 pnpm test
代码风格遵循某个约定
这类信息适合放在 CLAUDE.md 或 memory 里,作为项目级上下文,让模型在推理时知道"这个仓库怎么工作"。
一个简单的 CLAUDE.md 可以这样写:
markdown
# CLAUDE.md
## Project conventions
- Use `pnpm`, not `npm`.
- Run `pnpm test` after changing application code.
- Run `pnpm lint` before summarizing code changes.
- Frontend code lives under `apps/web/`.
- Do not edit `db/migrations/` unless the user explicitly asks.
## Workflow
- Explain any risky change before making it.
- Prefer small focused diffs.
- When tests fail, summarize the failing command and the suspected cause.
它看起来像 prompt,但定位不一样。
CLAUDE.md 是项目上下文:告诉 Claude Code 这个仓库怎么构建、怎么测试、有哪些约定。它会影响模型判断,但它不是强制执行层。
Claude Code 的文档里还区分了不同作用域:
text
~/.claude/CLAUDE.md # 个人偏好,所有项目通用
./CLAUDE.md # 项目级说明,适合提交到仓库
./.claude/CLAUDE.md # 另一种项目级位置
./CLAUDE.local.md # 个人本地说明,通常不提交
在大仓库里,还可以用 .claude/rules/ 拆规则。
比如:
text
.claude/rules/testing.md
.claude/rules/security.md
.claude/rules/frontend/react.md
还可以做路径级规则:
markdown
---
paths:
- "src/api/**/*.ts"
---
# API rules
- All API handlers must validate input.
- Use the standard error response format.
- Include OpenAPI comments for public endpoints.
这就解决了一个很实际的问题:不是所有规则都应该每轮加载,也不是所有规则都适用于整个仓库。
放在 Harness 的视角里,它对应的是:
text
全局偏好 -> user context
项目约定 -> project context
路径规则 -> scoped context
当前任务 -> runtime state
第二类是可复用能力。
比如:
text
怎么做 code review
怎么整理 release note
怎么检查一个 PR
这类更适合做成 skills。模型需要时再加载,不必每一轮都塞进 system prompt。
第三类是硬约束。
比如:
text
不能执行危险命令
某些工具调用必须确认
某些动作要记录日志
这类就不应该只靠 prompt 提醒,而应该交给 hooks 和 permissions。因为它们不是"建议模型怎么做",而是"系统是否允许这么做"。
这说明它的结构大致不是:
text
一个超长 system prompt 解决所有问题
而更像:
text
基础行为规则
+ 项目上下文 CLAUDE.md
+ 自动记忆
+ skills 能力包
+ hooks 生命周期拦截
+ permissions 权限策略
也就是说,Claude Code 把"告诉模型怎么做"和"系统是否允许这么做"分开了。
CLAUDE.md 可以提醒模型项目规范,但如果要硬性阻止某个动作,更适合用 hook 或 permission 这类系统层机制。
这个拆分对我们写 Harness 很有启发:
text
项目约定进 context
可复用流程进 skill
安全边界进 policy / hook
当前任务状态进 runtime state
system prompt 仍然重要,但它不再承担全部责任。
Codex:把项目指令、沙箱和审批拆成不同层
再看 Codex。
Codex 也有类似的分层思路。公开文档里,AGENTS.md 用来给项目提供额外说明和约定;sandbox 和 approval policy 用来限制命令、文件系统、网络访问等执行边界。
还是用一个具体场景。
假设你让 Codex 做这件事:
text
帮我修一下登录页的失败测试,必要时可以改代码,但不要碰数据库迁移。
这件事里面至少有四类信息。
第一类是项目规则。
比如:
text
测试命令是什么
代码风格是什么
哪些目录是前端代码
哪些文件不要随便改
这类适合放在 AGENTS.md 里,让每次进入仓库时都有稳定的项目约定。
一个很小的 AGENTS.md 可能长这样:
markdown
# AGENTS.md
## Repository expectations
- Use `pnpm`, not `npm`.
- Run `pnpm test` after changing application code.
- Run `pnpm lint` before summarizing code changes.
- Do not modify files under `db/migrations/` unless the user explicitly asks.
- Document behavior changes in `docs/`.
这类文件的重点不是"写得像 prompt 一样华丽",而是把仓库里的工作约定稳定放在一个地方。
Codex 的公开文档里还提到,AGENTS.md 是有层级的。
比如:
text
~/.codex/AGENTS.md # 个人全局偏好
repo/AGENTS.md # 仓库级约定
repo/apps/web/AGENTS.md # 前端子目录约定
repo/services/payments/AGENTS.md # 支付服务约定
如果当前任务在 services/payments/ 下面,Codex 可以把全局约定、仓库约定和支付服务的局部约定一起作为项目上下文。
这就比把所有规则都写进一个 system prompt 更自然:
text
通用工作习惯放全局
仓库规则放 repo 根目录
局部模块规则放子目录
当前任务状态由 runtime 注入
第二类是当前任务状态。
比如:
text
这次目标是修登录页测试
已经读过哪些文件
当前失败点是什么
下一步准备跑什么命令
这类属于 runtime state,随着任务推进不断变化。
第三类是可用能力。
比如:
text
能读文件
能改文件
能跑测试
能调用 MCP 工具
能启动 subagent
这类由 tools 和运行时工具池控制,不应该混在项目说明里。
第四类是执行边界。
比如:
text
当前 sandbox 允许写哪些目录
网络能不能访问
哪些命令需要 approval
是否允许跨出当前 workspace
这类由 sandbox 和 approval policy 控制。
这说明 Codex 的运行时也不是只靠 prompt:
text
AGENTS.md:项目规则和上下文
tools:当前可用能力
sandbox:技术边界
approval policy:哪些动作需要确认
runtime state:当前任务、工作区、命令结果、文件变化
换句话说,模型可以"想做一件事",但 runtime 会决定:
- 这个工具是否存在
- 当前环境是否允许
- 是否需要用户批准
- 结果如何回写到上下文
- 文件变化是否能被追踪和恢复
这就是产品级 Agent 和教学版 prompt 最大的差异:
text
教学版把很多规则写进 prompt
产品级把不同性质的规则拆到不同控制面
更具体一点,可以拆成五层:
text
prompt plane:定义 Agent 的角色、边界和基本行为方式
context plane:注入项目说明、历史记忆、当前任务状态
tool plane:决定这一轮模型能看到和调用哪些能力
policy plane:判断哪些动作允许、哪些动作需要审批、哪些动作禁止
state plane:记录任务进度、工具结果、错误现场和文件变化
这样拆开以后,很多事情就不会互相打架。
项目规范变了,改 context 或 AGENTS.md / CLAUDE.md。
新增一个外部系统,改 tool plane。
某类命令要拦截,改 policy。
任务失败以后要恢复,查 state。
system prompt 仍然重要,但它不再是所有规则的垃圾桶。它更像最上层的行为宪法,负责稳定地告诉模型"你是谁、你怎么工作、你的边界是什么"。至于项目知识、工具列表、权限审批、运行状态,都应该交给更适合的系统层去管理。
所以我们在自己写 Harness 时,不需要一上来复刻 Claude Code 或 Codex 的复杂度,但至少要学到这个方向:
产品级 Agent 的关键,不是把 system prompt 写得越来越长,而是把上下文、工具、权限和状态拆成可维护的运行时结构。
二、错误恢复:从返回报错到可恢复执行
教学版里,工具失败时常见的处理方式是:
text
把错误文本返回给模型
让模型自己决定下一步
这在入门阶段完全合理。
但产品级系统还要继续往前走一步:错误恢复不能只靠模型临场发挥。
1. 区分错误类型
不是所有错误都一样。
至少要区分:
- 用户输入问题
- 工具参数问题
- 环境问题
- 权限拒绝
- 临时失败
- 不可恢复失败
比如,同样是工具失败:
- 参数缺字段,可以让模型修正后重试
- 网络超时,可以按策略重试
- 权限拒绝,应该进入审批或终止
- 删除生产数据被拦截,不能让模型换一种方式绕过去
如果系统只返回一段字符串,模型可能看懂了,也可能没看懂。产品级系统不能把所有判断都压在"模型应该会理解"上。
2. 决定恢复策略
不同错误应该对应不同处理:
- 重试
- 换路径
- 请求用户确认
- 回滚
- 降级执行
- 终止当前任务
这里的关键是:恢复策略应该是系统能力,而不只是提示词建议。
比如一个工具声明自己是临时网络失败,runtime 可以允许最多重试两次;如果是权限拒绝,runtime 就应该直接进入审批流,而不是让模型自己猜下一步。
3. 保留恢复现场
如果 Agent 做到一半崩了,系统最好能知道:
- 它做到哪一步
- 哪个工具失败了
- 当时的输入是什么
- 哪些任务已经完成
- 哪些任务还没有完成
这就是为什么真实系统里,"错误恢复"通常不是一个函数,而是一整套状态管理和策略体系。
一个可恢复的 Agent,不只是会说"抱歉我失败了",而是能回答:
text
失败在哪里
为什么失败
还能不能恢复
恢复应该从哪一步继续
4. 现实产品里的例子:审批失败、会话恢复与检查点
这里不要一上来想"怎么让模型更聪明",先看失败现场本身。
假设 Agent 正在执行:
text
修复测试
-> 发现需要改配置
-> 准备写入受保护目录
-> 触发权限限制
教学版 Harness 很可能只是把错误返回给模型:
text
Permission denied
然后让模型自己判断下一步。
问题是,这条错误太薄了。
它没有告诉系统:
- 这是临时失败,还是权限失败?
- 失败发生在哪个任务步骤?
- 是应该重试,还是应该请求审批?
- 如果用户稍后批准,能不能从这里继续?
所以产品级系统里,错误恢复通常分成三层。
第一层是错误分类。
text
错误类型:permission_denied
失败动作:写入受保护目录
当前任务:修复测试
第二层是恢复决策。
text
permission_denied:
-> 如果动作可审批,请求用户确认
-> 如果有安全替代路径,换路径
-> 如果动作被策略禁止,终止当前分支
第三层是现场保留。
text
task_id:当前任务
step_id:失败步骤
tool_name:哪个工具失败
tool_args:当时的参数
error_type:失败类型
recovery_options:可选恢复策略
这样,Agent 不只是知道"失败了",而是知道"失败在哪里,以及从哪里恢复"。
Claude Code 和 Codex 的产品机制,都能看到这种分层思路。
Claude Code 里的 checkpoint、session resume、hooks、permissions,分别对应不同问题:
text
checkpoint:保留可以回看的工作点
session resume:让任务不是一次性对话
hooks:在工具调用前后插入系统逻辑
permissions:把允许/拒绝变成运行时判断
Codex 里的 sandbox 和 approval policy,则更直接地把"能不能做"拆开:
text
sandbox:技术边界,决定动作能不能直接执行
approval policy:交互边界,决定什么时候必须问用户
parent workflow:子任务失败后,把失败结果带回主流程
这就比"把 traceback 交给模型看"稳定得多。
放到自己的 Harness 里,最小实现可以先不用很复杂:
text
tool_result = {
ok: false,
error_type: "permission_denied",
message: "cannot write protected path",
task_id: "fix-tests",
step_id: "update-config",
recovery_options: ["request_approval", "choose_safe_path", "stop"]
}
然后 runtime 做两件事:
- 把失败写进 task/todo 状态。
- 下一轮把"失败类型 + 失败步骤 + 可选恢复策略"交给模型。
这样模型仍然负责推理下一步,但恢复边界由系统提供,而不是全靠模型临场猜。
三、隔离环境:Agent 不能总在同一个工作区里动手
教学版 Harness 通常直接在当前目录里读文件、写文件、跑命令。
这很适合讲原理,因为读者一眼就能看到:
text
模型决定调用工具
工具就在当前目录执行
结果再回到模型
但产品级 Agent 一旦开始处理真实任务,就不能只问"能不能执行",还要问:
text
在哪里执行
影响哪些文件
能不能回滚
会不会和别的任务互相干扰
这就是隔离环境要解决的问题。
1. 隔离的不是目录,而是任务影响范围
先看一个简单场景。
你同时让 Agent 做两件事:
text
任务 A:修复登录页测试
任务 B:探索把状态管理从 Redux 迁到 Zustand
这两个任务都可能改到同一批前端文件。
如果它们都直接在主工作区里执行,很快会出现问题:
- 任务 A 的修复和任务 B 的实验混在一起
- 测试失败时不知道是谁引入的
- 用户想保留修复、丢弃实验,会很麻烦
- 后台 scheduled task 可能污染正在开发的分支
所以隔离环境的第一层含义,不是"换个目录这么简单",而是给每个任务划出自己的影响范围。
一个产品级 Agent 至少要能回答:
text
这个任务在哪里执行?
它改了哪些文件?
这些改动属于哪个 task?
如果失败,能不能丢弃?
如果成功,怎么合回主线?
2. 隔离可以分成几层
不同系统会用不同隔离手段。
大致可以分成四层:
| 隔离方式 | 解决的问题 | 适合场景 |
|---|---|---|
| 临时目录 | 避免临时文件污染主目录 | 数据处理、文件生成 |
| Git worktree | 隔离代码改动和 diff | 代码修复、重构实验、并行任务 |
| 容器 / 沙箱 | 限制命令、网络、依赖和系统资源 | 不可信命令、复杂环境 |
| 只读挂载 / 受控写入 | 控制哪些路径能读写 | 高风险项目、生产配置 |
所以 worktree 不是隔离环境的全部。
它只是代码任务里特别常用、也特别容易理解的一层。
3. worktree 是什么
在 Git 里,一个 repository 默认只有一个工作目录。你切分支、改文件、跑测试,都是在这个目录里发生。
git worktree 允许同一个 Git 仓库同时拥有多个工作目录。
它们共享同一份 Git 历史,但每个目录可以 checkout 到不同分支,有自己独立的文件状态。
可以把它想成:
text
同一个仓库
-> main/ # 用户当前工作目录
-> ../repo-fix-login/ # 修登录测试的工作目录
-> ../repo-zustand-exp/ # 做重构实验的工作目录
这不是简单复制一份仓库。
更准确地说,它是给同一个仓库开了几张独立工作台:
text
Git 历史共享
文件修改隔离
分支可以不同
diff 可以单独看
不想要的工作台可以删掉
放到 Agent 里,worktree 的价值就很直接:
text
一个 task 一个 workspace
一个 workspace 一组改动
一组改动可以被查看、测试、丢弃或合并
4. 现实产品里的例子:Codex worktree
Codex app 的公开文档里,worktree 就是一个明确的产品机制。
它解决的不是 Git 技巧问题,而是 Agent 并行执行的问题。
比如同一个项目里:
text
main workspace:用户当前工作目录
worktree A:Codex 修复登录页测试
worktree B:Codex 做 Zustand 实验
background worktree:定时任务后台运行
这样每条任务线都有独立文件变化。
任务 A 跑测试失败,不会影响任务 B。
任务 B 改得太激进,也不会污染主工作区。
用户可以单独检查任务 A 的 diff,也可以直接丢弃任务 B 的实验 worktree。
对应到 Harness 设计里,关键抽象不是 git worktree 这个命令,而是这条链路:
text
task_id
-> workspace_id
-> execution_boundary
-> diff / artifacts
-> merge_or_discard
也就是说,Agent 不只是有一个"当前目录",而是每个任务都有自己的执行边界。
5. 这和 sandbox、permission 的关系
worktree 解决的是:
text
这次任务的文件改动落在哪里
sandbox / permission 解决的是:
text
这次任务技术上能访问什么、能执行什么
这两者不是替代关系。
更接近产品级的结构通常是:
text
task
-> workspace / worktree # 隔离改动
-> sandbox # 限制环境能力
-> permission policy # 判断是否需要审批
-> audit log # 记录做过什么
所以第三章真正要表达的是:
Agent 不只需要知道"要做什么",还要知道"在哪里做、能改哪里、改动怎么收回来"。
四、权限策略:从轻量拦截到审批与审计系统
教学版里,我们已经通过 Hooks 看到了一个重要方向:
text
权限逻辑不要写死在每个工具里
而要作为横切策略统一挂上去
但产品级通常还要更进一步。
因为真实系统里的权限问题会更复杂:
- 哪些命令可以直接执行
- 哪些操作必须询问用户
- 哪些文件永远不能读
- 哪些外部系统需要额外认证
- 不同用户角色能用哪些工具
- 哪些操作在开发环境允许,在生产环境禁止
所以最终你往往需要的不是几个 if,而是一套更清晰的策略层:
- 策略判断
- 审批入口
- 审计记录
- 结果追踪
也就是说:
text
Agent 不是"能做什么都做"
而是"能做的事也要在策略边界内做"
这里还有一个容易被忽略的点:审批不是打断体验的补丁,而是产品级 Agent 的交互协议。
用户真正需要的不是"每一步都问我",而是:
- 小风险自动执行
- 中风险清楚说明后确认
- 高风险直接拦截
- 所有关键动作可回看
这就要求权限系统和工具元数据、运行环境、用户身份、审计日志一起工作。
现实产品里的例子:hooks、permissions、sandbox 和 approval
假设你希望 Agent 可以自动读文件、跑测试,但遇到下面这些动作必须停下来:
- 删除文件
- 修改
.env - 执行数据库迁移
- 调用生产环境部署工具
- 访问外网
如果只写进 prompt:
text
请不要做危险操作,必要时先问我。
它仍然只是一个行为建议。
Claude Code 的 hooks 很适合理解产品级做法。
它可以在工具调用前后、权限请求、MCP 工具调用等生命周期节点执行用户定义的逻辑。也就是说,权限不只是写在 prompt 里的"请小心",而是可以变成运行时拦截点。
例如:
text
PreToolUse hook:工具调用前检查
PermissionRequest hook:权限弹窗前决定是否允许
PostToolUse hook:工具调用后记录或处理结果
Codex 也把这件事拆成两层:
- sandbox 决定技术上能不能访问文件、网络、命令
- approval policy 决定什么时候必须停下来问用户
比如同一个 curl 命令,在不同配置下可能是:
text
network disabled:技术上直接不允许
network enabled + approval:先问用户
trusted domain:允许执行
unknown domain:拦截或审批
这两个东西配合起来,比"在 system prompt 里提醒模型不要乱来"可靠得多。
对我们自己的 Harness 来说,最小可实现版本可以是:
text
工具调用请求
-> before_tool hooks
-> permission policy
-> approval request
-> 执行工具
-> after_tool hooks
-> 写审计日志
有了这层之后,工具系统就不只是"函数分发表",而开始变成一个受策略控制的执行层。
五、状态外置:上下文不是数据库
前面几篇讲过的很多机制,最后都会落到同一个问题上:关键状态不能只放在模型上下文里。
Plan 需要保存任务进度。
Memory 需要保存长期经验。
后台任务和定时任务需要保存下一次运行时间。
错误恢复需要保存失败步骤和恢复现场。
产品级 Agent 至少会逐步把下面这些状态外置出去:
- todo / task
- 长期记忆
- cron 配置
- 后台任务状态
- 子智能体消息
- transcript 摘要
- 工具调用记录
- 错误恢复检查点
原因很简单:
text
上下文不是数据库
模型看到的上下文,适合做当前推理。
但系统如果要长期可靠运行,关键状态必须能:
- 持久化
- 查询
- 恢复
- 审计
这也是为什么"可恢复"几乎总是和"状态外置"绑在一起。
如果任务状态只存在于模型上下文里,那么一旦上下文被 compact、进程重启、后台任务跨轮执行,系统就会失去连续性。
真正可用的 Agent runtime,通常会把状态拆成两类:
text
模型上下文:用于当前推理
外部状态:用于长期事实和系统恢复
这两者不是替代关系,而是配合关系。
现实产品里的例子:memory、settings、task 和 compact
这里也可以用一个长任务场景。
用户说:
text
帮我这周每天检查一次 CI 失败,如果是 flaky test 就记录下来,周五给我总结。
这个任务不可能只靠当前上下文完成。
系统至少要记住:
- 任务是什么
- 下次什么时候运行
- 每次检查到了什么
- 哪些失败已经判断为 flaky
- 周五要生成什么总结
Claude Code 里能看到几类不同的状态:
CLAUDE.md和 memory 用来保存项目约定、经验和偏好- settings 里有 permissions、hooks、MCP server 等配置
- 交互界面里有 context indicator,也支持自动 compact 或手动
/compact
这说明它不会把所有东西都塞在一次模型上下文里。
有些东西是项目上下文,有些东西是用户配置,有些东西是会话状态,有些东西是运行时压缩后的摘要。
Codex 也类似。公开术语里,task 是一个持久工作单元,包含上下文、消息、结果和动作;AGENTS.md 是持久指令;memories 是跨会话可复用的本地上下文;worktree 和 diff 则保存任务实际产生的文件变化。
这些机制对应到 Harness 里,可以拆成:
text
messages:当前推理上下文
task store:任务状态
memory store:长期经验
config store:权限和工具配置
artifact store:文件变化、日志、结果
schedule store:定时触发信息
这样 compact 不会让任务失忆,进程重启也不会让系统不知道自己做过什么。
六、Multi-Agent 收敛:不只是派出去,还要收回来
很多人第一次做多 Agent,会很兴奋地让大家同时开工。
但产品级系统更关注另一个问题:
text
结果怎么收回来
冲突怎么处理
谁来做最终决策
也就是说,多 Agent 不是越多越好,而是:
- 分工边界要清楚
- 上下文隔离要清楚
- 工作环境隔离要清楚
- 汇总协议要清楚
否则很容易变成一堆并发噪音。
一个更稳的 Multi-Agent 系统,至少要回答四个问题:
- 谁有权派发任务?
- 子 Agent 的结果用什么格式回流?
- 多个结果冲突时谁裁决?
- 子 Agent 是否允许直接修改主工作区?
如果这些问题没有回答,多 Agent 看起来是在协作,实际上只是把不确定性并发放大了。
所以真正成熟的多 Agent,不只是"能派出去",还得"能稳定收回来"。
现实产品里的例子:Codex subagents
假设用户给了一个复杂任务:
text
帮我评估这个 PR:检查代码风险、测试覆盖、性能影响,并给出是否建议合并。
这个任务天然可以并行:
text
reviewer:看代码风险
tester:看测试覆盖
perf:看性能影响
但如果三个 Agent 各自返回一大段文字,主 Agent 仍然会很难收敛。
Codex 的 subagents 文档很适合用来解释这个问题。
它不是简单地说"可以开很多个 Agent",而是强调:可以生成多个专门的子 Agent 并行工作,然后把结果收集到一个响应里。
这里的关键不是"多",而是:
text
分工明确
权限继承
结果汇总
父流程裁决
也就是说,产品级 Multi-Agent 不是让每个 Agent 各自跑完、各自说一段话就结束,而是要有一个父流程负责:
- 派发任务
- 限定子任务边界
- 收集结果
- 合并结论
- 处理审批和失败
Codex 文档还提到,subagent 会继承当前 sandbox policy。这个细节很重要:子 Agent 不是逃出主系统边界的"自由执行者",它仍然在父任务的权限和环境约束里工作。
这对我们自己的 Harness 有一个直接启发:
text
spawn_subagent 时,不只传 prompt
还要传 role、task_id、workspace、permission_scope、return_schema
更进一步,返回结果最好也结构化:
text
status:completed / blocked / failed
findings:发现了什么
risk_level:风险等级
suggested_next_step:建议主 Agent 怎么处理
artifacts:相关文件或日志
否则多 Agent 只是多几个对话,不是一个可控协作系统。
七、MCP 边界:它解决接入,不自动解决架构
这一点也很重要。
很多人接触到 MCP,会有一种"系统终于完整了"的感觉。
其实 MCP 解决的是:
text
外部能力如何标准化接进 LLM 应用
它很重要,但它不是:
- 任务系统
- 权限系统
- 审批系统
- 错误恢复系统
- 环境隔离系统
- 状态恢复系统
所以把 MCP 接进来,系统只是"外部能力来源更标准化了",不代表其他工程问题自动消失。
甚至可以反过来说:MCP 接得越多,越需要前面那些工程机制。
因为外部能力一多,系统就更需要知道:
- 哪些工具可信
- 哪些工具危险
- 哪些工具只读
- 哪些调用失败可以重试
- 哪些调用必须记录审计
MCP 扩展的是能力边界。
产品级架构要补的是控制边界。
现实产品里的例子:MCP 工具接入也需要权限
假设你给 Agent 接了三个 MCP server:
text
docs:查内部文档
jira:读写工单
deploy:触发部署
如果只是把它们的工具全部加入工具池,模型一下子就多了很多能力。
但这些能力风险完全不同:
text
docs.search:只读,风险低
jira.update_ticket:会改外部系统,风险中
deploy.production:可能影响线上,风险高
Claude Code 的 MCP 文档里有一个很关键的点:MCP server 可以提供 tools、prompts、resources,而且还支持动态更新能力列表。
这很强,但也带来一个问题:
text
工具可以动态变多
风险也会动态变多
所以 Claude Code 的安全文档会强调 MCP server 的信任来源,也支持给 MCP server 配置 permissions。
Anthropic 的 MCP connector 文档也有类似思路:连接远程 MCP server 时,可以 allowlist、denylist 或配置单个工具。
Codex 术语里也把 MCP resource、MCP server、MCP tool 分开描述,本质上也是在说明:外部能力不是一个模糊整体,而是要拆成可读资源、可调用工具和服务来源来管理。
所以在自己的 Harness 里,connect_mcp 不应该只是:
text
发现工具
加入工具池
更完整一点应该是:
text
发现工具
-> 标记 server 来源
-> 区分 resource / prompt / tool
-> 标记 tool 风险级别
-> 套用 allow/deny 策略
-> 高风险工具进入 approval
-> 写入审计记录
-> 下一轮暴露给模型
这也是为什么 MCP 是外部能力标准,不是完整 Agent 架构本身。
八、完整架构:产品级差异在执行闭环
把前面几件事放在一起,你会发现产品级 Agent 真正的提升,不是多了某个单点 feature,而是闭环更完整了。
一个更接近产品级的闭环,通常至少会包括:
text
任务创建
-> prompt 装配
-> 计划与执行
-> 工具调用
-> 权限检查
-> 状态落盘
-> 错误分类
-> 恢复策略
-> 长任务异步推进
-> 定时唤醒
-> 审计与回放
如果缺任何一环,系统都可能在真实环境里掉链子。
比如:
- 没有 prompt 分层,系统会越来越难维护
- 没有状态落盘,长任务会失去连续性
- 没有错误分类,失败后只能靠模型猜
- 没有 worktree 或沙箱,并发执行会污染主环境
- 没有审计,用户很难知道 Agent 到底做过什么
这也是这篇文章想强调的核心:
text
产品级不是单点能力更炫
而是执行闭环更稳
现实产品里的例子:Codex 和 Claude Code 都在做"组合系统"
最后把场景拉完整一点。
假设用户说:
text
每天早上检查 main 分支 CI。
如果失败,定位原因;
如果是简单测试问题,开一个修复分支;
如果需要改生产配置,先问我;
周五给我一份总结。
这已经不是"调用一个工具"能解决的任务。
它需要一整套 runtime:
text
定时触发:每天早上运行
上下文装配:知道项目规则和历史失败
隔离环境:在 worktree 里做修复
工具调用:跑测试、读日志、改文件
权限审批:生产配置必须确认
错误恢复:CI 服务失败要重试或记录
状态外置:保留每天的检查结果
结果汇总:周五生成总结
把前面这些产品机制放在一起看,会发现 Claude Code 和 Codex 都不是只提供一个聊天框。
Claude Code 有:
- 项目上下文和 memory
- skills
- hooks
- permissions
- MCP
- 会话恢复和 compact
Codex 有:
- AGENTS.md
- tools
- sandbox
- approval policy
- worktree
- subagents
- automations / scheduled tasks
- task 和 thread
这些东西单独看都是 feature。
但放在一起,它们其实是在组成一个 Agent runtime:
text
上下文系统
+ 工具系统
+ 权限系统
+ 隔离环境
+ 状态系统
+ 协作系统
+ 外部能力接入
这就是产品级 Agent 的真实形态。
不是一个更聪明的 prompt,也不是一个更长的工具列表,而是一套能把模型推理、工具执行、权限边界、状态恢复和用户控制连接起来的运行时。
小结
一个 Agent Harness 能跑起来,说明它已经具备了最基本的执行闭环:
text
模型理解任务
-> 选择工具
-> 执行动作
-> 看结果
-> 继续推进
但"能跑"不等于"可用"。
真正可用的 Agent,还要补上另一组工程闭环:
text
prompt 要分层
错误要能恢复
环境要能隔离
权限要能控制
状态要能持久化
结果要能审计和回放
这些机制看起来不像模型能力本身,但它们决定了用户敢不敢把真实任务交给 Agent。
所以这一篇的重点不是再加一个新功能,而是换一个视角看前八篇:
text
前八篇解决 Agent 怎么工作
这一篇开始讨论 Agent 怎么被可靠地运行
这也是从教学 Harness 走向产品级 Runtime 的关键一步。