从想法到发布:Harness Engineering 的一次完整实践

TLDR

《Harness Engineering 又来颠覆了》提出了三条原则,《蒸馏 Google 工程基因》介绍了 agent-skills------但「状态持久化」那条原则一直没有落地答案。这篇用一个真实项目 jk 走完一次:idea-refine 把模糊需求逼出三个核心设计决策,OpenSpec 把决策切成 17 组可执行任务并跨越多次 context 压缩,agent-skills 在关键节点自动介入。从「我想做个 Jenkins CLI」到 v0.1.0 发布,每一步都有凭据。

一、两个未竟的问题

Harness Engineering 又来颠覆了》结尾留了一个悬念:AI coding 不会替你省掉软件工程的基本功,它会让你欠的债加倍奉还。三条债:可见性、状态持久化、质量门禁。但怎么还,没说。

蒸馏 Google 工程基因》给出了一半答案:agent-skills 把高级工程师的判断力编码成 20 个结构化 skill,覆盖了质量门禁(TDD、code review、显式化假设),也覆盖了可见性(spec-driven-development 把假设和边界写进 repo,跟代码一起演化)。但有两个问题没有解决:

状态持久化没有答案。 agent-skills 管的是「一次任务怎么做」,管不了「多次 session 之间项目状态怎么延续」。agent 没有记忆,context 会压缩,上一次做到哪里、踩了什么坑、下一步是什么------skill 本身解决不了这个问题。

没有一次完整的实践记录。 《蒸馏 Google 工程基因》讲的是 agent-skills 是什么、怎么设计的。但实际用起来是什么感觉,两个工具碰在一起会发生什么,从来没有跑通过一次。

OpenSpec 是缺失的那块。它的核心设计是 spec 与 change 分离:spec 是意图载体,跨 session 稳定;change 记录执行状态,append-only。agent 每次进来,读 spec 知道要做什么,读 change 知道做到哪了。context 可以压缩,项目状态不会丢。

加上 agent-skills,三条原则才有了完整的覆盖。jk 是这套组合跑通的一次真实记录------一个 Pipeline-native 的 Jenkins CLI,从「想法」到「v0.1.0 发布 + Homebrew tap 自动更新」,全程用这套工作流完成。

二、从一句话到三个设计决策

「我想做一个 Jenkins CLI 工具。」

agent 拿到这句话会写出东西------但大概率不是你要的那个。它没有说为什么现有工具不够用,没有说用户是谁、在什么场景下用、什么叫做完。

这是 idea-refine 存在的原因。它不是头脑风暴工具,是一个把隐藏假设逼出来的结构化对话流程:先发散、再收敛、最后输出一份可以直接驱动后续工作的 one-pager。

在 jk 的开发里,idea-refine 经历了三轮关键交互,每轮都有一个具体的转折:

第一轮:「替代浏览器 Tab」和「快速出结果」之间的张力。

agent 直接点出来:这两个目标会打架。真正「替代浏览器 Tab」需要宽泛的功能覆盖,「快速出结果」需要窄而深。这是 jk 的第一个实质性决策:不做通用工具,只做 Pipeline operator 的日常工具------触发、监控、看日志、回应 input step。

第二轮:URL-as-identity。

「你怎么指定要操作哪个 Jenkins 实例?」这个问题的常见答案是 profile、context、配置文件。但实际使用习惯是:直接粘贴浏览器里的 Jenkins URL。agent 从这个行为模式里提炼出了核心设计哲学:URL 即身份。每个命令以 Jenkins URL 为输入,hostname 隐式决定使用哪份凭证,无 profile、无 context、无 use

这不是一个功能,是整个 CLI 的交互范式。

第三轮:schema 设计反转。

最初的设想是「原样反映 Jenkins API 的响应」。用户提出:「我无法控制 Jenkins 版本。」agent 意识到这意味着什么------Jenkins 升级改字段,所有依赖 jk 输出的脚本集体崩溃。当场反转:jk 维护自有 schema,由 jk 的版本号控制,不随 Jenkins 升级漂移。--raw 作为逃生舱留着。

schemaVersion: "1" 出现在每条命令输出的第一行,是这次反转留下的直接痕迹。

三轮之后,「我想做一个 Jenkins CLI」变成了可以驱动实现的设计约束。这些约束直接体现在最终产品里:jk auth add 只需要一个 URL,不需要给实例起名字、不需要选 profile。

