项目Mayday在接到告警后自动分析范围、并行查询指标和日志、关联部署历史,但所有修复操作必须人工批准才能执行,避免自主误操作导致故障扩大。
Built for the WeMakeDevs × TrueFoundry Agent Harness Hackathon.
"AI 事故响应"有两类产品,但它们都有问题。
第一类自主行动。它检测到延迟飙升,判断出原因,然后在凌晨三点回滚你的部署。做对了就是魔法——做错了就是灾难,因为生产环境是自信推理的坟墓。一旦它错了,你现在就有两起事故。
第二类只是分页给人类并总结一些日志。安全,但几乎没用。那二十分钟的机械性工作仍然落在刚被叫醒的人身上。
我想探索的是:能不能既拥有第一类的速度,又拥有第二类的安全性?不是通过让模型更谨慎——你无法通过 prompt 获得安全保障——而是从结构上让不安全动作在人类说"是"之前根本不可能执行。
这就是 Mayday。以下是它运行的演示:
Repo: https://github.com/Anamiiikka/Mayday
它实际做什么
一个告警触发。Mayday 接收并自行:
确定事故范围——一次调用,不猜测哪个服务受影响
并行派出两个子 agent——指标分析师和日志分析师,各自在独立线程上运行
关联部署历史——出事前有没有发版?
编写 Python 诊断脚本并在沙箱中运行——这段代码三十秒前还不存在
提出一个修复方案,包含根因分析、为什么是这个方案、预期影响、以及排除掉了什么
第六步是整个项目的核心。之前的所有步骤都是自主运行的。修复操作直到人类点击"批准"才会执行。
然后在批准后,它验证修复是否真的生效,并发出询问
任何人都可以在 UI 里放一个"批准"按钮,让 agent 礼貌地等待。这是约定,不是保证。agent 可以忽略它。bug 可能跳过它。其他人可以直接调用工具。
在 Mayday 中,这个暂停是由 harness 强制执行的。TrueForge 的 agent manifest 是这样配置的:
"require_approval_for_tools": [
"restart_service",
"rollback_deployment",
"scale_service",
"resolve_incident"
]
TrueForge 在这些工具执行前停止并发出 tool.approval_required。Agent 无法继续。不是"选择不继续"——是"无法继续"。我的后端唯一的作用是将操作员的点击作为 user.tool_approval 转发回去。
五个只读工具——metrics、logs、deploys、incident details、service health——完全不设门控。调查永远不会被阻塞。只有改变状态的操作才需要批准。
而且因为这道门在工具层,所以明显的攻击是绕过它:直接与 MCP 服务器通信,自己调用 rollback_deployment。所以 MCP 服务器需要 bearer token。在视频中,我在事故中途恰好运行了那个请求,而回滚正坐在那里等待批准:
401
我想在这里说清楚,因为"我们用了赞助商工具"可以意味着从深度集成到一个 import 语句的任何东西。
TrueForge 运行 agent。我的代码不运行。整个 agent 是一个 manifest 文件——instructions 字段里是一份 SOP,加上配置。我的 Express 后端创建会话、将 turn 事件转发给 UI、提交批准。它从不编排循环、不决定下一步调用什么工具、不管理对话。
四个 harness 能力在做真正的工作:
批准门——上面的 require_approval_for_tools。这是项目的核心。
通过 MCP 的真实工具。一个假云 MCP 服务器(streamable HTTP,bearer auth),暴露九个由 Postgres 支持的工具。用 enable_tools: ["@all"] 和 preload: true 注册。
沙箱——sandbox: { enabled: true }。Agent 自己编写 Python 并在 bubblewrap 监狱中运行。这是真正属于 agent 的代码,不是我写好让它调用的脚本。
子 agent——dynamic_sub_agents: { enabled: true }。在证据收集期间有两次 create_sub_agent 调用。它们在独立线程上运行,无法访问父对话,所以每份简报必须是完全自包含的。我从事件流中读取 thread_id 将它们渲染为时间线上的并行泳道。
演示事故响应器的问题在于:一次事故什么也证明不了。
给 agent 看一个部署后立即出现的延迟飙升,看着它提出回滚,你什么都学不到——你不知道它是推理还是只是匹配"事故 → 回滚"。两种情况它看起来都一样自信。
所以这里有两个事故,而且它们被设计成会得出不同结论:
同一个 agent。同样的 SOP。同样的工具。在第二个事故中,它明确说:
这是运行中进程的泄漏,不是坏版本,也不是负载驱动的。
它排除了回滚,因为没有任何东西发布。它排除了扩容,因为 RPS 在下降而不是上升。剩下的就是泄漏,而重启会清除泄漏状态而不影响发布历史。
如果它是模式匹配,它早就回滚了一个 26 小时前的部署,什么问题都解决不了。
几乎我所有的迭代都在 SOP 上,而不是应用代码。Harness 给 agent 真正的自由,这意味着真正的自由去以我预料不到的方式犯错。
子 agent 烧光速率限制
我告诉子 agent 每次工具调用各一次。相反,它们通过沙箱代码访问工具——写一个脚本,import MCP 客户端,从里面调用工具。我想要一次模型调用,结果用了三次。在免费层上,这会导致运行中途挂掉。
修复方法是让约束变得明确和绝对:
硬性规则:恰好一次直接工具调用,然后给出你的答案。不许 list_tools,不许 get_tool_info,不许代码,不许沙箱——对你来说写代码是被禁止的。
沙箱属于父 agent,恰好一步。子 agent 只读取和报告。
让它拒绝完成的 bug
这个是我最喜欢的,因为 agent 做的事和我告诉它的一模一样,但仍然错了。
在批准修复后,它验证恢复。它调用的是 query_metrics,默认是 5 分钟桶聚合的平均值。但它在修复落地后立即检查——所以那 5 分钟窗口仍然包含 4 分钟的故障时间。
平均值看起来很糟糕。Agent 正确地断定数字没有回到基线,拒绝解决:
恢复进行中但尚未回到基线。
它对数据判断正确,对现实判断错误。修复方法是教它为什么数据会说谎:
调用 query_metrics 时加上 raw=true 和 window_minutes 5,然后只根据最新的一个或两个样本判断——不要平均整个窗口。桶聚合的平均值在修复落地后的一小段时间内仍会被修复前的分钟数拖累;那是陈旧数据,不是修复失败的迹象。
同样的工具,同样的数据,正确的结论。我大多数的"agent bug"都是这样——不是模型犯傻,而是模型忠实地执行了我告诉它的一些对世界略微错误的指令。
一个 100 倍的错误率
error_rate 返回 0.476。是百分比还是分数?Agent 猜测是百分比,报告了 47.6% 的错误率,而实际故障中的错误率是 0.476%。
我把字段在所有地方重命名为 error_rate_pct,并在工具描述中记录了单位。当你的调用者是一个语言模型时,命名就是接口。
代码审查发现了四种绕过门的方式
Qodo 审查了每个 PR——25 个发现,跨越 8 个 pull request,所有问题在合并前都已修复,只有一个被故意忽略。
重要的都是同一类问题:绕过批准门的方式,从 UI 都看不到。
PR #4:/mcp 完全没有认证即可访问
PR #6:agent、approval 和 reset 路由完全没有 token 即可调用——加上 rollback_deployment 能够恢复刚被回滚的精确版本
PR #8:Codespaces 设置生成空的 MCP_TOKEN,悄无声息地让任何用简单方式启动项目的人都能绕过门
每一个都会让演示看起来完美,而核心主张却是假的。这是手动测试永远抓不到的失败模式,因为快乐路径总是正常工作。
其他是正确性 bug,本会毒害 agent 的推理。PR #5:遥测数据按格式化的时钟字符串排序,所以任何跨越午夜的窗口都会反向返回——agent 会倒着读序列并据此诊断,自信地。PR #9:一个 45 分钟的指标窗口,对于 90 分钟前开始的泄漏没有基线,加上 level=error 过滤器排除了恰好命名了根因的确切堆压力警告。
一次审查改变了架构:PR #7 将 README 与代码对比,发现代码不达标——批准后又被拒绝的操作没有留下审计日志,因为 guarded handler 在记录任何东西之前就返回了。
把保证放在运行时,而不是 prompt 里。"请在破坏性操作前询问"是一个愿望。require_approval_for_tools 是系统的属性。如果你的安全故事依赖模型配合,你就没有安全故事。
**构建你的 agent 应该出错的情况。**一个快乐路径演示什么都证明不了。第二个事故——那个明显修复方案其实是错误的——比看第一个成功一百遍更能让我相信这东西真的管用。
你的工具描述就是 prompt 工程。 error_rate vs error_rate_pct 是 100 倍的报告误差。模型把这些字符串当作它唯一的文档。
当 agent 行为奇怪时,先怀疑你的指令。 我遇到的几乎每个"错误推理"bug 都是模型忠实地执行了我告诉它的事情——那些我对世界的描述悄悄错了的指令。
坦白说,因为反正也在 README 里:
单元测试止步于数据库。纯决策逻辑有测试;事务性工具体只在端到端层面被触发。
scale_service 从未被执行过。实现并加了门控,但 seed 的事故都没有调用它。
委托不是可靠并发的。SOP 在一条消息中发出两个 create_sub_agent 调用;模型经常按顺序发出它们。仍然是两个分析师在两个线程上——只是按顺序收集的。
UI 轮询,不流式传输。每四秒重新读取状态,而不是消费 SSE 流。更简单,支持恢复,落后最多四秒。
只有本地沙箱。没有配置 Daytona provider,所以主机必须允许非特权用户命名空间——WSL2、有特权的 codespace、或你有 root 权限的 VM。
一键通过 GitHub Codespaces(devcontainer 设置了 bubblewrap、Postgres 和 harness):
https://github.com/Anamiiikka/Mayday
需要一个 Gemini API key。免费层就够了。
基于 TrueForge(TrueFoundry 的开源 agent harness)构建。使用 Qodo 审查。