作者用Claude Code管理量化bot,因缺少人工确认机制导致两次事故:一次K线数据bug引发虚假交易信号,一次策略被静默替换。最终设计了profit-factor gate防止AI在未经授权状态下执行高风险操作。
我经营一家一人 AI 公司:Claude Code 负责写代码和维护代码,我负责做那些需要人来做决策的事。它构建的大多数东西都是无人值守运行的——一个跑在 5 分钟调度器上的实时策略 Bot、一个每周运行一次的内容流水线,起草并发布,全程我不需要先读草稿(我自己英文阅读能力不足以做审查)。
这种安排一直运转良好,直到两个特定时刻让我意识到"智能体是有能力的"和"智能体可以安全地无人值守运行"是两个完全不同的属性。以下是两次事故的完整经过,以及我在第二次之后构建的那道门——防止第三次发生。
我让智能体用一个新策略替换一个亏损的交易策略,新策略已经过回测并通过了 profit-factor 门禁。它重写了 run.py,保存了文件,然后开始向我解释这次改动。
这个 Bot 的 Windows Task Scheduler 条目每 5 分钟无条件运行一次,无论是否有人正在审查。它触发了。新代码有一个 bug:它把当天正在形成的价格 K 线当作已闭合的 K 线来处理,并用它在一个不完整的数据上做出了真实的(模拟账户)卖出决策。
我很快发现了它,撤下了调度任务,然后开始排查问题有多深。结果比这一个 bug 要深得多:
安全网上的三个独立漏洞同时存在,只因为第一个触发才被发现。四个问题全部修复,线上验证通过,重新启用了调度。经验总结为一句话:让智能体接触它所驱动的文件之前,先把调度任务关掉——而不是等到发现为什么要这么做的时候。
在上述修复进行到一半时,我执行了 git reset --hard HEAD~1 来撤销一个错误的测试提交。--hard 不是撤销一个提交——它丢弃工作区中所有未提交的更改。其中包括了上面事故中的安全修复,它们还没有提交。只能从头重做一遍。
这里没有什么复杂的问题出错了。一个破坏性的 git 命令做了它文档中记录的一模一样的事,而一个移动迅速的智能体使用它时没有停下来检查还有什么未提交的东西放在那里。这是一位谨慎的高级工程师可能每十年才会犯一次的错误。但一个每周做几百次提交的智能体,如果没有任何东西在执行前检查,它会以快得多速度犯下这个错误。
到了内容流水线存在的时候——起草、安全检查、发布,全程没有人类先读英文——我已经学到了足够的教训,不会把这个流水线推出去而不在这条线的前面加一道硬门。三层防线,任一触发即阻断:一个手工维护的敏感字符串拒绝列表、一个实时扫描器读取每个 .env 文件并检查草稿中是否逐字出现了任何当前的 secret 值,以及一次语义层检查——用第二次模型调用问"这看起来可以发布吗",把实际草稿摆在它面前,而不是一条规则列表。
第一次真实测试,没有计划:流水线起草了一篇关于本周工程工作的帖子。门禁在草稿中拦下了三个真实的账号数字,在它到达 git 或博客之前就阻止了发布。其中一个数字甚至不在手工写的拒绝列表上——它是被一个通用的"这看起来像是账号数字形状的字符串"模式拦下的,而这恰恰是多层防护的意义所在。草稿至今坐在一个被 gitignore 的文件夹里,从未发布,光标从未在它上面继续前进。
这就是前两次事故和第三次之间的区别:前两次,事情已经发生了之后我才知情。第三次,因为门禁已经就位,在任何事情发生之前它就完成了自己的工作。这个差距——事后发现与事前阻止——就是为什么这成为一篇文章而不是一份事故记录。
能写出好代码的 AI 智能体和可以安全地无人值守运行的 AI 智能体是两个不同的命题,测试前者几乎不能告诉你任何关于后者的信息。上面的每个漏洞都是通过实际发生的事情才被发现的,而不是任何人事先预测到的——断路器的盲点只有在第一个 bug 触发后才浮出水面;git-reset 的危险性在吃掉我的真实工作之后才被命名。
我正在把门禁——拒绝列表加实时 secret 扫描加语义检查这个模式——提取成一个独立工具,面向那些让 AI 智能体拥有真实写权限(一个在线 Bot、一个发布流水线、一个部署步骤)且没有人类实时审查每个动作的个人开发者和小团队。不是做一个通用的 secrets 扫描器——这类产品已经有很多拿到融资的了(GitGuardian、gitleaks)。这个工具专门针对的是:一个智能体即将无人值守执行某个不可逆操作的时刻,而且没有人正好在那一秒盯着。
如果你正在运行一个具有某种无人值守写权限的智能体,而且你自己也有类似事故一或事故二的经历,我很想听你说——我想要的不只是自己的两个数据点。