论文:Code as Agent Harness: Toward Executable, Verifiable, and Stateful Agent Systems(arXiv:2605.18747,2026-05) PDF:arXiv:2605.18747
1. 代码作为 Harness 的三层结构
Code as Agent Harness 把相关工作分成 interface、mechanisms 和 scaling 三层. Interface 规定模型怎样通过代码接触推理、行动与环境, mechanisms 让这些动作在长轨迹中持续运行, scaling 再处理多 Agent 的共享状态与协作拓扑.
三层存在明确依赖. 扩展 worker 数量之前, 单个执行体的动作语义、状态持久化和验证闭环必须先成立; 否则并发只会放大重复副作用、过期状态和合并冲突. 代码的价值在于把计划、动作、证据和状态转移放进可执行对象, 而不只是让模型多输出一段程序.
代码从输出变成操作基底,LLM 已能生成竞赛程序和仓库级补丁, 但 Agent 系统里的代码还有另一项用途: 承载推理过程、工具动作、环境状态和 execution-based verification. 此时程序不只是 target output, 还是系统运行时的 operational substrate.
Code as Agent Harness: Toward Executable, Verifiable, and Stateful Agent Systems 用这个视角整理了约 197 篇代表性工作. 论文关注的对象是完整 Agent 系统: 模型提出程序或状态转移, Harness 执行并保存结果, 验证器依据环境证据决定下一步.
接口、机制与扩展,Interface 层描述代码怎样把 Agent 接到三项基本功能.
| 接口 | 代码的角色 | 典型机制 |
|---|---|---|
| Reasoning | 程序承载中间推理 | CoT-as-code、过程奖励、中间结果检查 |
| Acting | 生成程序作为动作策略 | Code-as-action、API 调用脚本、可复用 skill |
| Environment Modeling | 形式化状态、动力学与反馈 | 模拟器、单元测试、GUI/OS 状态机 |
接口层把模型输出变成可运行、可比较、可回归验证的对象. 程序的退出码、状态 diff 和测试结果都能成为下一轮决策的输入, 纯自然语言完成声明则做不到这一点.
Mechanisms 层负责维持 long-horizon 执行. 论文归纳了五类机制.
- Planning: 把意图编译成可解析、可验证的 program/contract.
- Memory: 保存单 Agent 状态, 支持多 Agent 共享记忆, 并通过 context compaction 与 state offloading 控制上下文.
- Tool Use: 覆盖函数 API、shell/GUI 环境、测试/linter/sandbox 反馈和 DAG/CI 工作流.
- Plan-Execute-Verify: 在权限约束下执行状态转移, 再由确定性传感器验证结果.
- Adaptive Harness Optimization: 根据 telemetry 修改 prompt、tool 或 workflow, 修改后必须通过治理与回归检查.
Mechanisms 层把「更长 context」与长期可靠性区分开. 上下文只能提供模型当前可见的信息, 显式状态与可重复验证闭环才负责确认哪些动作真实发生、哪些证据仍然有效.
Scaling 层把单 Agent 扩展成协作系统. repo、测试、trace 和结构化中间状态构成共享 code artifacts, 各个角色围绕同一份可验证状态工作.
- 角色分化: manager、planner、coder、reviewer、tester.
- 交互模式: pair programming、repair、debate、red teaming、adversarial collaboration.
- 协作拓扑: 中心化 coordinator、分布式 peer 或 streaming 协作, AutoGen、MetaGPT 和 Self-Collaboration 属于文中讨论的案例.
- 状态收敛: test-gated merge 与 correctness consensus.
扩展层依赖代码与测试提供共同判据. 多个 Agent 可以对同一工件做 diff, 由 CI 和 property test 复算结果, 再按权限与版本规则合并; 增加聊天消息本身不会产生这种共享真值.
1.1. 应用域与尚未解决的问题
这套框架覆盖 coding assistant、GUI/OS automation、embodied agent、科学发现、个性化推荐、DevOps 和企业工作流. 它们共享可执行控制与状态验证问题, 具体动作空间和 verifier 却不相同.
尚未解决的问题集中在几处: 如何评价终态成功之外的过程质量, 如何在反馈不完整时验证动作, 怎样让 Harness 升级不引入回归, 多 Agent 如何维护一致状态, 哪些安全关键动作必须由人监督, 以及图像、界面和实体环境怎样进入同一套状态表示.
三层结构如何约束执行,自然语言循环通常把会话文本当作主要状态. Code Harness 另有文件、测试、进程和 repo 等外部可观测状态: LLM 提议转移, 执行器实施动作, 确定性 verifier 决定新状态是否成立. 因此, 可靠性由模型策略与 Harness 共同决定, 不能从单次 sampling 的文本质量直接推出.
LongHorizon-Harness 的 MEA 循环和 Polaris 的确定性抓取与模型判断分流, 都可以放进这套坐标系. Code as Agent Harness 更关注跨领域概念: interface 规定接入方式, mechanisms 维持单条长轨迹, scaling 处理协作与吞吐.
1.2. 三层之间的依赖关系
Interface 先把模型输出接到可执行对象, mechanisms 再让这些对象跨轮保持状态, scaling 收尾阶段复制执行体并协调共享工件. 下图只表示依赖顺序, 不代表每个系统都需要多 Agent.
某项工作只有 tool call API 时, 它覆盖的是 interface 的一部分; 缺少 verify loop 时, mechanisms 仍不完整; 多 Agent 若没有共享测试和版本化状态, scaling 层也无法收敛.
Interface(代码接 reasoning/action/env)
↓ 需要
Mechanisms(plan/memory/tool/verify 维持长程)
↓ 扩展到
Scaling(repo 级共享工件 + 多角色编排)
五个问题可以检查三层是否真正落地. Executable 要求关键决策进入脚本、测试或配置等可运行工件; Verifiable 要求测试、类型检查或环境 diff 独立于模型自评; Stateful 要求目标与进度在 context 外持久化; Regression-safe 要求 Harness 自我修改经过 telemetry 与回归门控; Multi-agent ready 要求共享工件带版本且可审查.
这些条件把代码 Harness 与普通代码生成区分开. 自动修改 prompt、tool 或 workflow 后若没有 golden trace 回归, 生产配置已经变化, 完成声明却缺少对应证据. 共享 repo、test suite 和 trace 也只有在版本、权限和合并规则明确时, 才能成为多 Agent 的公共状态.
状态、控制流与事务语义,把代码当作 harness, 会把任务中的一部分隐含状态放进可以读取、执行和比较的对象. 单纯把自然语言计划改写成 Python 还不够. 对话里的「已经处理到第三批文件」很难独立验证; 一个带版本号的任务清单、一组文件哈希和一份测试报告则能由其他执行体重新读取. 代码让状态转移变得显式, schema、权限、验证和恢复机制共同决定可靠性.
这里的「代码」至少包含三种载体. 第一种是程序文本, 它保存分支、循环、异常处理与工具调用顺序. 第二种是文件和数据库记录, 它们保存跨进程仍需存在的目标、任务进度、环境快照与验证结果. 第三种是运行时对象, 例如队列项、进程句柄、浏览器会话和事务上下文, 它们连接当前执行体与外部环境. 三者的生命周期不同: 程序文本可以版本化, 文件可以持久化, 运行时对象通常在进程退出后失效. Harness 如果只序列化收尾阶段一种对象的名称, 却没有重建方法, 重启后看到的只是一个无法继续使用的旧引用.
可以把第 步的外置状态写成
是当前目标版本, 是待办与依赖, 是已经执行的动作记录, 是外部资源及版本, 是验证结果. LLM 不直接把一句「完成」写入 . 它只能提出动作, harness 执行动作并读取传感器结果, 验证器再决定某项状态能否从 running 转成 verified. 这就是综述所说的 executable、verifiable 和 stateful 在工程里的交点.
控制平面与数据平面,长任务系统可以分成两个互相配合的平面. 控制平面保存目标、计划、权限、任务状态机、重试规则、checkpoint 指针和审计事件. 它回答「现在允许做什么、下一步由谁做、怎样判断完成」. 数据平面承载真正被处理的对象, 例如仓库文件、网页内容、数据库行、模型权重、实验产物和工具返回. 它回答「动作实际作用于什么、产生了哪些变化」.
两者分开以后, 控制平面的一条 task.completed=true 不能覆盖数据平面的现实. 假设写文件的动作只成功了一半, 执行进程却在更新任务状态前崩溃. 恢复时不能仅看任务记录是 running 就重做, 也不能看到目标文件存在就宣布完成. Harness 需要读取文件哈希、临时文件、提交标记与对应验证结果, 再决定继续、补偿还是回滚.
控制平面也不能包含全部数据. 把大段工具输出、二进制产物和完整网页塞进任务状态, 会让每次调度都承担不必要的读写成本. 更合理的记录是资源定位符、版本、摘要和验证状态, 完整对象留在数据平面. 需要精确证据时再按定位符读取. 也就是说, 控制平面持有「怎样找到可信状态」, 不复制所有状态内容.
类型与 schema 是可执行约束. 自然语言计划允许一句话同时表达目标、猜测和结果, 程序化状态则可以用 schema 把它们拆开. 一个最小任务对象可以写成:
type TaskState = {
taskId: string;
goalVersion: number;
phase: "planned" | "running" | "verifying" | "verified" | "failed";
inputs: Array<{ uri: string; version: string }>;
actions: Array<{ id: string; tool: string; status: string }>;
checks: Array<{ name: string; exitCode: number; artifactUri: string }>;
checkpointId?: string;
};
这段类型做了几件具体的事. goalVersion 防止旧计划在目标改变后继续运行; inputs.version 让动作与输入版本绑定; actions.id 为副作用提供幂等标识; checks 保存验证器读到的结果, 而不是模型对结果的转述. phase 只能取有限状态, 因而可以定义合法转移. 例如 running -> verified 应被拒绝, 中间必须经过 verifying, 且 checks 中存在与当前产物版本对应的成功记录.
Schema 只会拒绝结构错误, 无法保证语义正确. 一个合法的 artifactUri 可能指向旧日志, exitCode: 0 也可能来自错误的测试范围. 因此字段需要交叉约束: 检查记录关联动作 ID、输入版本和产物哈希; 验证器确认它检查的是本轮结果; 状态转移函数一次性校验这些关系. 如果把校验散落在多个回调里, 任一调用路径漏掉检查, 同一个 schema 仍会保存错误状态.
2. 工具副作用怎样进入状态机
工具调用可以分成只读、可重复写入和不可逆副作用. 读取文件或查询状态通常可以安全重试, 但结果可能随环境变化, 因而仍要记录读取时间和版本. 生成同一临时文件属于可重复写入, 前提是路径与内容由确定输入决定. 发送消息、创建远程资源或扣费调用则可能不可逆, 盲目重试会产生重复副作用.
幂等键把一次逻辑动作与多次网络尝试关联起来. 设动作标识为 , harness 在调用前写入 intent(k), 工具返回后写入 effect(k, r). 进程若在两次写入之间退出, 恢复逻辑先查询外部系统是否已经存在对应 的效果. 查到就补写结果, 查不到才重新执行. 工具不支持幂等键时, 可以使用业务唯一约束、预创建资源 ID 或执行后的查询来缩小不确定区间.
事务适合能够由同一存储系统原子提交的动作. 例如任务状态与本地数据库变更可以放在一个事务里, 要么一起成功, 要么一起撤销. 跨越文件系统、浏览器与远程 API 的工作流通常无法共享一个事务, 这时需要 补偿动作. 创建资源后的补偿可能是删除资源, 发布内容后的补偿可能只能追加撤回声明. 补偿并不保证恢复到完全相同的世界, 所以控制平面应记录它的实际结果, 不能用 rolled_back=true 掩盖仍然存在的外部影响.
代码不是天然可靠的关键证据就在副作用边界. 模型可以生成语法正确的重试循环, 却把不可逆动作执行三次; 也可以捕获异常后返回成功, 让控制平面错误进入完成态. Harness 必须在模型生成代码之外强制权限、幂等与状态转移, 不能把这些约束作为 prompt 里的建议.
Checkpoint 保存哪些东西. Checkpoint 不是把进程内存完整复制一份. 它要保存能够重建执行的最小充分状态: 当前目标版本、已验证里程碑、未完成动作、外部资源版本、已发生副作用及其幂等键、下一步允许的状态转移. 对运行时对象, checkpoint 保存重建配方, 例如重新打开哪个文件、查询哪个远程任务、用哪个会话凭证恢复连接, 而不是保存已经失效的句柄值.
合适的 checkpoint 通常位于验证边界. 一个子任务完成且产物通过检查后, harness 固化 X_t; 下一阶段失败时, 可以回到这份已验证状态. 若在每条模型消息后保存, checkpoint 数量很多, 其中大部分并没有新的可信事实. 若只在任务结尾保存, 中途故障又会丢掉全部进展. 触发条件应对应真实状态变化, 例如目标版本更新、不可逆操作之前、阶段性验证通过或即将压缩上下文.
恢复时要重新读取数据平面. 文件可能被其他进程修改, 远程任务可能在 Agent 离线期间完成, 权限也可能过期. Harness 用 checkpoint 作为参照, 将当前环境与保存版本做 diff, 然后选择继续、重新规划或进入人工审批. 直接把旧运行时对象反序列化并继续调用, 只是把陈旧状态包装成了程序对象.
沙箱和权限边界,代码可执行意味着错误会产生真实影响. 沙箱负责限制执行体能够看见和修改的资源, 权限系统负责判断某个动作在当前目标和用户授权下是否允许. 两者不能互相替代. 容器隔离可以阻止脚本读取宿主机目录, 但无法判断 Agent 是否有权删除容器内的项目数据; 审批规则可以禁止删除, 却无法阻止恶意依赖利用系统漏洞逃逸.
权限应靠能力授予, 不靠工具名称猜测. 同一个 shell 工具既能只读列目录, 也能覆盖文件. Harness 可以为任务分配明确的工作目录、只读数据源、允许访问的网络域和资源配额, 高风险动作再要求更窄的临时能力. 模型生成的代码运行前先静态检查目标范围, 运行时由沙箱限制系统调用, 运行后再比较环境 diff. 任一层发现超出范围, 控制平面都不能把动作记为成功.
日志同样属于安全边界. 完整命令和返回内容便于审计, 也可能包含凭证与隐私数据. 状态层应保存脱敏后的摘要和受控证据指针, 原始日志按访问权限隔离. 多 Agent 共享 repo 或 trace 时, 共享范围必须跟随角色权限, 不能因为它们属于同一任务就默认全部可见.
一个小型执行例. 设任务是「修改配置中的超时值, 运行检查, 通过后生成变更记录」. 控制平面创建目标版本 3, 输入文件版本为哈希 h0, 并生成动作 a1=patch, a2=test, a3=report, 依赖为 .
- 执行
a1前, harness 检查目标路径位于允许目录, 记录幂等键task-7/a1, 并把旧文件保存到 checkpoint. - 沙箱内脚本读取
h0, 应用结构化 patch, 写入临时文件, 校验 schema 后原子替换, 得到新哈希h1. - 控制平面不能因为脚本退出码为 0 就完成任务. 它把阶段转为
verifying, 执行a2, 并把检查范围、退出码和h1写入checks. - 如果检查失败, 文件恢复为
h0, 记录失败日志与补偿结果, 计划停在a1. 如果检查通过,a1与a2进入verified, 然后a3只从验证记录生成变更说明.
假设进程在文件替换后、状态更新前退出. 新执行体读取 checkpoint, 发现磁盘哈希为 h1, 幂等键没有完成记录. 它不会再次应用 patch, 而是核对 h1 的 schema 并重新运行 a2. 这个例子只有三步, 已经同时用到版本、幂等、验证和恢复. 如果状态只存在对话里, 新执行体很难区分「准备修改」与「已经修改但尚未登记」.
怎样对照评测代码 Harness. 比较代码 harness 与纯对话控制时, 应固定模型、任务、工具和最大调用预算, 只改变状态与控制机制. 任务集合要包含正常流程、工具超时、部分成功、进程中断、输入版本变化和不可逆副作用. 每种故障在预先指定的步骤注入, 避免某组碰到更简单的随机轨迹. 终态指标包括任务成功率、硬约束违反数和产物检查结果. 过程指标至少记录: 未验证结果被提升为完成态的次数; 重试造成的重复副作用; 从故障到可信 checkpoint 的恢复步数; 状态与实际环境不一致的持续时间; schema 或权限层拒绝非法转移的次数; 为持久状态、验证和恢复增加的 token、延迟与存储成本.
还要加入「代码自身出错」的对照. 人为让生成脚本引用错误字段、捕获异常后返回成功、读取旧版本输入或遗漏补偿动作, 观察 harness 的外层约束能否拒绝这些结果. 如果系统只在正常路径上表现更好, 故障路径仍然相信模型生成代码, 它只是把自然语言幻觉换成了可执行 bug.
结果按任务依赖深度与副作用等级分桶. 简单只读任务里, 代码 harness 的固定成本可能大于收益; 状态多、恢复频繁或含高风险写操作的任务, 显式控制与验证更重要. 这类评测不需要宣称代码一定优于对话. 它要回答更具体的问题: 哪类状态被外置以后可以恢复, 哪类错误被确定性检查拦住, 系统为此付出了多少工程和运行成本.
从自然语言到可执行计划,代码成为 harness,先要把含混目标编译成带类型的计划。自然语言目标 经过解析后,可以得到动作图 :每个节点 记录输入、输出、前置条件、权限和验收器,每条边表示数据依赖或控制依赖。执行器只调度所有前置条件已经满足的节点,多写几段 Python 并不能替代这层约束。 一个节点可以写成如下结构:
id: update-timeout
reads: [config/app.yaml@h0]
writes: [config/app.yaml]
requires: [workspace.write]
precondition: current_hash == h0
action: apply_patch
postcondition: schema_valid && timeout == 30
compensation: restore(config/app.yaml@h0)
YAML 中各字段共同形成可执行约束。reads 给出输入版本,writes 限制副作用范围,precondition 防止旧计划作用于新环境,postcondition 决定状态能否前进,compensation 则说明失败后是否可撤销。若这些信息仍藏在一段 prompt 里,模型每轮都要重新解释,解释结果还可能漂移。 计划图也给并行执行留下了明确边界。两个节点的写集合不相交、读写之间没有冲突,并且不存在依赖边时,才可以并行。用 表示节点 的读写集合,安全并行至少要求:
这三个集合条件仍然不够处理外部系统。例如两个动作分别修改同一云账户下的配额与实例数,资源路径不同,却共享预算约束。此时还要给动作声明逻辑资源锁。代码 harness 的好处在于这些规则可以由调度器执行,而不是靠 Agent 在忙乱的上下文里记住一句「小心不要冲突」。
程序、解释器与环境各自保存什么. 长任务经常把三种状态混在一起。程序状态是计划节点、变量和待执行分支;解释器状态是调用栈、异常、重试计数和已注册的补偿动作;环境状态则是真实文件、进程、数据库、网页与远端资源。只保存其中一层,都无法完整恢复。 若进程在 write_file() 返回后立刻崩溃,程序状态可能仍显示该节点处于 running,环境里的文件却已经改变。恢复时不能盲目重放。Harness 先读取环境,比较输入与输出版本,再决定把节点推进到 verifying、重新执行,还是标成冲突。这个过程相当于数据库里的 write-ahead record,只是动作对象不一定支持事务。
可逆动作与不可逆动作要分开。覆盖本地文件通常能靠快照恢复;创建云资源可通过删除进行补偿,但补偿本身也可能失败;发送消息或提交付款几乎无法撤销。动作类型可以标成 reversible、compensatable、irreversible。不可逆动作进入队列前需要更强的前置验证和授权门,而且幂等键必须由外部系统认可,单靠本地日志阻止不了重复发送。 一次恢复的判断可以写成状态表:
| 计划记录 | 环境观察 | 恢复动作 |
|---|---|---|
| pending | 输入版本仍为旧值 | 正常执行 |
| running | 输出版本已出现 | 跳过写入,转入验证 |
| running | 环境处于部分状态 | 执行补偿或生成修复节点 |
| verified | 环境版本已漂移 | 撤销完成标记,重新规划 |
| failed | 外部幂等键显示成功 | 核对结果,修正本地状态 |
收尾阶段一行很容易被忽略。本地超时只说明没有收到响应,不能证明远端动作失败。可靠 harness 会把「未知」保留成独立状态,随后用资源 ID 或幂等键查询;直接重试可能制造两个真实副作用。
2.1. 代码执行为什么便于验证
自然语言结论很难做等价判断。两段描述用词不同,可能表达同一状态;同一句「已完成」也可能对应完全不同的产物。代码把一部分语义压进类型、测试和断言,使验证从语言相似度转向环境事实。例如要求输出 JSON 时,schema 可以先验证字段、类型和范围,业务检查再验证字段之间的关系:
三项分别对应结构正确、内部约束成立和外部效果出现。结构正确只说明文件能解析;内部约束可以检查总额、引用与版本关系;外部检查才确认服务确实加载了这份配置。很多 Agent 系统只做第一项,随后把「JSON 合法」写成「任务成功」,中间差了整整两层。 测试也有证据强弱。单元测试验证局部函数,集成测试覆盖组件协作,端到端测试靠近用户结果。Harness 应按动作风险选择证据,不必所有节点都跑最昂贵的端到端检查,也不能让高风险写操作只配一个语法检查。可以给条件赋权 ,把已通过权重记为:
适合显示进度,不适合作为自动完成阈值。只要有一个硬条件失败,哪怕 ,任务仍未完成。进度分数帮助排序,硬谓词决定提交,这个区分能防止漂亮的百分比遮住致命缺口。
Harness 本身也是待测程序. 一旦代码负责规划、权限和恢复,Harness 的 bug 会跨任务复用,影响面甚至大于单次模型幻觉。类型系统只能阻止它能表达的错误,错误 schema 会稳定地验证错误对象;测试程序也可能因为缓存、路径和环境变量读到错误结果。因此,Harness 需要自己的回归集。 一组最低限度的故障注入应覆盖:动作执行前中断、动作成功后回执丢失、验证期间环境漂移、补偿动作失败、日志写入失败、同一幂等键并发到达、旧 checkpoint 在新版本程序中恢复。每种故障都要检查两个问题:环境有没有产生重复副作用,控制平面有没有把未知结果提升成完成。
还要做模型无关测试。把模型替换成固定脚本,让它依次返回合法动作、越权动作、错误字段和自相矛盾的完成声明,观察 harness 是否按规则处理。若系统必须依靠一个聪明模型才不越权,约束其实没有进入基础设施。反过来,确定性层也不该擅自决定开放语义问题,例如替用户判断设计稿是否好看;它只负责把这类条件路由到合适的人工或模型评审。
三层框架怎样落到一条运行时间线,综述所说的 interface、mechanisms 与 scaling 并非三组互不相关的技术。一次运行从接口层接收目标、工具 schema 和环境观察;机制层把它们变成计划、代码、验证与恢复;扩展层再决定并发多少执行体、怎样共享状态、如何控制成本。时间线大致如下:
目标与权限
↓ interface: 统一消息、工具与环境表示
计划图生成
↓ mechanisms: 类型检查、沙箱执行、验证、checkpoint
单任务可信轨迹
↓ scaling: 并行调度、共享缓存、跨 Agent 协作、预算控制
审计过的最终状态
扩展不能早于单任务语义稳定。一个会重复副作用的执行循环扩到一千个 sandbox,只会更快地产生一千份错误。反过来,所有动作完全串行虽然容易推理,却会让长任务慢得无法使用。比较合理的顺序是先定义动作与提交语义,再按读写集合和资源锁开放并行,收尾阶段用队列、配额与优先级控制系统规模。
代码作为 harness 的真正吸引力也在这里:它把一些原本靠模型临场发挥的约束变成可运行、可测试、可版本化的对象。但代码没有消灭不确定性。开放世界里的目标解释、视觉判断、外部服务漂移仍然存在,生成的程序本身也会出错。它提供的是更坚实的承载层,让错误有位置可查、状态有版本可读、恢复有规则可循。
可观测性、程序语义与验证,Agent 每一步只能拿到局部 observation。终端输出可能被截断,网页可访问性树可能缺失画布内容,API 返回也可能来自缓存。Harness 若把局部观察直接当成完整状态,代码再精确也会在错误输入上稳定运行。 设真实状态为 ,工具返回观察 。动作 改变环境后,Harness 实际维护的是对状态的估计 。当观察不完整时,计划节点应记录证据来源与置信边界,而非伪装成精确值。例如命令只打印收尾阶段 100 行日志,就不能据此断言此前没有错误;网页截图没出现按钮,也可能只是按钮在折叠区域。
组合读取多种观察能减少这类偏差。文件写入后同时检查内容哈希和解析结果,服务部署后同时查询健康端点与版本端点,GUI 操作后同时读取截图、URL 和可访问性树。不同观察若冲突,状态进入 ambiguous,由下一步主动消歧。让模型挑一个顺眼的结果继续,往往会把观测缺口变成后续事实。 缓存还会制造时序错误。测试进程读取旧构建产物,健康检查命中旧实例,Agent 看到的是合法但过期的结果。验证记录要包含输入版本与观测时间;能使用强一致读取时使用强一致读取,不能时则等待明确的收敛条件,而非固定睡几秒后祈祷。固定延迟既浪费快路径,也无法覆盖慢路径。
2.2. 多 Agent 共享代码时的冲突
Scaling 层常把任务拆给多个 Agent,但共享仓库不是共享一张便签纸。两个执行体改动同一个模块,即使 Git 能合并文本,语义也可能冲突。调度器要维护每个子任务的读写集合、基线版本与验收条件,并在合并后重新验证组合结果。 最简单的策略是悲观锁:同一写集合一次只允许一个 Agent。它容易理解,却会压低并发。乐观策略允许各自在隔离工作区修改,提交时比较基线哈希;没有冲突则合并,有冲突则生成新的协调任务。无论哪种策略,都不能把 git merge 成功视为功能正确,组合回归测试才是提交门。
共享知识也要版本化。Agent A 根据接口 v1 写调用方,Agent B 同时把接口升级到 v2;两边各自测试都通过,合并后调用方已经过期。计划图里的数据依赖应引用具体 schema 版本,接口变更会使依赖它的未完成节点失效。调度器随后重算这些节点,而非让旧任务继续消耗预算。 消息通信不适合承载全部状态。自然语言适合解释意图和异常,资源版本、动作结果、锁与依赖应进入结构化存储。否则多个 Agent 会形成几份彼此接近却不完全一致的项目故事,收尾阶段再由一个协调模型猜哪份可信。这正是 code as harness 希望消除的情况。
版本升级与状态迁移. 长任务可能运行数小时甚至数天,期间 Harness 代码、工具 schema 或模型端点会升级。旧 checkpoint 能否在新版本恢复,需要显式兼容规则。状态文件至少带 schema_version、Harness commit 和工具版本;加载时先迁移结构,再恢复执行。 迁移函数应满足幂等性。状态从 v1 升到 v2 后再次运行迁移,结果不能继续变化;迁移失败时保留旧快照,不能写出半份新状态。若新版本改变动作语义,例如 delete 增加回收站参数,旧计划节点不应静默套用默认值,而应失效并重新规划。
模型升级也可能改变输出格式和工具选择。Harness 的解析器要用契约测试覆盖支持的模型端点,把未知字段、缺失字段和多个并发 tool call 都作为输入。版本升级先在保存的离线 trace 上重放,再进入真实任务。这样能够分辨解析层回归与模型能力变化,避免把基础设施 bug 算到新模型头上。
最小可观测面,一条长任务失败时,工程人员至少要能回答:哪个计划节点在何时由哪个执行体运行;它读取了哪个版本;产生了哪些副作用;验证器看到了什么;状态为什么前进或停下。对应日志可分成事件流、状态快照和证据对象三层。 事件流只追加,记录计划、调度、工具调用、验证与提交;状态快照便于快速恢复;大体积日志、截图和测试报告作为证据对象单独保存,以哈希关联。三层都带 task、node、attempt 与 trace 标识,就能从最终状态反查到原始证据,也能从一次异常定位受影响的状态转移。
指标应服务于故障定位。总 token 和成功率之外,还应监控每类状态停留时间、重试原因、幂等冲突、权限拒绝、验证器不确定率、补偿成功率和 checkpoint 恢复耗时。若失败率上涨,可以先判断是模型动作变差、环境故障变多,还是验证规则变严。没有这些分解,总成功率只会告诉人系统坏了,却不告诉人坏在哪里。 日志必须脱敏。命令参数、工具响应和环境变量可能含密钥;完整 trace 也可能包含用户数据。采集层在写盘前按 schema 标记敏感字段,原始证据进入权限更窄的存储,普通调试页面只展示脱敏摘要。可观测性不能以复制所有秘密为代价。
证据对象还应设置保留周期。最终状态和关键验证可以长期保存,原始屏幕录制、完整 shell 输出与模型上下文则按风险缩短周期;删除时保留哈希和删除记录,使审计链知道证据曾经存在,却不继续暴露其内容。