给 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,它描述 RoomShell、Door、GableRoof 以及 anchor、wall、offset、overhang 这类建筑语义,不需要自己列出几万个方块坐标。
比如模型只需要表达“门在前墙偏移 3 格的位置”,而不是计算墙和门洞涉及的所有绝对坐标。
这也是我一直保留的一条设计原则:Agent is the author. Engine is the compiler.
确定性留给引擎
CraftDAG 现在大致按下面的路径工作:
- BuildIntent:用户的自然语言意图;
- ComponentPlan:Agent 生成的组件级计划;
- CraftDAG IR:展开依赖、依附关系和几何约束;
- 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.txt 和 JSON 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。
相关探索与资源: