← 工程跃迁 / 第 15 章
架构定位

Harness 与治理

用运行环境控制工具、权限、副作用、预算、人工确认、恢复和平台差异。

谁控制权限、运行环境、副作用、平台适配和恢复?

概念校准

Harness

它是什么
承载模型、工具、状态、权限、日志、预算、人工确认和恢复的运行控制环境。
它不是什么
它能限制风险并保留证据,但不能直接阻止模型产生幻觉。

Skill

它是什么
包含指令、资源、适用边界、版本和兼容信息的可复用能力包。
它不是什么
不等于一段 Prompt,也不意味着其中脚本可以被无条件执行。

工具

它是什么
Agent 可通过明确参数、权限和结果合同调用的受约束能力。
它不是什么
工具不等于 MCP;本地函数、命令或 HTTP 客户端也可以是工具。

多平台适配与迁移#

建立 Codex、Claude Code、WorkBuddy 的能力模型、目录和权限适配器。

本章只做一件事同一 Forge Skill 在三个平台通过兼容测试。
开始前,Forge 已经具备已完成 Module 14 的“身份、权限、安全与供应链”,其通过验收的 solution 是本章起点。
完成后,Forge 将能够让同一个 Forge Skill 通过适配器部署到三个平台。
卡住时的最小恢复点先输出平台差异报告,不执行安装或写入。
下载本章实验仓库Python 3.12 · pytest · Pydantic · SQLite · deterministic Mock
阶段能力链
01用平台无关合同描述 Skill 需要的工具、资源和确认能力02把规范 Skill 合同映射为 Codex 目录、工具声明和确认策略03生成 Claude Code 命令白名单,并拒绝无法表达的必需能力04将能力映射到 WorkBuddy 工作区权限,并保留只读与写入的差异05把三个平台的安装计划规范化为可比较、可审计的配置记录06区分阻塞缺失和可接受降级,并生成可解释的平台兼容报告07用统一合同检查安装路径、权限不升级和能力覆盖,阻止不安全适配器发布
每一步只增加一种可验证能力;后一步建立在前一步已经通过的代码和测试上。

概念校准\n\n在开始操作代码前,先把本章涉及的概念放回正确的工程边界。\n\n
\n\n### Harness harness\n\n- 它是什么: 承载模型、工具、状态、权限、日志、预算、人工确认、隔离和恢复的运行控制环境。\n- 它不是什么: 它可以缩小风险、拒绝动作并保留证据,但不能保证模型输出永远正确,也不能代替领域验收。\n- 与相邻概念的关系: Agent Loop 在 Harness 中运行;Tool、Memory 和 MCP 连接受其策略约束;Observability 记录过程,Evaluation 判断行为。\n- 在 HeatStack Forge 中的位置: Forge Harness 管理工作目录、工具、确认队列、预算、Checkpoint、审计日志和回滚,把模型建议与真实副作用分开。\n- 典型误用与修正: 误用是把系统提示词当成安全边界。修正方法是把权限、路径、网络、密钥、预算和回滚落实在模型之外的控制中。\n\n### Skill skill\n\n- 它是什么: 包含指令、资源、适用边界、版本和兼容元数据的可复用能力包,可被 Agent 平台发现、安装或引用。\n- 它不是什么: 它不等于一段 Prompt,也不代表附带脚本可以自动执行;实际执行仍受平台、工具合同和权限策略约束。\n- 与相邻概念的关系: Prompt 可以是 Skill 的一部分,Tool 可以被 Skill 引用,Harness 决定安装与运行边界,适配器处理平台差异。\n- 在 HeatStack Forge 中的位置: Forge 将需求抽取能力打包为带 Manifest、测试、资源清单和风险说明的 Skill,并适配三个目标平台。\n- 典型误用与修正: 误用是复制一个 Prompt 文件就宣称跨平台兼容。修正方法是声明依赖、入口、资源、版本、权限与验收测试。\n\n### 工具 tool\n\n- 它是什么: Agent 可通过明确参数、权限、执行边界和结果合同调用的受约束能力,例如函数、命令、数据库查询或 HTTP 客户端。\n- 它不是什么: 工具不等于 MCP,也不等于任意脚本。协议可以暴露能力,但 Harness 仍决定是否允许调用及如何处理副作用。\n- 与相邻概念的关系: Agent Loop 选择工具,Schema 约束参数,Harness 执行权限检查和隔离,Verification 检查结果是否推进目标。\n- 在 HeatStack Forge 中的位置: Forge 的 tool registry 保存合同与风险等级;执行前生成变更计划,高风险写操作等待确认,执行后记录结果与恢复信息。\n- 典型误用与修正: 误用是让模型根据工具名称自由拼参数。修正方法是严格 Schema、确定性校验、超时、幂等键和拒绝分支测试。\n\n
#

