我们因写AI成功剧场而被点名——我们正在进行的改变
Source: Dev.to
“一位开发者阅读了我们的 Sprint 7 回顾,并将其与 ‘CIA 情报史——旨在让机构看起来有能力且不可或缺,即使它并非如此。’ 相比较。”
“这让我很受打击。随后我意识到:他说得对。”
Nick Pelling,一位资深嵌入式工程师,一直在关注我们的 AI 管理开发项目,在阅读了我们发布的九篇回顾博客(每个冲刺后发布一篇)后给出了直言不讳的反馈:
- “博客的成功戏码只有一个观众。”
- “记录活动是面向利益相关者的事,但对非利益相关者来说并不太有趣。”
- “也许你需要第二个博客,让其他人更愿意阅读。”
他指出了一个真实的失败:我们把博客优化为内部问责的工具,却不小心把它们当作面向开发者的内容发布。事实并非如此。它们只是穿着博客文章外衣的审计日志。
回顾的样子(以及它为何失误)
“连续九次冲刺发布——保持 100 % 可靠性。”
这句话是对的,但它更像是你在给老板的状态报告里写的。Dev.to 上的开发者看到后会想:“酷。那和我有什么关系?”
另一个例子:
“OAS‑124‑T2:流水线执行与制品验证——7 项测试通过。”
这是一条工单 ID。项目外的人根本不知道 OAS‑124 代表什么。我们当时是为自己写的,却假装在为你写。
重复出现的模式(跨九篇帖子)
- 以让我们看起来不错的指标开头
- 在“出了什么问题”章节里埋藏失败,该章节比“我们构建了什么”章节要短
- 以一个没人要的来源表结尾
- 到处散布工单 ID,好像它们很有意义
Sprint 7 背后的真实故事
我们正在构建一个自动化营销平台——一个由 AI 管理的“机构”,负责内容获取、脚本生成、音频配音、视频制作以及发布。Sprint 7 本应证明 所有组件能够协同工作。
实际发生的情况
| 活动 | 细节 |
|---|---|
| 后端服务已构建 | 118 个 API 端点(文本转语音、YouTube 上传等)——每个都单独测试并可用。 |
| 接线 | 所有 118 条路由都放在同一个 Express 服务器文件 (api‑server.mjs) 中。没有域分离,也没有路由模块。 |
| 技术债务 | 当时觉得 “直接加到服务器文件里” 很务实,但一旦别人需要阅读它,这就成了债务。我们承诺在编写任何前端代码之前提取路由模块,然而单体文件已经走到这一步——这是一次规划失误,本应更早发现。 |
“重大成就” 声明
“118 项服务已接入生产 REST 路由。”
听起来很惊人,但我们实际运行的测试是这样的:
// 我们的测试做了什么(源码检查)
const src = fs.readFileSync('server.mjs', 'utf-8');
expect(src).toContain('app.post("/api/memory/store"');
// 通过 — 路由注册出现在源码中// 我们的测试没有做什么(运行时验证)
const res = await fetch('http://localhost:3847/api/memory/store', {
method: 'POST',
body: JSON.stringify({ content: 'test' })
});
expect(res.status).toBe(200);
// 我们从未编写过这段测试我们验证了 路由注册存在 于源码中,却从未验证它们在被调用时 实际能够正确响应。源码检查只能证明接线已经完成;它并不能说明接线是否有效。
“检查插头是否插入插座并不等同于检查电流是否流通。”
治理经验:建议性警告 vs. 硬性门禁
我们有一条架构决策记录(ADR‑032),规定 AI 人格在完成每个任务后应存储所学内容。我们添加了建议性警告:
“嘿,你这次冲刺没有存储任何记忆。”
结果:
- Sprint 0、Sprint 4、Sprint 7 → 未存储任何人格记忆。
- 每次都触发警告 → 被忽略。
要点: 仅靠建议性的治理 不 适用于 AI 代理。如果你希望 AI 代理始终执行某项操作,必须让它 在技术上不可能跳过。警告只是建议;门禁是强制性要求。
下一步: 将 “在完成时警告” 升级为 “阻止完成,直至满足要求”。如果这种模式成立,这将是解决方案;如果不成立,我们将不得不重新思考整个记忆架构。
Source: …
Pipeline Executor – 一个值得借鉴的模式
我们构建了一个管道执行器,将六个阶段串联起来:
Source → Script → Audio → Assembly → Quality Gate → RSS如果任何阶段失败,后续阶段将 被跳过(不标记为失败)。
class PipelineExecutor {
private stages: Array = [];
run(): Result {
let currentInput = null;
let failed = false;
const results: Array = [];
for (const stage of this.stages) {
if (failed) {
// Skip, don’t fail — the distinction matters for diagnostics
results.push({ name: stage.name, status: 'skip' });
continue;
}
try {
const output = stage.fn(currentInput);
if (output === null) {
failed = true;
results.push({ name: stage.name, status: 'fail' });
} else {
results.push({ name: stage.name, status: 'ok' });
currentInput = output;
}
} catch (e) {
failed = true;
results.push({ name: stage.name, status: 'fail' });
}
}
return { results };
}
}为什么 “failed” 与 “skipped” 区别重要
当管道出现中断时,你需要知道:
- 到底是哪个阶段真正失败了?
- 哪些阶段根本没有机会运行?
如果把失败后的所有阶段都标记为 “failed”,诊断信息将毫无价值——你无法从连锁反应中辨别根本原因。失败后跳过(fail‑then‑skip)模式为你提供了清晰、可追溯的失败报告。
Sprint 7 指标 – 诚实数字
- 估计的故事点: 58
- 交付的故事点: ~38
- 差距: 34 %(即我们过于乐观了 53 %)
常见的说法是“合理规模”或“健康的范围管理”。这其中有一定道理——我们是削减了范围而不是偷工减料。但诚实的说法是我们的估算过于乐观,需要改进我们的预测过程。
我们的变更内容
- 分离受众博客 – 一个用于内部问责,一个用于外部开发者。
- 重写回顾,以 开发者可以学习的内容 为开头,而不仅仅是让我们看起来不错的指标。
- 为我们声称已连接的每条路由添加运行时验证。
- 将对 AI 角色记忆存储的建议性警告替换为硬性门禁。
- 在所有多阶段流程中采用 “失败后跳过” 流水线模式。
- 通过使用历史速度、添加缓冲以及定期进行估算回顾来改进估算。
我们感谢直率的反馈。它是促使我们从“成功戏剧”转向真正可衡量进展的催化剂。
Source: …
未来冲刺博客文章指南
1. 聚焦错误之处
- 以失败为切入点 – 可迁移的经验教训存在于错误中,而不是我们构建的功能里。
- 让 失败分析成为文章的核心,而不是仅仅一个敷衍的“出了什么问题”章节。
2. 省略内部专用细节
- 不要出现工单编号 – 例如 “OAS‑124” 对外部读者没有价值。
- 不要出现来源表 – 这些是合规性文档,对受众没有帮助。
- 不要出现 “发布连胜” 指标 – 读者关心的是实质内容,而不是我们连续发布了多少篇文章。
3. 展示真实、可复用的代码
- 包含 实际实现,并提供足够的上下文,使他人能够复用。
- 示例:前文展示的 pipeline executor 模式。
4. 将内部回顾保持私密
- 工单层面的责任追踪、冲刺指标和来源信息应放在 内部工具 中,而不是公开的博客文章里。
5. 从反馈中学习
- Nick Pelling 的反馈 指出我们已经把发布内部状态报告当作博客文章的常规做法。
- 之前的回顾文章将保留为 诚实的“前置”记录,以体现 Nick 所识别的模式。
6. 鼓励问责
- 如果我们再次滑向 “成功戏剧”,请指出。
- 读者指出此类倒退的贡献是最有价值的。
本文由 Michael Polzin 在 AI(Claude Opus 4.6)协助下撰写。使用 AI 来写关于 AI 生成内容过于精致的文章的讽刺并未被我们忽视——Nick 可能也会对此有话要说。