Browse this library

Chinese source article

3.2.1 · 代码 Harness 与副作用

解释代码作为 Harness 的层次、工具副作用、事务边界与多 Agent 冲突。

View original Markdown ↗

论文: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 如果只序列化收尾阶段一种对象的名称, 却没有重建方法, 重启后看到的只是一个无法继续使用的旧引用.

可以把第 tt 步的外置状态写成

Xt=(Gt,Qt,At,Rt,Vt),X_t=(G_t,Q_t,A_t,R_t,V_t),

GtG_t 是当前目标版本, QtQ_t 是待办与依赖, AtA_t 是已经执行的动作记录, RtR_t 是外部资源及版本, VtV_t 是验证结果. LLM 不直接把一句「完成」写入 VtV_t. 它只能提出动作, 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. 工具副作用怎样进入状态机

工具调用可以分成只读、可重复写入和不可逆副作用. 读取文件或查询状态通常可以安全重试, 但结果可能随环境变化, 因而仍要记录读取时间和版本. 生成同一临时文件属于可重复写入, 前提是路径与内容由确定输入决定. 发送消息、创建远程资源或扣费调用则可能不可逆, 盲目重试会产生重复副作用.

幂等键把一次逻辑动作与多次网络尝试关联起来. 设动作标识为 kk, harness 在调用前写入 intent(k), 工具返回后写入 effect(k, r). 进程若在两次写入之间退出, 恢复逻辑先查询外部系统是否已经存在对应 kk 的效果. 查到就补写结果, 查不到才重新执行. 工具不支持幂等键时, 可以使用业务唯一约束、预创建资源 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→a2→a3a1\rightarrow a2\rightarrow a3.

  1. 执行 a1 前, harness 检查目标路径位于允许目录, 记录幂等键 task-7/a1, 并把旧文件保存到 checkpoint.
  2. 沙箱内脚本读取 h0, 应用结构化 patch, 写入临时文件, 校验 schema 后原子替换, 得到新哈希 h1.
  3. 控制平面不能因为脚本退出码为 0 就完成任务. 它把阶段转为 verifying, 执行 a2, 并把检查范围、退出码和 h1 写入 checks.
  4. 如果检查失败, 文件恢复为 h0, 记录失败日志与补偿结果, 计划停在 a1. 如果检查通过, a1 与 a2 进入 verified, 然后 a3 只从验证记录生成变更说明.

假设进程在文件替换后、状态更新前退出. 新执行体读取 checkpoint, 发现磁盘哈希为 h1, 幂等键没有完成记录. 它不会再次应用 patch, 而是核对 h1 的 schema 并重新运行 a2. 这个例子只有三步, 已经同时用到版本、幂等、验证和恢复. 如果状态只存在对话里, 新执行体很难区分「准备修改」与「已经修改但尚未登记」.

怎样对照评测代码 Harness. 比较代码 harness 与纯对话控制时, 应固定模型、任务、工具和最大调用预算, 只改变状态与控制机制. 任务集合要包含正常流程、工具超时、部分成功、进程中断、输入版本变化和不可逆副作用. 每种故障在预先指定的步骤注入, 避免某组碰到更简单的随机轨迹. 终态指标包括任务成功率、硬约束违反数和产物检查结果. 过程指标至少记录: 未验证结果被提升为完成态的次数; 重试造成的重复副作用; 从故障到可信 checkpoint 的恢复步数; 状态与实际环境不一致的持续时间; schema 或权限层拒绝非法转移的次数; 为持久状态、验证和恢复增加的 token、延迟与存储成本.

还要加入「代码自身出错」的对照. 人为让生成脚本引用错误字段、捕获异常后返回成功、读取旧版本输入或遗漏补偿动作, 观察 harness 的外层约束能否拒绝这些结果. 如果系统只在正常路径上表现更好, 故障路径仍然相信模型生成代码, 它只是把自然语言幻觉换成了可执行 bug.