STAGE 01

用平台无关合同描述 Skill 需要的工具、资源和确认能力

先看一个具体问题#

如何声明 Skill 需要哪些能力,并区分必需与可选?#

你正在为 Forge 平台构建跨平台适配层,需要让同一个 Skill 在不同平台上安装时能够判断该平台是否满足其能力需求。

当前代码库中还没有 forge/adapters 包,也没有任何能力模型,因此测试 test_required_capabilities_are_reported 和 test_optional_capability_is_not_a_blocker 在导入阶段就失败了。

你需要创建一个平台无关的合同模型,用 CapabilityRequirement 表示单个能力需求(包含名称和是否必需),用 SkillContract 表示一个 Skill 的全部能力需求,并实现 missing_capabilities 函数来计算缺失的必需能力。

关键决策是:当某个能力被标记为可选时,即使平台没有提供该能力,也不应该阻止安装。

先做判断#

在实现 required_names 方法时,以下哪种做法能正确区分必需与可选能力?

  • 返回所有能力名称,不管 required 标志。
  • 只返回 required=True 的能力名称。
  • 只返回 required=False 的能力名称。
  • 返回能力名称和 required 标志的元组。

判断依据: 正确做法是只返回 required=True 的能力名称,因为 missing_capabilities 只应报告缺失的必需能力,可选能力缺失不应成为安装障碍。

它是什么#

平台无关能力合同是什么#

平台无关能力合同是一组数据结构,用于声明一个 Skill 在运行时需要哪些外部能力,而不关心这些能力在具体平台上如何实现。

CapabilityRequirement 封装了能力名称和一个布尔标志 required,默认值为 True,表示该能力是必需的。

SkillContract 聚合了一个 Skill 的所有能力需求,并提供 required_names 方法来提取所有必需能力的名称集合。

missing_capabilities 函数接收一个合同和一个可用能力集合,返回所有缺失的必需能力。

它不是什么#

平台无关能力合同不是什么#

它不是平台映射表,不负责将能力名称转换为具体平台的 API 或配置。

它也不是安装器,不会执行任何安装逻辑或检查平台是否真的具备这些能力。

它不包含任何平台特定的实现细节,只描述需求本身。

它与相邻概念的关系#

合同内部各组件的关系#

SkillContract 包含多个 CapabilityRequirement,每个需求都有名称和必需标志。

required_names 方法遍历所有需求,过滤出 required=True 的项,并返回它们的名称集合。

missing_capabilities 调用 required_names 得到必需能力集合,然后与可用能力集合做差集,得到缺失的必需能力。

本阶段的边界决定: 合同模型只负责描述需求,不负责平台映射或安装;判断平台是否满足需求由 missing_capabilities 完成,但实际安装决策由上层 Harness 根据缺失能力集合做出。

STAGE 02

把规范 Skill 合同映射为 Codex 目录、工具声明和确认策略

先看一个具体问题#

从规范合同到 Codex 安装计划#

你需要在 forge/adapters/codex.py 中实现 plan_codex 函数,把 SkillContract 转换为 CodexInstallPlan。