idea-refine 做的事,对应的正是《Harness Engineering 又来颠覆了》里的可见性原则:「Agent 看不见的就不存在。」这三个设计决策如果没有在开始就显式化,会在实现阶段以 bug、返工、和「这不是我想要的」的形式还回来。

三、两套 spec 打架的那一秒

idea-refine 的产出是一份 one-pager,OpenSpec/opsx-propose 负责把它拆成可执行的 artifacts。

/opsx-propose 生成了四类文件:

  • proposal.md------做什么、为什么
  • design.md------10 个技术决策
  • 7 份 specs/*/spec.md------auth、build、errors、output、pipeline、tls-and-transport、url-resolution
  • tasks.md------17 组、约 80 个 checkbox,把整个实现切成可执行的最小单元

artifacts 创建完成后,手动触发了 spec-driven-development。

然后问题出现了。

spec-driven-development 的第一步是写项目级 spec------一份定义「在这个 repo 里怎么干活」的文档:构建命令、目录布局、代码风格、边界规则。但 OpenSpec 刚刚已经生成了 7 份 specs/*/spec.md。两套文档,都叫 spec,放在同一个 repo 里。

agent 加载 skill 后的第一个输出,直接处理了这个冲突:

We've completed the OpenSpec flow (proposal/design/specs/tasks), but the project-level spec that spec-driven-development calls for is a different dimension of document --- it's not about what capabilities we're building, but about how we work in this repo (build commands, layout, style, guardrails).

The two are complementary, not conflicting:

  • OpenSpec specs = what the product does (capabilities & requirements)
  • Project spec = how we work (toolchain, layout, style, guardrails)

裁决结果:SPEC.md 放在 repo 根目录,作为「项目宪法」;openspec/specs/ 管产品行为;两者边界从此清晰,永不合并

这条边界在开发过程中一直有效。一个具体的例子:开发后期把「credentials 加密留作 v0.2」这条决定从 SPEC.md 的 Open Questions 转为 Past Decisions------这是 engineering practice 层面的判断,改 SPEC.md,不动 OpenSpec。如果决定的是「新增 jk auth encrypt 命令」,那就必须开新的 OpenSpec change,因为那是 product behavior。

这条边界不是自然存在的。它是两个工具碰撞的那一秒,由 skill 即时裁决出来的。如果没有 spec-driven-development 的介入,SPEC.mdopenspec/specs/ 很可能会在实现阶段互相打架------改了这边,那边过时;agent 下次进来,不知道以哪个为准。

四、tasks.md:让「下一步做什么」的决策成本归零

spec 的边界划清之后,剩下的问题是:怎么执行。tasks.md 就是答案。

/opsx-propose 生成的 tasks.md 里有 17 个任务组,约 80 个 checkbox。每个 checkbox 都是一个独立可执行的最小单元,有明确的完成标志。比如第 4.5 条:

Unit tests: every URL in docs/spikes/urls.txt parses correctly; rejected URLs produce clear errors

不是「写单元测试」,是「这些输入,这些输出,测试通过才算完」。checkbox 的粒度就是验收标准的粒度。

agent 每次进来,第一件事是找下一个 - [ ]------不需要上下文重建,不需要问「我们做到哪了」。但更关键的是 context 压缩发生时的行为。

jk 的开发跨越了多次 session,context 被压缩了不止一次。agent 在 context 压缩时自动生成摘要,摘要里有一个固定的 Goal 段落,每次都完整保留了整个开发流程的标记:

复制代码
Goal: Design, scaffold, and incrementally implement jk via
idea-refine → OpenSpec change → SPEC.md → code flow.
Current: Task group 9 (output rendering), 3 of 5 checkboxes done.

这一行是**「记忆脊柱」**。它不依赖 conversation history,不依赖外部工具,只要 tasks.md 在 repo 里,压缩摘要就能精确定位到「上次做到哪了」。

这正是 OpenSpec 的 tasks.md 与普通 TODO list 的本质区别:TODO list 的状态活在你的记忆或外部工具里;tasks.md 的状态活在 repo 里,跟代码一起被 git clone,被压缩摘要引用。context 可以压缩,项目状态不会丢。

jk build triggertasks.md 第 11 组的产物------trigger + 实时状态轮询 + 退出码等于 build 结果。这条命令能存在,是因为在 context 压缩了三次之后,agent 还能找到并完成它对应的 checkbox。

这是《Harness Engineering 又来颠覆了》里「状态持久化」原则最具体的落地形态。

五、没有人要求,但它自己加载了

实现 internal/jenkinsurltasks.md 第 4 组,共 5 个 checkbox:定义 Ref 结构体、实现 Parse()HostKey()APIPath(),以及单元测试。