结果按任务依赖深度与副作用等级分桶. 简单只读任务里, 代码 harness 的固定成本可能大于收益; 状态多、恢复频繁或含高风险写操作的任务, 显式控制与验证更重要. 这类评测不需要宣称代码一定优于对话. 它要回答更具体的问题: 哪类状态被外置以后可以恢复, 哪类错误被确定性检查拦住, 系统为此付出了多少工程和运行成本.

从自然语言到可执行计划,代码成为 harness,先要把含混目标编译成带类型的计划。自然语言目标 TT 经过解析后,可以得到动作图 G=(V,E)G=(V,E):每个节点 viv_i 记录输入、输出、前置条件、权限和验收器,每条边表示数据依赖或控制依赖。执行器只调度所有前置条件已经满足的节点,多写几段 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 里,模型每轮都要重新解释,解释结果还可能漂移。 计划图也给并行执行留下了明确边界。两个节点的写集合不相交、读写之间没有冲突,并且不存在依赖边时,才可以并行。用 Ri,WiR_i,W_i 表示节点 ii 的读写集合,安全并行至少要求:

Wi∩Wj=∅,Wi∩Rj=∅,Wj∩Ri=∅.W_i\cap W_j=\varnothing,\qquad W_i\cap R_j=\varnothing,\qquad W_j\cap R_i=\varnothing.

这三个集合条件仍然不够处理外部系统。例如两个动作分别修改同一云账户下的配额与实例数,资源路径不同,却共享预算约束。此时还要给动作声明逻辑资源锁。代码 harness 的好处在于这些规则可以由调度器执行,而不是靠 Agent 在忙乱的上下文里记住一句「小心不要冲突」。

程序、解释器与环境各自保存什么. 长任务经常把三种状态混在一起。程序状态是计划节点、变量和待执行分支;解释器状态是调用栈、异常、重试计数和已注册的补偿动作;环境状态则是真实文件、进程、数据库、网页与远端资源。只保存其中一层,都无法完整恢复。 若进程在 write_file() 返回后立刻崩溃,程序状态可能仍显示该节点处于 running,环境里的文件却已经改变。恢复时不能盲目重放。Harness 先读取环境,比较输入与输出版本,再决定把节点推进到 verifying、重新执行,还是标成冲突。这个过程相当于数据库里的 write-ahead record,只是动作对象不一定支持事务。

可逆动作与不可逆动作要分开。覆盖本地文件通常能靠快照恢复;创建云资源可通过删除进行补偿,但补偿本身也可能失败;发送消息或提交付款几乎无法撤销。动作类型可以标成 reversible、compensatable、irreversible。不可逆动作进入队列前需要更强的前置验证和授权门,而且幂等键必须由外部系统认可,单靠本地日志阻止不了重复发送。 一次恢复的判断可以写成状态表:

计划记录 环境观察 恢复动作
pending 输入版本仍为旧值 正常执行
running 输出版本已出现 跳过写入,转入验证
running 环境处于部分状态 执行补偿或生成修复节点
verified 环境版本已漂移 撤销完成标记,重新规划
failed 外部幂等键显示成功 核对结果,修正本地状态

收尾阶段一行很容易被忽略。本地超时只说明没有收到响应,不能证明远端动作失败。可靠 harness 会把「未知」保留成独立状态,随后用资源 ID 或幂等键查询;直接重试可能制造两个真实副作用。

2.1. 代码执行为什么便于验证

自然语言结论很难做等价判断。两段描述用词不同,可能表达同一状态;同一句「已完成」也可能对应完全不同的产物。代码把一部分语义压进类型、测试和断言,使验证从语言相似度转向环境事实。例如要求输出 JSON 时,schema 可以先验证字段、类型和范围,业务检查再验证字段之间的关系:

Valid⁡(x)=Schema⁡(x)∧Invariant⁡(x)∧ExternalCheck⁡(x).\operatorname{Valid}(x)=\operatorname{Schema}(x)\land\operatorname{Invariant}(x)\land\operatorname{ExternalCheck}(x).

