跬步 On Coding

打造适合自己的 AI Harness 工程:从开发流、E2E 测试到自动排障

最近有点忙,主要围绕着打造适合自己的 harness 工程来进行。在之前的《AI工程落地实践》的文章中,我已经基于 superpowers 构建了适合我们团队的 AI 开发工作流:

brainstorming ──→ writing-plans ──→ executing-plans ──┬──→ test-driven-development
                                                      └──→ code-review-expert ──→ learn

但是在这个流程的中间,我发现了一个问题:我需要多次跟 Agent 对话来确认进度,而 Cursor 是以请求次数计费的。这就导致每次开发需求我不仅需要频繁切入确认,还浪费了不少 Cursor 的高级模型请求次数。为了解决这个问题,我在现有的开发流程上增加了一个新的 go 启动 SKILL。

1. 开发流程的全自动化与架构规约

通过引入新的启动机制,现在的流程流畅了很多:

Image1

https://github.com/zhu327/go-clean-arch/blob/main/.cursor/skills/go/SKILL.md

除了第一步 brainstorming 阶段还需要人工确认(其实也是通过 AskQuestion Tool 来引导提问),剩下的一旦启动,1次请求就可以走完整个开发流程,最多可以一口气消耗掉 2 千万 Token。

除了开发流程本身的提效,这期间我感触最深的一点是:要写清楚架构约束,制定好项目的规约。这就是 harness 工程极其重要的一部分。比如我在维护的 Go 项目中,基于 DDD + Clean Architecture 的约束,我对每一层的代码都做了详细的规范化:

https://github.com/zhu327/go-clean-arch/tree/main/.cursor/rules

基本上按这个流程并加上严格的 rule 约束开发以后,我就不怎么再自己去一行行 review 代码了。Opus 4.6 确实强,配上详尽的上下文,真有点“言出法随”的意思。

2. 让 AI 接管 E2E 测试

代码虽然是 AI 写的,但在合并代码前,我还是得手工黑盒测试相关逻辑是否满足需求。既然 AI 都能写代码了,那能不能让 AI 来帮我们做 E2E 的 API 测试呢?

要做 E2E 测试,首先要解决的是环境问题。以前手工测试的时候,我往往只能把代码发布到测试环境上做,因为应用本身依赖的外部 HTTP/gRPC 服务非常多,又有自己的数据库,在本地测试造数据非常麻烦。为了打造一个适合 AI Agent 的 E2E 测试环境,我做了以下基建:

  • 外部依赖 Mock:我找到了 mockd,它可以直接 mock 真实的 HTTP/gRPC 服务。我们的 app 只需要把依赖服务的地址配置到 mockd 上,所有的请求真实发生,只是打到了 mockd 上。
  • 数据库依赖:通过 testcontainers-go,用 Docker 在每次测试前启动真实的数据库,测试后自动销毁。
  • API 验证:引入了 gavv/httpexpect 来方便地请求 API 并验证返回数据。

环境有了,数据构造对 AI 来说根本不是事儿。为了把 E2E 测试也加入到标准流程中,我专门写了一个 e2e-testing 的 skill,并把它加入到 writing-plans 的过程中,强制要求 Agent 在有接口变更时,先设计 E2E 测试用例并构造数据。所有的 E2E 测试直接在代码仓库中维护。

把 E2E 加入到开发流程中后,我基本上彻底从代码细节中解放出来了。现在我主要看的就是 E2E 的测试用例是否符合业务预期,预期对上了,就快速开发、快速上线。

3. SRE 与自动排障 Agent 的探索

除了开发流程的 AI 化,我还关注到了一个非常有意思的开源项目:OpenSRE。它旨在构建开源 AI 运维智能体,依托大模型与各种监控基建工具,通过自动关联告警、日志与指标进行深度推理,实现生产故障从定位到修复的端到端自动化。

在研究了相关流程以后,我萌生了自己做一个自动 AI 排障 Agent 的想法。正好我们部门也在推进 srecli,这个命令行工具整合了 SRE 平台的容器、网关、CMDB、DB平台、日志、告警和知识库。有了底座,结合我之前在《复盘:ClawOps AI Agent》中得出的经验——不要轻易从头造一个厚重的 Agent,我决定还是走轻量级的路线,构建一套 SKILL 组合。

