给 Agent 造一个 Domain Harness

前段时间,我一直在思考怎么让 Agent 在复杂的虚拟环境中稳定地执行任务。当时我偶然看到了一个叫 mindcraft 的开源项目。它通过 harness 让大模型作为 Bot 进入 Minecraft 世界。你可以直接在游戏里给它发消息,比如“建一个房子”,Bot 就会开始动工。

演示很有意思,但我自己试下来稳定性并不好。Bot 经常原地卡住,一个很小的房子也可能做到一半停下来。原因可能是环境感知、动作执行、路径规划或者上下文维护,但共同的问题很明显:如果让大模型在游戏里实时闭环控制空间动作,它必须长期维护三维状态、理解环境反馈,还要持续做精确的坐标推理,误差很容易一点点累积起来。

于是我换了一个方向:既然游戏内实时建造太脆弱,能不能先让大模型在游戏外生成建筑蓝图,再通过 Mod 加载?顺着这个思路,我找到了 WorldEdit 和 schematic 相关工具链。问题就变成了:怎么让 LLM 生成可以被 WorldEdit 加载的 .schem 蓝图?

这也是后来 MinePilot 的起点。

.schem 本身仍然是很低层的表示,每一个位置最终都要落到具体的 block state。让模型直接生成体素数据,还是很容易出现墙体漏风、屋顶错位、楼梯断裂之类的问题。

这时我想起了另一个完全不同的项目——量化投资系统 策引(MyInvestPilot)。正如我在《我是如何构建一个 AI 原生量化系统的》里写过的,为了避免大模型写出带前视偏差(Look-ahead bias)的 Python 交易代码,我后来不再让模型直接生成底层计算逻辑,而是设计了一套策略原语(Primitives)和 DAG 引擎,让 Agent 只能在受约束的策略空间里表达想法。

一个是 Minecraft 建筑,一个是投资策略,看起来没什么关系,但最后走到了很像的架构。最近再回头看 coding agent 领域里常说的 harness engineering,我才找到一个更合适的词来理解这件事:我反复在做的,其实是在给不同领域造自己的 Domain Harness

Coding 为什么特别适合 Agent

Coding Agent 发展得快,一个重要原因是软件工程本来就有一套对机器非常友好的反馈环境:

文件系统
Git
代码与 AST
Compiler / Type Checker
Linter
Tests
CI
Issue / PR / Review
Shell / Runtime / Logs

Agent 改完代码可以马上编译,编译器会告诉它具体文件和行号;测试失败会给 assertion;CI 再跑一遍检查;Git 保存每一次修改;PR 和 Review 又提供更高一层的反馈。

所以 coding agent 并不是在一个完全开放的世界里“凭感觉写代码”。模型当然重要,但文件、编译器、测试、版本控制和运行时反馈,让它可以不断做 修改 → 验证 → 修复

很多专业领域恰好缺少这样的机器工作环境。我们经常给通用 Agent 接上 API 或 MCP,然后期待它自己理解领域语义、维护状态、遵守约束、发现错误并修复。真正困难的部分往往还没有被软件化。

这里我借用 harness 这个词,把这层基础设施称为 Domain Harness(领域 Harness)。这不是一个已经有严格定义的行业标准,只是我在策引和 MinePilot 两个项目里逐渐形成的一种理解。

我说的 Domain Harness 是什么

Domain Harness 可以理解成一套面向 Agent 的领域工作环境。它把这个领域里相对稳定的知识、动作、状态、约束和验证规则做成机器可以操作的结构。

一个简单的形态大致是:

用户意图
  ↓
Domain Contract / DSL / Tools
  ↓
Structured Plan ← Repair Feedback
  ↓
Domain Engine + Validation
  ↓
Versioned Artifact / Result

这里的 Domain Engine 是 Harness 内部负责确定性部分的核心,而不是与 Harness 并列的另一层。凡是可以稳定计算或机械验证的事情,我更倾向把它们下沉到引擎:坐标、数据对齐、指标计算、边界、预算、引用关系、schema 等。模型负责的主要是意图理解、规划和对错误反馈做出修正。

这样的设计有几个直接好处:Agent 的动作范围更清楚,重要状态可以被机器读取,错误能定位到具体对象和字段,计划与产物可以版本化;固定计划和引擎版本后,确定性阶段还可以被重新运行和验证。

DAG 和 DSL 在策引、MinePilot 里都很好用,但它们不是这个概念的必要条件。有些领域只需要一小组工具、状态和验证规则,也完全可以形成有用的 Harness。关键是把 Agent 原本需要“自己记住、自己算、自己猜”的部分,尽量变成环境提供的约束与反馈。

策引和 CraftDAG 是怎么做的

把动作空间提升到领域语义

很多 AI 产品发现“让模型自由写代码不可靠”之后,会转向极细的 API。例如在 Minecraft 里只开放 placeBlock(x, y, z, type)。这样虽然限制了模型,却没有减少真正的复杂度:坐标、状态和大量底层计算还是留给了 Agent。

策引的策略原语做的是另一件事。模型不需要写 Pandas 计算代码,而是组合 EMA、GreaterThan/LessThan、Lag、Streak 这类原语。底层的数据对齐、指标计算和前视偏差防护由引擎处理。

MinePilot 的 CraftDAG 也是一样。Agent 面对的是 ComponentPlan,它描述 RoomShellDoorGableRoof 以及 anchorwalloffsetoverhang 这类建筑语义,不需要自己列出几万个方块坐标。

比如模型只需要表达“门在前墙偏移 3 格的位置”,而不是计算墙和门洞涉及的所有绝对坐标。

这也是我一直保留的一条设计原则:Agent is the author. Engine is the compiler.