三项分别对应结构正确、内部约束成立和外部效果出现。结构正确只说明文件能解析;内部约束可以检查总额、引用与版本关系;外部检查才确认服务确实加载了这份配置。很多 Agent 系统只做第一项,随后把「JSON 合法」写成「任务成功」,中间差了整整两层。 测试也有证据强弱。单元测试验证局部函数,集成测试覆盖组件协作,端到端测试靠近用户结果。Harness 应按动作风险选择证据,不必所有节点都跑最昂贵的端到端检查,也不能让高风险写操作只配一个语法检查。可以给条件赋权 wjw_j,把已通过权重记为:

P=∑jwj1[gj=pass]∑jwj.P=\frac{\sum_j w_j\mathbb{1}[g_j=\text{pass}]}{\sum_j w_j}.

PP 适合显示进度,不适合作为自动完成阈值。只要有一个硬条件失败,哪怕 P=0.99P=0.99,任务仍未完成。进度分数帮助排序,硬谓词决定提交,这个区分能防止漂亮的百分比遮住致命缺口。

Harness 本身也是待测程序. 一旦代码负责规划、权限和恢复,Harness 的 bug 会跨任务复用,影响面甚至大于单次模型幻觉。类型系统只能阻止它能表达的错误,错误 schema 会稳定地验证错误对象;测试程序也可能因为缓存、路径和环境变量读到错误结果。因此,Harness 需要自己的回归集。 一组最低限度的故障注入应覆盖:动作执行前中断、动作成功后回执丢失、验证期间环境漂移、补偿动作失败、日志写入失败、同一幂等键并发到达、旧 checkpoint 在新版本程序中恢复。每种故障都要检查两个问题:环境有没有产生重复副作用,控制平面有没有把未知结果提升成完成。

还要做模型无关测试。把模型替换成固定脚本,让它依次返回合法动作、越权动作、错误字段和自相矛盾的完成声明,观察 harness 是否按规则处理。若系统必须依靠一个聪明模型才不越权,约束其实没有进入基础设施。反过来,确定性层也不该擅自决定开放语义问题,例如替用户判断设计稿是否好看;它只负责把这类条件路由到合适的人工或模型评审。

三层框架怎样落到一条运行时间线,综述所说的 interface、mechanisms 与 scaling 并非三组互不相关的技术。一次运行从接口层接收目标、工具 schema 和环境观察;机制层把它们变成计划、代码、验证与恢复;扩展层再决定并发多少执行体、怎样共享状态、如何控制成本。时间线大致如下:

目标与权限
   ↓  interface: 统一消息、工具与环境表示
计划图生成
   ↓  mechanisms: 类型检查、沙箱执行、验证、checkpoint
单任务可信轨迹
   ↓  scaling: 并行调度、共享缓存、跨 Agent 协作、预算控制
审计过的最终状态

扩展不能早于单任务语义稳定。一个会重复副作用的执行循环扩到一千个 sandbox,只会更快地产生一千份错误。反过来,所有动作完全串行虽然容易推理,却会让长任务慢得无法使用。比较合理的顺序是先定义动作与提交语义,再按读写集合和资源锁开放并行,收尾阶段用队列、配额与优先级控制系统规模。

代码作为 harness 的真正吸引力也在这里:它把一些原本靠模型临场发挥的约束变成可运行、可测试、可版本化的对象。但代码没有消灭不确定性。开放世界里的目标解释、视觉判断、外部服务漂移仍然存在,生成的程序本身也会出错。它提供的是更坚实的承载层,让错误有位置可查、状态有版本可读、恢复有规则可循。

可观测性、程序语义与验证,Agent 每一步只能拿到局部 observation。终端输出可能被截断,网页可访问性树可能缺失画布内容,API 返回也可能来自缓存。Harness 若把局部观察直接当成完整状态,代码再精确也会在错误输入上稳定运行。 设真实状态为 sts_t,工具返回观察 ot∼O(⋅∣st)o_t\sim O(\cdot\mid s_t)。动作 ata_t 改变环境后,Harness 实际维护的是对状态的估计 s^t\hat s_t。当观察不完整时,计划节点应记录证据来源与置信边界,而非伪装成精确值。例如命令只打印收尾阶段 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 输出与模型上下文则按风险缩短周期;删除时保留哈希和删除记录,使审计链知道证据曾经存在,却不继续暴露其内容。