首先用 AGENTS.md 给 Agent 立一个人设:

You are Tracer, a Senior SRE Assistant for incident investigation and root cause analysis.
你的任务是帮助开发者排查生产故障。你可以调用底层的 `srecli` 工具来获取监控、日志、Kubernetes和发布状态等信息。

核心工作原则:
1. **证据驱动****:当你需要确切证据(错误堆栈、具体指标值、发版时间)时,必须调用 `srecli`,不要猜测。
2. **结构化输出****:永远不要向用户倾泻原始日志或大量 JSON,请对信息进行提炼。
3. **区分事实与推测****:明确指出哪些是已验证的证据(Validated Claims),哪些是合理的推测(Non-validated Claims)。
4. **无指责文化(Blameless)****:关注系统和流程的缺陷(如“缺乏配置校验”),而不是人的失误(如“小明配错了”)。
5. **增量排查****:每次调用工具后,说明你的发现,并说明你的下一步排查计划,让开发者随时掌握排查进度。

接下来就是一套基于事件流的 SKILL 组合:alert-triage -> root-cause-analysis -> blameless-postmortem。我把多方看文章和研究 OpenSRE 蒸馏出来的逻辑写成了模板(节选部分核心逻辑):

Skill: Alert Triage (告警初诊)

要求 Agent 提取受影响范围,收集四大黄金信号(Latency, Traffic, Errors, Saturation),追溯最近1小时的变更记录,并对日志进行摘要聚类。绝对禁止直接输出原始日志。

# Skill: Alert Triage & Context Gathering
**Description**: 
当用户丢出一个告警(如高错误率、CPU飙升)或描述一个线上故障时触发。你的任务是作为 SRE 值班工程师的“副驾”,迅速通过 `srecli` 完成第一轮现场信息收集与整合,避免工程师在多个监控平台间来回切换。

## Core Workflow (执行动作要求):
1. **解析意图与范围****:提取受影响的 Service, Namespace/Environment, Time window。
2. **收集四大黄金信号 (Golden Signals)****:调用 `srecli` 收集该服务及核心依赖的以下指标:
   - **Latency (延迟)**: 获取 P50, P95, P99 响应时间。
   - **Traffic (流量)**: 获取请求速率 (RPS) 或当前活跃连接数。
   - **Errors (错误)**: 计算整体错误率 (5xx 比例) 及 Error Budget Burn Rate (判断是 Fast burn 还是 Slow burn)。
   - **Saturation (饱和度)**: 获取 CPU 利用率 (是否发生 Throttling)、内存使用率 (是否接近 OOM)、连接池使用率。
3. **变更追溯 (Change Context)****:调用 `srecli` 获取最近 1 小时内该服务的基础设施变更、代码发布记录(Deployments/Rollouts)。
4. **日志摘要 (Log Digestion)**:调用 `srecli` 获取最近 15 分钟的 ERROR/FATAL 日志。**[绝对禁止]**** 直接输出原始日志,必须对日志进行聚类(如:某 NullPointerException 出现 50 次)。

## Constraints (排障护栏):
- 必须验证指标的当前状态,不要只看告警触发那一刻。
- 不要只看当前服务,检查其**下游依赖调用失败率**。
- **输出必须结构化**,让值班人员一眼看清现场。

## Output Template (必须严格遵循以下格式输出):
### 🚨 告警初诊报告: [Service Name]
- **告警时间与影响服务**: [时间] / [受影响服务]
- **Error Budget 状态**: [判断属于 Fast burn / Slow burn / 正常,及当前错误率]

#### 📊 黄金信号快照 (Golden Signals)
- 🟢/🔴 **Latency****: [当前 P99 延迟,对比基线]
- 🟢/🔴 **Traffic****: [当前 QPS/连接数趋势]
- 🟢/🔴 **Errors****: [当前 5xx 错误率]
- 🟢/🔴 **Saturation****: [CPU节流 / 内存使用 / 磁盘 / 连接数状态]

