用 Pi 构建 Pi:AI 时代开源维护者的反思
Flask 作者 Armin Ronacher 基于维护 Pi 项目 issue 追踪器的亲身经历,深刻反思 AI 生成内容对开源维护的冲击——从 slop issue 的泛滥到 LLM 代码的过度工程化,再到开源协作精神的消解。
Pi 现在是 Earendil 的一部分,但在真正重要的意义上,它仍然是 Mario 的项目。他与 issue 追踪器相处的时间比我更长,也更早接触到开源项目中那种新型 Agent 流量带来的种种怪象。这篇文章主要是我自己在追踪器上投入更多时间、用 Pi 来开发 Pi、以及观察所学所得之后的个人反思。
Slop Issues
不出所料,我们正在用 Pi 来构建 Pi。这听起来像是一种自用测试的小花样,但它确实帮助我们理解自己所做的东西。用 Agent 构建软件的一个有趣效应是,它稍微改变了 issue 追踪器的角色。issue 描述不再仅仅是用户发给维护者的消息,因为我们也把它们作为 Pi 会话中 prompt 的输入。它是我可能交给我的 clanker1 然后说:"理解它、复现它、检查代码、提出修复方案"的东西。
这意味着 issue 的形态现在有了新的意义。一个糟糕的 issue 过去只是令人烦恼,但至少很多 issue 只是含糊不清。而现在,我们还要面对一类新的 issue——5% 是人写的,95% 是 clanker 生成的,而且基本是垃圾。一个包含看似合理实则错误诊断的 issue 会制造额外的工作。
目前最令人沮丧的失败模式是:人们提交的 issue 不是用他们自己的语言写的。它们确实包含了对某个问题的观察,但已经被丢进 clanker 里重新措辞,结果搞得一团糟。通常,给它下的 prompt 极差,导致产出的结论往往不准确,却总是充满自信。结果就是:对根因的完全猜测、伪最小复现、建议的实现策略、对相邻但往往无关代码的类比、以及一长串可能有关也可能无关的错误类别。
这比没有诊断还糟糕。
我不想指名道姓,因为我真的不想说谁坏话,但这确实令人沮丧。更令人沮丧的是,当我把这个 issue 交给 Pi 时,Pi 也会看到那个错误的诊断。它不会把 issue 正文当作传闻,而是当作证据。它会心甘情愿地沿着 issue 已经铺好的路走下去,因为那些文字充满自信,代码引用看起来也似乎合理。我们使用了一个自定义斜杠命令 /is,其中明确包含这条指令:
不要信任 issue 中写出的分析。独立验证行为,从代码和执行路径中推导你自己的分析。
遗憾的是,这并不能完全奏效。因为当人们先把他们的 issue 扔进 clanker 的绞拧机时,他们的 clanker 几乎立刻就扩大了范围。原本一个非常狭窄、基于事实的 bug 观察,变成了一个大幅扩展的问题面,充斥着各种假设。所以至少对我个人而言,我越来越希望 issue 报告能精简到人类实际观察到的内容:
- 我运行了这个命令。
- 我期望发生这个结果。
- 实际发生了这个。
- 这是确切的错误信息或日志。
这就够了。如果你用了 LLM 来理解问题,很好,也许可以留作后续评论。但 issue 及其文本应该是你自己的东西。如果你不知道根因,就说不知道。我也会用 clanker,我宁愿自己来做,也不想用你那些 slop。如果你的复现是猜测的,就说是猜测。如果唯一确凿的事实就是一条 stack trace,那就给我 stack trace,到此为止。
Slop 滋生 Slop
我们看到的满是 slop 的 issue,不过是这些机器当前质量的必然结果。遗憾的是,它们在创建 issue 上的失败也延伸到了大量生成的代码上。不是全部,但很多。我一次又一次地遇到它们把问题和实现过度工程化。
如果你告诉它"这段格式错误的 session log 会导致 reader 崩溃",clanker 往往会添加一个容错的 reader。然后加一个 fallback,然后可能加一个迁移,再加更多调试输出,再给所有这些加上测试。这些在孤立来看未必是错的,但对系统来说可能是错误的举动。
Pi 的核心是一个设计良好的 session log,有着必须维护的不变量。clanker 当前的行为是直接假设不存在这样的不变量,转而让系统兼容各种格式错误,在此过程中膨胀了复杂度。
几乎在所有情况下,正确的修复不是处理坏状态,而是让坏状态不可能出现。这对于持久化数据(如 Pi 的 session log)尤其重要。它们会被打开、分支、压缩、导出、分享和分析。这里的目标是永远不写出坏的 session 数据。然而,如果你让 clanker 自由发挥,它会试图用更宽松的 reader 来处理 session log 中每一种坏数据的情况。
我已经抱怨过很多次了,但在 Pi 代码库上的工作继续强化着这个观点。这就是 LLM 生成的代码为什么会增长出如此多不必要的复杂度的机制之一。所有这些模型看到一个局部失败,就试图在局部防御它。作为维护者,我们必须不断把讨论拉回到全局不变量上,这比本该有的难度更高,也更费力。
数量才是问题
然后是数量的问题。追踪器收到了大量的 issue 和 PR,其中相当一部分明显是 LLM 辅助生成的。有些还不错,没有出色的,大多数就是垃圾。总吞吐量本身就是个维护问题。
你可能知道,Pi 的 issue 追踪器会自动关闭所有来自新贡献者的 issue 和 Pull Request,然后我们有一个手动流程,可能会重新打开其中一些,或批准某些个人。所以"自动关闭 → 重新打开 → 再次关闭"对我们来说是一个值得关注的统计数据。
我在写这篇文章时拉取了过去 90 天的公开 GitHub 追踪器数据。排除 Earendil 成员后,剩余 3,145 个外部 issue 和 Pull Request。其中 2,504 个因来自未批准的个人而被自动关闭。17% 被重新打开,但这在一定程度上低估了 issue 数量,因为有些虽然仍然关闭,但我们其实仍在修复它们。如果我们也计入被 main 分支 commit 引用的 issue,以及已合并的 Pull Request,这个数字上升到 26%。对于 Pull Request,情况更糟:714 个自动关闭的 PR 中有 60 个最终被合并,大约 8%。