当前测试 test_codex_path_and_tools_are_mapped 和 test_codex_write_approval_is_preserved 都失败,因为模块尚未创建。

关键决策是:目标路径必须符合 Codex 的 skills/<skill_id>/SKILL.md 结构,工具列表必须来自合同要求,审批模式必须固定为 ask-before-writes。

如果审批模式被错误地设为 full-access,就会绕过写操作前的确认,导致权限提升。

先做判断#

在实现 plan_codex 时,审批模式应该如何处理?

  • 根据可用能力动态决定
  • 始终设为 ask-before-writes
  • 设为 full-access 以简化安装
  • 由调用者传入参数

判断依据: 正确选项是“始终设为 ask-before-writes”。适配器必须保留安全语义,不能因为平台差异而提升权限。

它是什么#

适配器是安全语义的翻译器#

适配器把规范合同中的能力要求映射为平台特定的安装计划,同时保持权限边界不变。

对于 Codex,这意味着生成正确的目录路径、工具声明和审批模式。

审批模式 ask-before-writes 确保任何写操作前都需要用户确认,这是合同隐含的安全要求。

它不是什么#

适配器不是权限决策者#

适配器不能随意更改权限模式,即使安装成功也不能提升权限。

它不负责执行安装,也不验证平台是否真正遵守审批模式。

适配器只是生成计划,实际执行由 Harness 控制。

它与相邻概念的关系#

合同、适配器与 Harness 的关系#

SkillContract 定义能力要求,适配器将其转换为平台计划,Harness 负责执行并强制权限。

missing_capabilities 函数用于检查可用能力是否满足合同,不满足时应抛出异常。

目标路径由 home 和 skill_id 拼接,工具列表来自合同的 required_names()。

本阶段的边界决定: 适配器必须保持安全语义,不能提升权限;审批模式固定为 ask-before-writes,即使平台支持更宽松的模式。

STAGE 03

生成 Claude Code 命令白名单,并拒绝无法表达的必需能力

先看一个具体问题#

为 Claude Code 平台生成命令白名单,并拒绝无法表达的必需能力#

当前仓库中只有 Codex 适配器,还没有 Claude 适配器,因此测试在导入 forge.adapters.claude 时直接失败。

你需要新建 forge/adapters/claude.py,实现 plan_claude 函数,将技能契约中的能力需求映射为 Claude Code 允许的工具列表。

如果某个必需能力无法在 Claude Code 中表达,适配器必须抛出 ValueError,而不是默认映射到通用 shell 访问。

本阶段的目标是让两个测试通过:test_known_capabilities_map_to_tools 和 test_unknown_required_capability_is_rejected。

先做判断#

当适配器遇到一个无法映射到 Claude Code 工具的必需能力时,最安全的做法是什么?

  • 默认映射到 Bash,因为 Bash 可以执行任何命令
  • 抛出异常,拒绝生成计划
  • 忽略该能力,继续生成计划
  • 记录警告并映射到只读工具

判断依据: 正确选项是“抛出异常,拒绝生成计划”。因为默认映射到 Bash 会扩大权限,可能让模型执行未预期的操作;忽略能力会导致技能无法正常工作;映射到只读工具同样不准确。适配器必须明确拒绝无法表达的必需能力,以保证安全性和语义正确性。

它是什么#

显式能力映射表#

显式能力映射表是一个从能力名称到平台工具名称的字典,例如 {"read_files": "Read", "write_files": "Edit", "shell": "Bash"}。

适配器只允许映射表中列出的能力,任何不在表中的能力都被视为无法表达,必须拒绝。

这种设计让能力边界清晰可见,避免意外扩大权限,也便于审计和测试。

它不是什么#

不是通用 shell 访问的别名#

显式映射表不是把未知能力默认映射到 Bash 的机制,那样会失去能力约束。

它也不是一个可以随意扩展的开放集合;添加新映射需要同时考虑平台是否真正支持该能力。

映射表只负责名称转换,不负责权限检查或执行隔离,这些由 Harness 层处理。