任务描述里没有出现「TDD」这个词。

agent 开始动手之前,输出了这样一段判断:

Loading test-driven-development skill since this package has well-defined inputs/outputs and table-driven tests are the natural fit.

然后它加载了 test-driven-development skill,按 RED-GREEN-REFACTOR 顺序推进:先把 tasks.md 里「10 个真实 Jenkins URL 样本」的 spec scenarios 全部写成失败的测试,再让测试通过,最后清理实现。

没有人要求这样做。

skill 的作用不是提供步骤,而是让正确的 pattern 在合适的时机自动发生。agent 识别到「inputs/outputs 明确」这个信号,就知道该用哪套 pattern------不需要人来提醒,不需要 prompt 里写「请使用 TDD」。这正是《蒸馏 Google 工程基因》里说的:把高级工程师的判断力编码成 agent 无法绕过的结构性约束。

internal/jenkinsurl 是第一个,但不是唯一一个。整个 schema 层(internal/schema/internal/output/)都是用同样的方式推进的------先把 spec scenario 里的字段写成测试断言,再让实现通过。

jk build status -o json | jq 的输出是结果:

每个字段在写实现之前就已经是测试的断言目标。TDD 驱动出来的不只是覆盖率,是 schema 稳定性。

质量门禁不是人来守的,是提前写进 skill 的信号识别逻辑里的。

六、v0.1.0 发布了,archive 还没做

tag v0.1.0 打了,CI 全绿,brew install addozhang/tap/jk 可用------从外面看,这个项目完成了。

openspec/changes/init-jk-jenkins-cli/ 这个 change 还没有 archive。

推迟的原因不是懒,是因为 archive 有一个前提:spec 被验证了

/opsx-archive 的作用是把 change 里的所有决定------design.md 的 10 个技术判断、7 份 spec 的 scenario 定义------封存进主 spec,成为下一个 change 的基线。封存之后,这些决定就是既成事实------新的 agent 进来,读 spec,知道这些事已经决定了,不需要重新讨论。

这是 spec 的封存,不是功能的完成。

v0.1.0 的实现覆盖了 tasks.md 的 76 个 checkbox,4 个显式留到 v0.2。代码是完整的,但有几条 spec scenario 在真实 Jenkins 环境里还没有跑过------jk build logs -f 的流式输出,在测试环境里 mock 过,但没有对着生产 pipeline 用过。这是第一个要 dogfood 的命令。

等这条命令在真实环境里稳定跑起来,再开 /opsx-archive

三条债,这是最后一条还清的时机------不是 tag 打了,而是 spec 的假设被现实确认了。

功能完成和 spec 封存,是两件事。

相关推荐
ALINX技术博客2 小时前
ALINX 亮相 2026 WAIC 世界人工智能大会,展示 AI 视觉 FPGA+GPU 异构计算与电子后视镜解决方案
人工智能·ai·fpga·世界人工智能大会·电子后视镜
JaydenAI2 小时前
[AG-UI详解-01]创建一个类似ChatGPT应用与Agent实时交互
ai·agent·ag-ui·maf
xcLeigh12 小时前
Doubao-Seed-Evolving大模型接入教程|搭建全品类提示词+AI工具导航网页
前端·人工智能·python·ai·html·ai开发·豆包
俊哥V12 小时前
每日 AI 研究简报 · 2026-07-20
人工智能·ai
元闰子13 小时前
怎样才算是好的 Agent 记忆系统?
ai
GPUStack13 小时前
Day 0 实测|在 GPUStack 上部署 Inkling-BF16:8 卡 H20-141G 推理性能测试
ai·大模型·llm·gpu·vllm·gpu集群·sglang·gpustack
云卷云舒___________13 小时前
阿里开源Qwen3.8 2.4T模型预览版、并推出Token Plan订阅、DeepSeek Harness与V4正式版同发 | 7月20日 AI日报
ai·阿里·deepseek·ai日报·harness·qwen38·tokenplan
不爱记笔记14 小时前
2026 WAIC大会亮点解析!超节点、机器人与AI应用全面开花
人工智能·ai·机器人·具身智能·deepseek
猫先生Mr.Mao14 小时前
一文梳理 RT-2 到 π0.7:通用机器人策略的技术演进
ai·机器人·具身智能·vla·π0.7
土星云SaturnCloud14 小时前
MP_SENet轻量语音降噪模型在土星云边缘设备的部署实战
服务器·人工智能·ai·边缘计算·语音识别