#### 🔄 上下文与变更记录
- **最近发布**: [时间 / 版本号 / 变更人,如无则写"1小时内无变更"]
- **关键日志聚类**:
  1. `[报错类型摘要]` - 出现 N 次
- **下游依赖**: [依赖服务是否健康]

#### 🧠 初步判断与建议步骤
- **初步判断**: [例如:现象高度吻合 21:31 的灰度发布窗口,且新版本 Pod 错误率显著高于旧版本]
- **下一步建议****: [列出具体建议,如“是否允许我调用 `srecli` 查看慢查询?”或“是否暂停灰度扩大?”等待用户指令]

Skill: Root Cause Analysis (根因分析)

遵循“提出假设 -> 寻找判别性证据 -> 排除假设 -> 锁定根因”的逻辑链。输出必须包含“已证实的推论”和“尚未证实的假设”,并且给出紧急止血的建议动作。

# Skill: Root Cause Analysis (Hypothesis-Driven)
**Description**:
当需要定位问题根本原因时触发。你必须表现得像一名资深 SRE。排查不是盲目执行命令,而是遵循“提出假设 -> 寻找判别性证据 -> 排除假设 -> 锁定根因”的逻辑链。

## Reasoning Sequence (排查逻辑链):
1. **观察事实 (Observe)****: 列出目前已知的确凿事实。
2. **生成假设 (Hypothesize)****: 形成 2-4 个合理的根因假设。
3. **判别性验证 (Discriminate)****:
   - 优先选择能**同时证实一个假设并排除其他假设**的 `srecli` 动作。
   - **特殊处理规则**:
     - *CPU/连接数告警*: 必须区分是“空闲连接堆积”、“慢查询/昂贵查询”还是“真实流量突增”。
     - *存储打满*: 必须明确检查是否为 Audit logs (如 Postgres/Aurora) 等后台机制导致。
4. **排除与锁定 (Eliminate & Select)****: 排除与证据矛盾的假设。如果多个假设存活,选择最符合奥卡姆剃刀原则的,并注明。

## Anti-Bias Rules (防偏见规则):
- 不要预设故障类型,让数据说话。
- 如果现有假设与调取到的指标/日志矛盾,立即抛弃并生成新假设。
- **绝不伪造数据**:如果缺少遥测数据,必须在“缺失证据”中明确声明。

## Output Template (必须严格遵循以下格式输出):
### 🔍 深度根因分析 (RCA) 结论