它与相邻概念的关系#

与技能契约和平台计划的关系#

技能契约(SkillContract)定义了技能需要哪些能力,适配器读取这些能力并转换为平台允许的工具列表。

plan_claude 返回的 ClaudePlan 包含技能文件路径和允许的工具元组,供后续部署使用。

如果契约中的能力不在映射表中,适配器通过 missing_capabilities 函数检测缺失并抛出 ValueError。

本阶段的边界决定: 适配器必须拒绝无法表达的必需能力,而不是扩大权限;这保证了平台计划的安全性和语义正确性。

STAGE 04

将能力映射到 WorkBuddy 工作区权限,并保留只读与写入的差异

先看一个具体问题#

从能力需求推导 WorkBuddy 工作区访问级别#

当前仓库中还没有 forge/adapters/workbuddy.py,因此测试在导入 plan_workbuddy 时直接失败,错误为 ModuleNotFoundError: No module named 'forge.adapters.workbuddy'。

本阶段需要创建该文件,实现 plan_workbuddy 函数,根据 Skill 的能力需求决定 WorkBuddy 工作区访问级别。

如果 Skill 需要 write_files 能力,则工作区访问级别应为 write;否则应保持 read,不能默认提升权限。

测试 test_write_skill_receives_write_access 和 test_read_only_skill_stays_read_only 分别验证写入和只读两种场景。

先做判断#

在实现 plan_workbuddy 之前,请预测:如果适配器总是请求写入权限,会发生什么?

  • 所有 Skill 都能正常工作,因为写入权限更强大。
  • 只读 Skill 会意外获得写入权限,违反最小权限原则。
  • 测试会全部通过,因为测试不检查权限级别。
  • 适配器会抛出异常,因为无法处理只读 Skill。

判断依据: 正确答案是第二个选项。如果适配器总是请求写入权限,只读 Skill 会意外获得写入权限,这违反了最小权限原则,并且测试 test_read_only_skill_stays_read_only 会失败。

它是什么#

WorkBuddy 适配器是什么#

WorkBuddy 适配器是一个将 Skill 的能力需求映射到 WorkBuddy 工作区访问级别的组件。

它接收 SkillContract 对象,检查其中是否包含 write_files 能力,然后生成一个 WorkBuddyPlan,其中包含配置路径和访问级别。

适配器必须根据能力需求推导访问级别,而不是硬编码为 write 或 read。

它不是什么#

WorkBuddy 适配器不是什么#

它不是一个通用的权限管理系统,也不负责执行实际的写入操作。

它不会自动提升权限,也不会忽略 Skill 的能力需求。

它不处理 shell 能力,如果检测到 shell 能力,会抛出 ValueError,因为 WorkBuddy 配置文件不暴露 shell。

它与相邻概念的关系#

与其他组件的关系#

plan_workbuddy 依赖于 SkillContract 的 required_names() 方法来获取能力名称集合。

WorkBuddyPlan 是一个冻结的数据类,包含 config_path 和 workspace_access 两个字段。

测试文件 tests/test_stage.py 导入 plan_workbuddy 并验证其行为,因此适配器必须与测试期望一致。

本阶段的边界决定: 适配器必须根据 write_files 能力是否存在来决定访问级别,不能默认提升权限;同时要拒绝 shell 能力,因为 WorkBuddy 不支持 shell。

STAGE 05

把三个平台的安装计划规范化为可比较、可审计的配置记录

先看一个具体问题#

如何将不同平台的安装计划统一为可比较的配置记录?#

当前 forge/adapters/config.py 文件尚未创建,因此 normalize_plan 函数不存在,导致测试在导入阶段就失败。

我们需要实现一个规范化函数,它接收平台标识符和安装计划数据类,返回包含 schemaVersion 和 configuration 的字典。

关键决策在于:规范化过程中必须验证平台标识符是否受支持,否则未知平台会混入发布配置,造成后续审计和部署风险。

先做判断#