许多 issue 和 PR 完全是 slop,在某些情况下,人类甚至没有意识到自己创建了它们。低质量垃圾的来源包括 OpenClaw 实例,以及一些人塞进上下文的某些 skill——这些 skill 似乎在鼓励创建 issue。
GitHub 显然不是为应对这种新型开源而设计的,但我越来越觉得,与其把责任推给 GitHub,不如归咎于所有让这种体验变得痛苦的人。如果你的 clanker 在别人的 issue 追踪器上拉屎,那不是 GitHub 的错,是你一个人的错。
谨慎的并行
Pi 也许是用 Pi 构建的,但我们离 Bun 和 OpenClaw 已达到的境界还差得很远:完全脱钩的、自动化的软件工程。也许我们会到达那个点,我不知道。目前看来,我们似乎还不知道怎么搞定暗黑工厂,也还没有这个意愿。话虽如此,确实存在相当程度的并行,主要用于复现 issue。
我们为此使用的小型设置是 Pi 自己提交的 .pi 文件夹中的三个小部件。/is(用于分析 issue)是分析 GitHub issue 的 prompt:它标记并分配 issue,读取完整线程和链接,然后明确告诉 Agent 不要信任 issue 中的分析,要从代码中推导自己的诊断。然后一个扩展添加了 prompt-url-widget,它会在 Agent 启动前监视 prompt,识别 /is(或对应的 PR 命令)放入 prompt 中的 GitHub issue 或 PR URL,用 gh 获取标题和作者,在一个小 UI 组件中渲染,并重命名会话。它还会在会话启动或切换时重建该状态,这样如果我们重新打开一个较早的调查,窗口仍然会告诉开发者它属于哪个 issue。
在实践中,这意味着可以打开多个 Pi 窗口,每个窗口对不同的 issue 运行 /is,UI 保持各个调查在视觉上可区分,而 Agent 各自独立进行复现和代码阅读。调查完成后,可以依次处理它们。要收尾所有工作,/wr(wrap it up)是配套的收尾 prompt:它从会话中推断 GitHub 上下文,更新 changelog,起草或发布带有免责声明的最终 issue 评论,只 commit 该会话中更改的文件,当恰好有一个 issue 时添加适当的 closes #...,然后从 main 推送。

开源关乎值得修复的难题
你可能已经注意到了,后 AI 世界的开源正承受着一种奇怪的新压力。我们得到了更多的代码、更多的项目、更多的 issue。项目出现时没有真正的用户,或者只有一个人的临时观众,甚至有数千 star 的项目也可能只有几周的保质期。
对我们来说,Pi 的 harness 层值得精心维护,因为它解决了困难的协调问题,并创建了一个我们和他人都能在其上构建的平台。我们也知道,协调与合作能提升所有人。很多时候,正确的答案不是在局部绕过问题,而是让上游行为变得正确。Mario 一直很擅长拒绝让 Pi 掩盖每个配置错误的 gateway,我们也在努力保持这种纪律。当一个 gateway 行为正确时,所有人受益。
遗憾的是,这种思维方式正在快速消失,因为这些机器让局部变通变得廉价,于是代码不断积累起针对每种不当行为的局部防御。不再是人与人讨论修复该放在哪里,取而代之的是一个人和一台机器在孤立中绕过问题。
请记住,AI 并没有增加需要软件的人数,也没有增加能审查代码的维护者人数。它主要增加了代码的数量和争夺注意力的项目数量。其中有些是健康的,但很多都在碎片化本应共享的努力。
我们需要更坚实的基础,而不是更弱的。开源需要更多的协作,而不是更多与机器的孤立工作。人际沟通是困难的,当你能独自和 clanker 呆在一起时,回避沟通是很有诱惑的。但孤立不是开源的价值所在。开源的价值在于社区,在于让项目比原始创建者更长寿的结构。