**ROOT_CAUSE (根本原因)****:
[1-2句话陈述。如果无法证实,请写 "最有可能的根因是...,但缺失以下证据:...。绝不能只写'无法确定'。]

**ROOT_CAUSE_CATEGORY****:
[必须从以下选择其一:configuration_error | code_defect | data_quality | resource_exhaustion | dependency_failure | infrastructure | unknown]

**CAUSAL_CHAIN (故障传导链)****:
- [Step 1: 触发点/初始故障,如: 错误配置被合入]
- [Step 2: 故障蔓延,如: 连接池未正确释放]
- [Step 3: 最终症状,如: 数据库拒绝连接导致API 502]

**EVIDENCE_DIGEST (证据摘要)****:
**VALIDATED_CLAIMS (已证实的推论)****:
- [事实 1] [Evidence: srecli 调用的具体指标/日志路径]
- [事实 2] [Evidence: ...]

⚠️ **NON_VALIDATED_CLAIMS (尚未证实的假设)****:
- [假设 1] [需什么数据验证,如: "需验证 DB 慢查询日志,但当前无 srecli 权限"]

**ALTERNATIVE_HYPOTHESES_CONSIDERED (被排除的假设)****:
- [被排除的假设及排除原因,如: "曾怀疑突发流量,但排查发现 QPS 同比无明显上升"]

**REMEDIATION_STEPS (修复建议)****:
1. [最紧急的止血动作,附带确切的 `srecli` 命令代码块(如回滚/扩容),提示用户确认执行]
2. [下一步修复建议]

Skill: Blameless Postmortem (无指责复盘)

故障恢复后,要求 Agent 出具复盘报告。运用 5 Whys 分析法深挖系统性缺陷,并按照 SMART 原则和控制层级(系统防护 -> 自动化检测 -> 流程规范)提出预防措施。

# Skill: Blameless Postmortem Generation
**Description**:
当故障恢复,用户要求出具故障通报或复盘报告时触发。你不仅要总结事件,还要将失败转化为组织学习的机会。

## Core Principles (核心原则):
1. **无指责文化 (Blameless Culture)****: 人难免犯错,系统应该具备韧性。绝对不要写“某开发人员配置错误”,必须重构为“部署流水线缺乏配置合法性校验”。
2. **量化影响 (Quantify Impact)****: 必须量化受影响用户数、SLA 违规时间、潜在业务损失。
3. **5 Whys 分析****: 深度追问,不能停留在“代码有Bug”表面,要深挖到流程、架构设计的系统性原因。
4. **SMART 与控制层级 (Hierarchy of Controls)****: 预防措施按“消除风险 -> 增加系统护栏 -> 完善流程(SOP) -> 培训”的优先级提出。

## Output Template (必须严格遵循以下 Markdown 结构):
### 📄 故障复盘报告 (Blameless Postmortem)

#### 1. 摘要与量化影响 (Summary & Impact)
- **事件描述**: [1-2句话总结]
- **故障级别**: [Sev1/Sev2/Sev3]
- **量化影响**: [受影响用户占比 / 具体 Downtime 时长 / 对外 SLA 影响]

#### 2. 时间线重建 (Timeline Reconstruction)
| Time (UTC/Local) | Event | Source | Action Taken |
|---|---|---|---|
| [HH:MM] | 故障潜伏/变更触发 | [如 CI/CD] | [发布 V2.0] |
| [HH:MM] | 告警触发/发现异常 | [如 Grafana] | [错误率飙升至 8.7%] |
| [HH:MM] | 锁定根因 | [人工/Agent] | [确认是新代码逻辑导致的连接泄漏] |
| [HH:MM] | 止血与恢复 | [CI/CD] | [执行回滚,指标恢复正常] |
> **核心指标**: 发现延迟 (Detection lag): [X] 分钟 | 修复时间 (MTTR): [Y] 分钟

#### 3. 根因分析 (Root Cause Analysis - 5 Whys)
- **Problem**: [初始现象,如支付网关宕机]
  - **Why?** [直接原因,如:DB连接池耗尽]
  - **Why?** [深入一步,如:重试逻辑没有正确释放超时连接]
  - **Why?** [再深入,如:新版本的压测在测试环境未暴露出此问题]
  - **Why?** [系统/流程原因,如:测试环境缺乏生产级别的真实长连接模拟]
- **系统性根因**: [总结最后的系统性缺陷]

#### 4. 纠正与预防措施 (Corrective Actions)
*(按控制层级从强到弱排列,符合 SMART 原则)*
- **[系统防护/Engineering]** [如:给连接池增加绝对超时强制回收机制] (Owner: ___ | Due: _____)
- **[自动化检测/Detection]** [如:增加连接池利用率 > 80% 的告警] (Owner: ___ | Due: _____)
- **[流程规范/Administrative]** [如:补充压测 SOP,增加长连接阻断测试] (Owner: ___ | Due: _____)

#### 5. 经验教训 (Lessons Learned)
- 🟢 **What went well****: [做得好的地方,如:回滚机制生效迅速,10分钟内止血]
- 🔴 **What could be improved****: [需要改进的短板,如:监控仅发现了报错,没有提前发现连接池缓慢泄露的趋势]

从 AI 开发流到 E2E AI 测试,再到 AI 排障,我算是为团队跑通了一套全流程的 AI 研发基建。

4. 探索多智能体并发工作流

除了公司的 Cursor,部门老板最近还为我们提供了火山云的 OpenCode (Coding Plan)。为了尝试新的可能性,我没有直接把 superpowers 搬过去。在读到《软件基本功没死,它在 AI 时代变得更值钱了》这篇文章后,我开始在 OpenCode 上实践基于 mattpocock/skills 改造的开发流程:

想法
  ↓
/grill-me [客户需求文档]       -- 与 AI 建立共同的设计概念
  ↓
/to-prd                        -- 将对话转化为目的地文档
  ↓
/to-issues                     -- 切分为垂直曳光弹 Issue(看板)
  ↓
ralph-once.sh / ralph-loop.sh  -- AFK 智能体逐 Issue 实现(TDD)
  ↓
人工 QA + 代码审查              -- 把品味和判断注回代码
  ↓
回到看板                        -- QA 产生新 Issue,循环继续

这个流程相比 superpowers 有个巨大的优势:to-issues 环节会把庞大的需求切分成一个个小的 Issue,并分析它们之间的依赖关系。下一步就可以启动多个 subagent 并发去开发多个 Issue。对于大需求而言,这种开发流不仅更快,而且更可靠,毕竟每个 subagent 只用专注于一个小任务,上下文污染的概率大大降低。

正如那篇文章里提到的感悟,在 AI 时代,我们人类要做的是接口和抽象的设计,内部的实现完全交给 AI,把实现细节当作黑盒。在使用 OpenCode 的过程中,我确实感觉到国产模型在推理能力上相较于 Opus 4.6 还有一定差距(Token 生成速度也稍慢一些),但通过这种极其严密的流程约束和任务拆解,依然能稳定输出不错的结果。

5. 业务实战:多云生命周期的 IaC 与抽象

说完了这些工具和流程上折腾的事情,再来说说我最近在做的具体业务。

目前我们在做一个 CMDB 系统,要实现对云资源的全生命周期管理。这里最大的挑战是要同时对接 4 朵公有云:AWS、阿里云、华为云、金山云。我之前在腾讯蓝鲸的同事做多云管理,搞了一年多还没能把几朵云完全对齐,可见水有多深。而且,我本心并不希望自己只是作为运维的“乙方”去纯做一套死板的平台功能,我希望运维同学能深度参与进来。我来做引擎,运维同学在引擎的基础上扩展能力。

基于这个思路,刚开始就把方案定格在了 IaC(基础设施即代码)上。在 Terraform 和 Pulumi 之间,由于国内云对 Pulumi 支持欠佳(特别是金山云根本不支持),最终选择了 Terraform。

为此,我开发了一个轻量级的执行引擎:tfengine。在产品逻辑上,我要求运维同学自己维护相关资源的 HCL 模板,然后在平台上以表单的形式面向普通用户提供申请。资源创建过程中,平台会完整透传 terraform plan/apply 的日志信息。这样做的好处是,一旦资源申请出问题,运维同学通过日志直接就能排查,而不是遇到报错就来找我们开发定位。

除了资源创建,全生命周期管理中还有很多 Day 2 的操作(比如开关机、升降配等),这些没法用 Terraform 搞定,只能通过各云厂商的 SDK 重新封装。这就引出了两座大山:

  1. 平台内部现有的 cloudservice 模块做得太早,抽象极差,甚至账号管理都乱七八糟,简而言之就是一坨屎山。
  2. 不同云厂商的模型差异巨大,比如华为云有一个独创的“企业项目 ID”概念,而金山云的 SDK 又经常缺斤少两。

幸运的是,现在我有 AI Agent 这个得力助手。我把这些繁杂的脏活全交给了 Agent:让它去扒不同云厂商的 SDK 源码,读又臭又长的 API 文档,最后帮我糊出了一个非常轻量的封装微服务,我管它叫 cloudkit

cloudkit 采用了纯插件式架构,新增一个功能就是新增一个插件,插件内部封装好 4 朵云对应的 API 流程。遇到屎山代码?让 Agent 去理清逻辑并隔离出清晰的接口。

最后,在 CMDB 平台上统一调用 tfenginecloudkit,走同一套账号体系进行纳管。这套方案非常灵活,开发极快。目前我们已经顺利实现了云主机和对象存储的全生命周期管理。

Image2

回过头看,无论是流程的演进,还是屎山代码的重构,AI 都在以一种不可逆的方式改变着我们的工作模式。从最初的“测试转开发”,到如今的“写规范让 Agent 开发”,我们解决问题的方式一直在变,但作为程序员,那种对系统掌控感和抽象设计的追求,始终如一。