确定性留给引擎

CraftDAG 现在大致按下面的路径工作:

  1. BuildIntent:用户的自然语言意图;
  2. ComponentPlan:Agent 生成的组件级计划;
  3. CraftDAG IR:展开依赖、依附关系和几何约束;
  4. VoxelPlan:得到最终方块坐标、状态、材料,再用于预览或 schematic 导出。

策引也类似。Agent 组合 Strategy Primitives,引擎构造指标和信号 DAG,通过拓扑排序完成计算,最后得到可以回测和持续计算的 signal series。

模型可以提出“用 MA200 做趋势过滤”,但当天的 MA200 是多少、信号什么时候生效、计算有没有使用未来数据,这些都应该由引擎按固定规则算。模型也可以决定城堡某个角落需要一座塔,但塔楼是否越界、材料预算有没有超出、引用关系是否合法,同样更适合交给确定性代码。

机器可读的约束和修复

只把 Agent 限制在 DSL 里还不够。Domain Harness 好不好用,很大程度上取决于失败之后能不能告诉模型“到底错在哪”。

CraftDAG 会返回类似这样的结构化错误:

{
  "stage": "component-validation",
  "code": "ASSEMBLY_INSTANCE_OUT_OF_BOUNDS",
  "path": "instances[0].anchor",
  "componentId": "northwest_tower",
  "repairHint": "Move the instance inward or increase global bounds."
}

这和只返回一句 Invalid plan 差别很大。Agent 能看到失败阶段、对象、字段和修复方向,于是可以只改出问题的那一部分,再提交验证。

事前约束也一样重要。CraftDAG 有一份 LLM_AUTHORING_CONTRACT.md。我后来越来越偏向在这种文档里写清楚“绝对禁止项”(ABSOLUTE PROHIBITIONS),而不是试图用很长的 Prompt 教模型所有正确做法。

一个很具体的例子是:ComponentPlan 要求每个组件有唯一 id,组件之间必须通过 { "ref": "existing_component_id" } 引用,并明确禁止在 inputs 里直接内联另一个组件。这样做的目的不是限制模型创造力,而是保证引用关系可以被验证,错误也能被精确定位。

策引也有类似的机器接口,包括机器可读的开发文档llm-quickstart.txtJSON Schema

到了这一步,Agent 才有比较稳定的修复路径:计划提交给引擎,验证失败后拿到结构化错误,修改局部,再重新验证。这和 coding agent 的 edit-test-fix 循环本质上很接近。

把几个系统放在一起看,大概是这样的:

Coding 环境 策引的 Domain Harness MinePilot 的 Domain Harness
Source code Strategy Primitives / DSL ComponentPlan
AST / dependency graph Indicator / Signal DAG CraftDAG IR
Compiler / runtime Strategy evaluator / backtest Geometry / voxel compiler
Type checker / tests Schema / look-ahead / data checks Schema / bounds / budget / composition checks
Build artifact Signal / Trade Record / Portfolio state VoxelPlan / schematic / metadata
Error diagnostics Validation errors Structured validation errors
Edit-test loop Strategy repair loop Plan repair loop

这里我会更谨慎地区分:CraftDAG 本身是 MinePilot 领域 Harness 的核心引擎,不等于完整 Harness;策引的 DAG evaluator 也一样。真正的 Harness 还包括模型看到的契约、schema、工具、状态和错误反馈。

Harness 之上还有 Production System

把 Agent 在一个领域里“怎么可靠工作”解决之后,还有一层问题没有解决:为什么现在要生产这个东西、它该不该交付、交付后发生了什么。

我现在会把这几层分开看:

Level 1 — Agent Runtime Harness
model + context + tools + sandbox + loop + memory

Level 2 — Domain Harness
domain contract + tools + state + engine + validation + repair

Level 3 — Domain Production System
opportunity / intent → Domain Harness → artifact → review → delivery → feedback

MinePilot 是一个比较直观的例子。CraftDAG 解决建筑计划怎么被可靠地展开、验证和编译;MinePilot 还要决定生产什么建筑、怎么 review、什么时候公开、用户是否下载,以及 review 中反复出现的、可以机械检查的问题能不能继续沉淀成 validator。

我把这一层称为 Agent-Native Domain Production System。它和 Domain Harness 解决的问题不同:Harness 关注 Agent 如何在领域里可靠工作;Production System 还要处理机会选择、交付和真实反馈。

这套方法的边界

并不是所有 AI 产品都值得造一套 Domain Harness。它更适合那些领域结构相对稳定、关键中间状态可以被观察、结果能够被计算或验证、错误也能定位和修复的任务。

即使适合,也应该从最小结构开始。不是每个领域都需要 DSL,更不是每个领域都需要 DAG。过早把一个开放的创意任务塞进复杂的 schema 和 compiler,反而可能把模型原本擅长的部分限制掉。

Validation 也不等于“结果正确”。投资策略通过 schema 和回测,不代表未来会赚钱;建筑蓝图通过几何和边界检查,也不代表它一定好看。能机械验证的部分交给引擎,价值、风险、审美这些判断仍然需要人来承担。

回头看,我以前把这套东西称为“Agent-friendly workflow”或者“Agent 时代的软件接口”。现在我更愿意把它理解成一个领域为了让通用 Agent 真正进入核心工作流程,需要补出来的一层基础设施。

如果这个判断成立,随着模型和通用 Agent Runtime 越来越标准化、可替换,长期真正需要积累的可能不是某个 Prompt,也不一定是某个 Agent,而是领域里的这些契约、原语、状态、验证规则和反馈机制——也就是这里所说的 Domain Harness。


相关探索与资源:

更新时间: 16天前 版本: 7c2c1c0