尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Harness Engineering实践:用OpenSpec与SDD给AI这匹野马套上缰绳

Harness Engineering实践:用OpenSpec与SDD给AI这匹野马套上缰绳 1. 为什么你的 AI 编程像在赌运气Harness Engineering 要解决的三个失控点如果你最近用 OpenCode 或 Claude Code 写过稍大一点的功能大概率经历过这种场景第一轮对话生成的代码跑得通第二轮加个需求就开始改坏旧逻辑第三轮你已经在手动回滚了。这不是模型不行而是缺少一套把「人的意图」翻译成「机器可执行约束」的中间层。Harness Engineering驾驭工程这个词在 2026 年频繁出现本质上就是在回答一个问题怎么让 AI 的产出从「碰运气」变成「可复现」。我把它拆成三个失控点来看。第一个是意图漂移你在对话里说「加一个租户网络隔离」AI 可能理解成 VLAN 隔离、VRF 隔离或者安全组隔离三种实现方向完全不同但它不会主动问你而是直接选一个开始写。第二个是上下文腐烂当项目文件超过几十个Agent 每次读取的上下文窗口里塞满了无关代码注意力被稀释开始忽略你早期定下的架构规则。第三个是反馈缺失AI 写完代码没有自动化的校验告诉它「你违反了 Domain 层不能依赖 Infrastructure 层」它就会一直错下去直到你人工发现。Harness Engineering 的核心要义可以凝练成三根支柱过程可控、结果可信、反馈闭环。过程可控靠的是 SDD规格驱动开发和 OpenSpec 这类工具把模糊需求硬化为结构化任务清单结果可信靠的是测试前置和 Linter 硬拦截反馈闭环靠的是把每次踩坑提炼成规则让 Agent 下次自动遵守。这三件事听起来像老生常谈的软件工程但放在 AI 编程场景下执行方式完全变了——你不再是写代码的人而是设计「代码工厂」的人。这篇文章面向的是已经在用 OpenCode、Cline 或 Claude Code 做团队协作的开发者。我会交付一套可复制的 OpenSpec 目录结构、一份 SDD 约束模板并演示从需求到代码的完整验证流程。同时会说明怎么通过 TaoToken 统一 Key 和 API 通道接入 OpenCode让调用链路可观测。如果你还在用「对话式编程」单打独斗这套方法能帮你把 AI 产出纳入工程化轨道。2. OpenSpec 与 SDD 前置目录结构、约束模板与 OpenCode 接入在进入具体配置之前先厘清三个工具的分工。OpenCode 是执行引擎负责调用模型把逻辑转成代码OpenSpec 是规约协议把人的意图固化成机器可读的约束文件SDD 是方法论规定「先写规格再写代码」的流程。三者关系类似SDD 是交通法规OpenSpec 是铺好的铁轨OpenCode 是跑在铁轨上的列车。2.1 OpenSpec 初始化后的目录结构在项目根目录执行openspec init后会生成一套标准化的工程约束目录。我实测下来核心结构如下project-root/ ├── openspec/ │ ├── specs/ # 规格文件按能力域拆分 │ │ ├── tenant-network/ │ │ │ ├── spec.md # 需求规格做什么、不做什么 │ │ │ └── design.md # 设计决策为什么这么做 │ │ └── site-init/ │ │ ├── spec.md │ │ └── design.md │ ├── changes/ # 变更提案每次迭代一个目录 │ │ └── add-tenant-plane/ │ │ ├── proposal.md # 变更背景与影响范围 │ │ ├── design.md # 技术方案 │ │ └── tasks.md # 原子化任务清单 │ └── config.yaml # OpenSpec 全局配置 ├── AGENTS.md # Agent 行为规约简短指南 └── .opencode/ └── commands/ # 自定义命令如 e2e-test.md这套结构的关键在于changes/目录。每次需求变更都新建一个子目录里面三份文件分别回答「为什么改」「怎么改」「分几步改」。tasks.md是 AI 的行动指南颗粒度要细到「创建一个 Vlan 值对象校验范围 100-4000」这种级别而不是「实现网络隔离功能」这种模糊描述。2.2 SDD 约束模板tasks.md 的写法SDD 的核心是「文档即真理代码只是副产品」但我必须提前说一句这个口号在项目初期有效进入迭代中后期会变成负担。不过tasks.md作为阶段性约束工具价值是实打实的。一份合格的 tasks.md 长这样## 1. Project Setup - [ ] 1.1 Create Maven project with Spring Boot 3 parent, JDK 21 - [ ] 1.2 Configure COLA layer package structure (adapter, app, domain, infrastructure) - [ ] 1.3 Add dependencies: Spring Web, Spring Data JPA, PostgreSQL driver, Liquibase - [ ] 1.4 Configure application.yml with database connection and Liquibase - [ ] 1.5 Create main Spring Boot application class ## 2. Domain Layer - Value Objects - [ ] 2.1 Create Vlan value object with validation (100-4000) - [ ] 2.2 Create Vrf value object with naming convention (VRF001-VRF999) - [ ] 2.3 Create Vendor enum (HUAWEI, H3C) - [ ] 2.4 Create status enums (SiteStatus, TenantStatus, ServerStatus, SwitchStatus) - [ ] 2.5 Create domain exceptions (VrfExhaustedException, VlanExhaustedException) ## 3. Domain Layer - Entities - [ ] 3.1 Create Site entity (id: String, name, switches, status) - [ ] 3.2 Create Switch entity (id: String, siteId: String, ipAddress, vendor, credentials, role, status) - [ ] 3.3 Create Tenant entity (id: String, siteId: String, vrf: Vrf, status) - [ ] 3.4 Create NetworkPlane entity (id: String, tenantId: String, vlan: Vlan, cidr) - [ ] 3.5 Create Server entity (id: String, networkPlaneId: String, switchId: String, portName, status)注意每个任务都是可验证的原子操作。AI 完成一项勾一项你 Review 时也能快速定位问题。这种结构化清单是拒绝「黑盒式开发」的关键武器。2.3 通过 TaoToken 接入 OpenCode 的配置OpenCode 不绑定任何模型厂商通吃主流 MaaS 平台。为了统一 Key 管理和调用链路可观测我建议通过 TaoToken 接入。TaoToken 提供统一的 API 通道Base URL 为https://taotoken.net/api你可以在控制台创建 Key 后在 OpenCode 配置中指定。OpenCode 的配置文件通常位于~/.config/opencode/opencode.jsonc。以下是接入配置片段{ $schema: https://opencode.ai/config.json, provider: { taotoken: { npm: ai-sdk/openai-compatible, name: TaoToken, options: { baseURL: https://taotoken.net/api, apiKey: sk-你的TaoToken密钥 }, models: { glm-4.7: { name: GLM-4.7, limit: { context: 128000, output: 8192 } } } } }, model: taotoken/glm-4.7 }这里三件套必须写全Base URL 是https://taotoken.net/apiKey 从 TaoToken 控制台的 API Keys 页面获取Model ID 填glm-4.7或你实际使用的模型。配置完成后OpenCode 的所有请求都会经过 TaoToken 通道你可以在控制台看到调用量、延迟和错误率。对于团队协作场景这意味着每个人的 Key 可以独立管理权限和配额也能分开控制。如果你用的是 Claude Code 或 Cline接入逻辑类似只是配置文件路径不同。Claude Code 需要设置ANTHROPIC_BASE_URL和ANTHROPIC_API_KEY环境变量Cline 在 VS Code 设置里填 Base URL 和 Key。核心原则不变统一通道统一观测。3. 可复制配置OpenSpec 约束模板与 OpenCode 命令硬化这一节交付可以直接复制到项目里的配置片段。我会给出 OpenSpec 的规格模板、AGENTS.md 的约束写法以及 OpenCode 自定义命令的配置。这些文件的作用是把「人的审美偏好」和「架构规则」固化成 AI 可执行的硬约束。3.1 spec.md 规格模板spec.md回答「做什么」和「不做什么」。以下是一个租户网络平面创建的规格模板# Tenant Network Plane Spec ## 能力描述 支持在边缘云站点创建租户专属的网络平面通过 VLAN 和 VRF 实现租户间二层/三层隔离。 ## 输入 - siteId: 站点标识 - tenantId: 租户标识 - cidr: 网段如 10.0.1.0/24 ## 输出 - networkPlaneId: 网络平面标识 - vlanId: 分配的 VLAN ID - vrfName: 分配的 VRF 名称 ## 约束 - VLAN ID 范围 100-4000全局唯一 - VRF 名称格式 VRF001-VRF999租户内唯一 - 创建失败必须回滚已下发的交换机配置 - 不允许跨租户复用 VLAN ## 不做什么 - 不负责物理交换机发现 - 不负责租户认证鉴权 - 不处理跨站点网络互通「不做什么」这一节经常被忽略但它能有效防止 AI 过度设计。我试过在规格里明确写「不负责物理交换机发现」AI 就不会自作主张去加 SNMP 扫描逻辑。3.2 AGENTS.md 约束写法AGENTS.md是 Agent 的行为规约要简短只放最高优先级的规则。OpenAI 的 Harness 实践建议把它当作「简短指南」详细内容通过 SubAgent 渐进式披露。以下是我在项目中使用的片段# AGENTS.md ## 架构约束 - 严格遵循 COLA 四层架构adapter - app - domain - infrastructure - Domain 层不得依赖 adapter、app、infrastructure 任何一层 - 领域对象必须包含业务方法禁止贫血模型 ## 代码质量 - 单方法长度不超过 30 行超过必须用组合方法模式重构 - 圈复杂度不超过 10 - 所有 public 方法必须有单元测试 ## 环境自愈 - 启动服务前先用 netstat 探测端口占用 - 8080 端口被无关进程占用时销毁进程重新拉起 - 3000 端口离线时进入前端目录执行 npm run dev这些规则不是写在 Prompt 里求 AI 遵守而是配合 Linter 做硬拦截。下一节会讲怎么用 ArchUnit 把架构约束变成测试用例。3.3 OpenCode 自定义命令配置OpenCode 支持自定义命令把重复性操作封装成可召唤的指令。在.opencode/commands/目录下新建e2e-test.md# E2E Test Command ## 前置检查 1. 执行 netstat -ano | findstr LISTENING 探测 8080 和 3000 端口 2. 若 8080 被占用执行 taskkill /F /PID pid 后重新拉起后端 3. 若 3000 离线进入前端目录执行 npm run dev ## 后端启动 powershell powershell -Command Start-Process cmd -ArgumentList /k cd crm-backend mvn spring-boot:run -Dserver.port8080前端启动powershell -Command Start-Process cmd -ArgumentList /k cd crm-frontend npm run dev浏览器自动化等待 7 秒后通过 chrome-devtools-mcp 驱动无头浏览器执行全链路回归。对应的 MCP 注册配置在 opencode.jsonc 中 jsonc { mcp: { chrome-devtools: { type: local, command: [npx, -y, chrome-devtools-mcplatest] } } }这套配置的意义是把「环境玄学」转化成 Agent 可执行的标准操作程序。当 Agent 能自主处理端口冲突、服务拉起超时这些杂讯时才真正具备无人值守交付的能力。4. 验证请求从需求到代码的完整调用链路配置写好了接下来验证整条链路是否跑通。我会用一个真实场景演示从 OpenSpec 探索需求到生成提案再到 OpenCode 执行代码生成最后用 ArchUnit 验证架构约束。每一步都有可复制的命令和预期结果。4.1 需求探索openspec-explore在 OpenCode 对话中输入以下 prompt/openspec-explore 创建一个新微服务管理租户在边缘云的 underlay 网络 通过租户网络平面实现租户间网络隔离该服务通过 Netconf 协议统一管理 边缘云站点的交换机配置支持站点初始化、租户网络平面创建/删除、 裸机实例网络配置等核心功能。现在先搭建应用框架要求使用 COLA 四层架构 MavenJDK21SpringBoot3OpenSpec 会发起一连串澄清问题覆盖业务逻辑、领域建模、部署拓扑、驱动层适配和架构选型。比如它会问「租户网络隔离是二层 VLAN 隔离还是三层 VRF 隔离」或者「不同厂商交换机的适配策略是策略模式还是工厂模式」。这些问题不是刁难而是用大模型的领域知识帮你查漏补缺。4.2 生成提案openspec-proposal需求澄清后执行/openspec-proposalOpenSpec 会在changes/目录下生成proposal.md、design.md和tasks.md。此时必须开启 Spec Review 模式。AI 有时会陷入「模式教条」比如在本项目中它过于推崇 DDD 的值对象把所有业务 ID 都建模成 VO。这显然增加了不必要的复杂度。我直接下达指令取消 ID 的 VO 建模回归简单类型。AI 随即更新所有设计文档和任务列表。这种「人类设定约束 - AI 调整蓝图 - 人类最终确认」的迭代确保 tasks.md 精准不跑偏。4.3 代码生成openspec-apply确认 tasks.md 后执行/openspec-applyOpenCode 开始按任务清单逐项生成代码。此时你要保持审视AI 在实现层更像一个「唯结果论」的平庸程序员能跑通逻辑但对可读性和复用性缺乏感知。它生成的代码是「草稿」不是「成品」。比如「创建租户平面」这个用例AI 最初把所有步骤塞在一个大方法里。我用组合方法模式重构后逻辑变成清晰的业务清单public CreateTenantPlaneResp createTenantPlane(String siteId, CreateTenantPlaneReq req) { Context ctx prepareContext(siteId, req); try { networkGateway.save(ctx.getNetwork()); vlanifGateway.save(ctx.getVlanif()); createVlanifAndBindVrfOnCoreSwitches(ctx.getCoreSwitches(), ctx.getVlanif(), ctx.getVlanId()); publishRoutesOnCoreSwitches(ctx.getCoreSwitches(), ctx.getVrf().getName(), ctx.getCidrs()); switchConfigService.allowVlanOnPorts(ctx.getCoreSwitches(), ctx.getSite().getSiteId(), PortRole.DOWNLINK, ctx.getVlanId()); switchConfigService.allowVlanOnPorts(ctx.getTorSwitches(), ctx.getSite().getSiteId(), PortRole.UPLINK, ctx.getVlanId()); createTenantBgpConfigOnCoreSwitches(ctx); networkGateway.updateStatus(ctx.getNetwork().getId(), NetworkStatus.ACTIVE, ); return new CreateTenantPlaneResp() .setId(ctx.getNetwork().getId()) .setVrfId(ctx.getVrf().getId()) .setVlanifId(ctx.getVlanif().getId()); } catch (Exception e) { log.error(Create tenant plane failed, rolling back. networkId{}, ctx.getNetwork().getId(), e); rollbackConfig(ctx); saveFail(req, e, ctx); throw new VlanException(Create tenant plane failed: e.getMessage()); } }另一个常见问题是逻辑散落。AI 经常把「从 CIDR 推断 Gateway IP」写在 Service 里我把它下沉到 Cidr 领域对象public String getGatewayIp() { IPAddress addr new IPAddressString(this.cidr).getAddress(); if (addr null) { throw NetworkErrorDefine.INVALID_IP_FORMAT.render(Invalid CIDR format: this.cidr).toVlanException(); } return addr.toSequentialRange().getLower().increment(1).toString(); }4.4 架构验证ArchUnit 硬拦截架构约束不靠 Prompt靠 Linter。以下 ArchUnit 测试用例是架在代码仓上的「高压线」class ColaArchitectureTest { ArchTest static final ArchRule domain_should_not_depend_on_other_layers noClasses() .that().resideInAPackage(..domain..) .should().dependOnClassesThat() .resideInAnyPackage(..adapter.., ..app.., ..infrastructure..) .because(Domain layer should be the core and not depend on other layers); }Agent 无论如何挣扎只要代码不达标Pipeline 就无法通过。这种「报错-修正」的闭环比任何苦口婆心的 Prompt 都有效。同样方法长度超过 30 行、圈复杂度过高等质量红线全部硬化为 CodeCheck 的 Linter 规则。4.5 验证调用链路可观测性配置 TaoToken 后在控制台可以看到每次 OpenCode 请求的调用记录。重点观察三个指标请求延迟、Token 消耗、错误率。如果某个任务的 Token 消耗异常高说明上下文可能被无关文件污染需要检查 AGENTS.md 是否过长或 specs 目录是否有过期文档。调用链路可观测是团队协作的基础没有这个你无法定位「为什么今天 AI 产出质量下降」这类问题。5. 本篇常见错排查401、local proxy failed、reading choices 与 OAuth配置和调用过程中最容易卡在几个典型报错上。这一节按报错信息逐一排查每个都给出根因和修复步骤。5.1 401 Unauthorized报错原文Error: 401 Unauthorized - invalid api key根因TaoToken 的 Key 没填对或者环境变量没生效。OpenCode 读取配置的优先级是环境变量 配置文件 默认值。如果你在opencode.jsonc里填了 Key但环境变量里有一个空的TAOTOKEN_API_KEY环境变量会覆盖配置文件。修复检查opencode.jsonc中apiKey字段是否以sk-开头且没有多余空格。如果使用环境变量执行echo $TAOTOKEN_API_KEYLinux/Mac或echo %TAOTOKEN_API_KEY%Windows确认值正确。三件套必须写全Base URL 是https://taotoken.net/apiKey 从控制台 API Keys 页面获取Model ID 填实际模型名。5.2 local proxy failed报错原文Error: local proxy failed - connection refused根因OpenCode 尝试通过本地代理转发请求但代理服务没启动。常见于你之前配置过代理后来关掉了但配置没清理。修复检查opencode.jsonc中是否有proxy字段如果有且指向http://127.0.0.1:xxxx直接删除该字段。OpenCode 会走直连。同时检查系统环境变量HTTP_PROXY和HTTPS_PROXY如果指向不存在的本地端口清空它们。5.3 reading choices 报错报错原文Error: reading choices - unexpected end of JSON input根因模型返回的响应被截断通常是max_tokens设置过小或者网络传输中断。在 OpenCode 中如果limit.output设置低于 4096长代码生成容易被截断。修复在opencode.jsonc的模型配置中把limit.output调到 8192 或更高。同时检查网络稳定性如果使用 TaoToken 通道可以在控制台看请求是否完整返回。如果问题持续尝试降低单次任务复杂度把大任务拆成多个小任务。5.4 OAuth 相关报错报错原文Error: OAuth token expired - please re-authenticate根因如果你用的是 Claude Code 或某些需要 OAuth 的客户端Token 过期后没有自动刷新。OpenCode 本身不依赖 OAuth但如果你在 OpenCode 中调用了需要 OAuth 的 MCP 服务也会出现这个报错。修复对于 Claude Code执行claude auth logout后重新claude auth login。对于 MCP 服务检查对应的 OAuth 配置重新授权。如果你通过 TaoToken 统一接入建议把 OAuth 类服务也走 TaoToken 通道减少独立认证的复杂度。5.5 CC Switch / Cline MCP / Codex auth.json 三件套检查如果你使用 CC Switch 切换模型配置或者用 Cline 的 MCP 功能或者手动编辑 Codex 的auth.json必须确保三件套完整配置项正确值常见错误Base URLhttps://taotoken.net/api漏掉/api或写成https://taotoken.netAPI Keysk-开头复制时带空格或换行Model IDglm-4.7或实际模型名填成gpt-4等不存在的模型Cline 的 MCP 配置在 VS Code 设置中搜索cline.mcp找到配置项。Codex 的auth.json通常位于~/.codex/auth.json确保api_key字段值正确。CC Switch 的配置文件在~/.cc-switch/config.json检查providers数组中的baseUrl和apiKey。6. 从代码手艺人到工厂设计师TaoToken 统一通道的长期价值走到这里你已经有了 OpenSpec 的目录结构、SDD 的约束模板、OpenCode 的接入配置以及一套排障手册。但真正决定这套体系能否长期运转的是调用链路的统一管理。团队协作中最怕的不是 AI 写错代码而是「不知道谁在什么时候用了什么模型、花了多少 Token、为什么今天产出质量下降」。TaoToken 在这个环节的价值是把分散的 Key 和调用记录收拢到一个控制台。具体来说你可以为每个团队成员创建独立的 API Key在 TaoToken 控制台设置配额和权限。当某个成员的调用量异常增长或者错误率突然升高你能第一时间定位到具体的人和任务。对于长期编码和 Agent 场景Coding Plan 提供了更稳定的通道保障适合把 OpenCode 作为日常开发主力的团队。如果你还在验证阶段可以先用模型对话功能测试不同模型在具体任务上的表现再决定主力模型。回到 Harness Engineering 的本质你不是在写代码而是在设计一套让 AI 稳定产出代码的系统。这套系统包括规格约束OpenSpec、执行引擎OpenCode、反馈闭环ArchUnit Linter、可观测通道TaoToken。四者缺一不可。我踩过的坑是初期只关注 Prompt 优化忽略了 Linter 硬拦截结果 AI 反复违反架构规则人工 Review 成本极高。后来把规则硬化成测试用例Review 压力才降下来。最后给一个实用建议不要期望一开始就构建出完整的 Harness。每个项目的环境不一样好的 Harness 是迭代生长出来的。从最简单的 tasks.md 开始发现一个问题就加一条规则慢慢把飞轮转起来。当你的 Harness 足够强大时AI 的算力才会真正变成可控的生产力。
返回列表