在实现 normalize_plan 时,如果传入一个未知平台标识符(如 "mystery-agent"),函数应该如何处理?

  • 直接序列化并返回配置记录,因为规范化只负责格式转换
  • 抛出 ValueError,因为规范化必须验证平台标识符
  • 返回一个带有默认平台字段的配置记录
  • 忽略平台字段,只序列化计划数据类

判断依据: 正确选项是抛出 ValueError。规范化不是简单的格式转换,它必须确保平台标识符属于受支持的适配器注册表,否则未知平台会进入发布配置,破坏后续的可审计性和部署安全性。

它是什么#

规范化平台配置是什么#

规范化平台配置是将不同平台的安装计划转换为统一结构的过程,该结构包含平台标识符、模式版本和配置数据。

它必须验证平台标识符是否在受支持的适配器注册表中,并检查计划是否为数据类且包含安装路径。

规范化后的配置记录是可比较、可审计的,能够被下游部署流程安全地消费。

它不是什么#

规范化平台配置不是什么#

规范化不等于简单的序列化,它不能接受任意平台名称而不加验证。

规范化不负责执行安装或修改计划内容,它只负责将计划转换为标准格式并确保其符合基本约束。

规范化不是平台适配器本身,它不处理平台特定的通信协议或认证逻辑。

它与相邻概念的关系#

与其他组件的关系#

规范化函数 normalize_plan 被测试文件 tests/test_stage.py 直接调用,测试期望它验证平台并返回正确的 schemaVersion。

它依赖于 Python 的 dataclasses 模块来序列化计划数据类,并检查计划是否包含安装路径字段。

规范化后的配置记录将被后续的部署流程使用,因此必须保证平台标识符的有效性。

本阶段的边界决定: 规范化必须验证平台标识符,拒绝未知平台;同时检查计划是否为数据类且包含安装路径,否则抛出相应异常。

STAGE 06

区分阻塞缺失和可接受降级,并生成可解释的平台兼容报告

先看一个具体问题#

平台缺少可选能力时被错误判为不兼容#

在上一阶段,你已经把不同平台的原始配置规范化为统一的 SkillContract,其中每个能力都带有 required 标志。

现在需要实现 evaluate 函数,根据平台实际可用的能力集合生成 CompatibilityReport,报告必须区分阻塞缺失和可接受降级。

当前 forge/adapters/compatibility.py 文件尚不存在,运行测试会直接报 ModuleNotFoundError,因为测试文件 tests/test_stage.py 试图导入 evaluate。

如果简单地把所有缺失能力都放进 blockers,那么缺少可选能力 git 的平台也会被标记为不兼容,这违背了合同语义。

先做判断#

在实现 evaluate 之前,请预测:如果平台缺少一个标记为可选的能力(例如 git),CompatibilityReport.compatible 应该是什么?

  • False,因为任何缺失都意味着平台不完整
  • True,因为可选能力缺失不应阻塞兼容性
  • False,但把缺失项放入 degradations
  • True,但把缺失项放入 blockers

判断依据: 正确答案是 True,可选能力缺失只应记录为降级,不影响整体兼容性。只有必需能力缺失才会导致 compatible=False。

它是什么#

兼容性报告是合同与平台能力的差异分析#

CompatibilityReport 是一个不可变数据类,包含 compatible 布尔值、blockers 元组和 degradations 元组。

evaluate 函数遍历 SkillContract 中的每个 CapabilityRequirement,检查其名称是否在 available 集合中。

对于缺失的能力,根据其 required 标志决定放入 blockers 还是 degradations。

最终 compatible 仅由 blockers 是否为空决定,与 degradations 无关。

它不是什么#

兼容性报告不是简单的缺失列表#

它不把所有缺失能力混为一谈,而是保留每个能力的必需/可选语义。

它不负责修改平台配置或自动安装缺失能力,只做静态评估。

它不保证平台在运行时一定成功,只反映合同层面的静态兼容性。

它与相邻概念的关系#

与已有组件的关系#

SkillContract 和 CapabilityRequirement 来自 forge/adapters/capabilities.py,本阶段不修改它们。

evaluate 函数被测试文件 tests/test_stage.py 直接调用,测试期望返回的 CompatibilityReport 字段精确匹配。

后续阶段可能会使用该报告来决定是否允许部署或触发迁移流程。

本阶段的边界决定: 只有当缺失的能力的 required 属性为 True 时,才将其加入 blockers;否则加入 degradations,并且 compatible 只取决于 blockers 是否为空。

STAGE 07

用统一合同检查安装路径、权限不升级和能力覆盖,阻止不安全适配器发布

先看一个具体问题#

如何自动检查适配器计划是否符合安全与能力要求?#

在上一阶段,我们生成了兼容性报告,但报告只描述差异,并不会阻止不安全的适配器发布。

现在需要实现一个一致性检查函数 check_plan,它接收平台名、配置字典和所需能力集合,返回 ConformanceResult。

如果配置缺少安装路径、未覆盖所需能力,或者审批模式为 full-access 但所需能力中不包含 unattended_write,则必须判定为不通过并给出具体错误。

当前代码库中还没有 forge/adapters/conformance.py 文件,因此测试在导入阶段就会失败。

先做判断#

在实现 check_plan 之前,请预测:如果只检查配置是否非空,能否通过 test_permission_escalation_fails_conformance 测试?

  • 能通过,因为配置非空且包含 capabilities。
  • 不能通过,因为 full-access 审批模式会授予超出所需能力的权限。
  • 能通过,因为测试只检查 passed 是否为 False。
  • 不能通过,因为配置缺少 target_path。

判断依据: 正确答案是“不能通过,因为 full-access 审批模式会授予超出所需能力的权限”。该测试传入 approval_mode 为 full-access,但所需能力只有 read_files,这属于权限升级,必须被拒绝。仅检查非空无法发现这种语义违规。

它是什么#

一致性检查是什么#

一致性检查是对适配器生成的配置计划进行语义验证,确保其满足安全与能力合同。

它检查三个关键维度:安装路径是否存在且非空、所需能力是否被配置声明、审批模式是否导致权限升级。

检查结果通过 ConformanceResult 数据类返回,包含平台名、是否通过以及错误元组。

它不是什么#

一致性检查不是什么#

它不只是检查配置字典是否非空或字段是否存在,因为形状正确不代表语义安全。

它不负责实际执行安装或修改系统状态,只对计划进行静态分析。

它不能替代领域验收,也不能保证模型输出永远正确,只验证适配器计划是否符合预定义规则。

它与相邻概念的关系#

与其他组件的关系#

check_plan 使用配置字典和所需能力集合作为输入,这些数据来自 Skill 的元数据和适配器生成的计划。

ConformanceResult 的 errors 元组会被测试断言,例如 test_permission_escalation_fails_conformance 检查 errors 中包含 “permission escalation”。

该检查位于 forge/adapters/conformance.py,与 capabilities.py、compatibility.py 等模块共同构成适配器层。

本阶段的边界决定: 一致性检查必须验证语义,包括权限不升级和能力覆盖,而不仅仅是配置形状。如果只检查非空,就会批准授予未声明 full-access 的适配器,导致安全风险。

完成本章#

让同一个 Forge Skill 通过适配器部署到三个平台。

FORGE / LOCAL CHECK

本地实验自检

  1. 未开始
  2. 2阅读中
  3. 3实验已下载
  4. 4测试结果已读取
  5. 5本地自检通过

verification.json 只在当前浏览器中解析,不会上传。这里验证的是实验合同,不是服务器认证或第三方背书。

概念校准与一周复习

三道题检查你是否掌握了本章边界、交付证据和恢复方法。答案只保存在当前浏览器。

  1. 1哪一项最能证明你真正完成了「多平台适配与迁移」?
  2. 2关于「Harness」,哪一种理解最准确?
  3. 3实验卡住时,哪个动作是本章建议的最小恢复点?