Think Deep,Work Lean

让 Agent 帮你验收:把点击改成查询,误发降到 0

Posted on By zack

大家好,我是Zack。

今天早上发生一件小事,把我用了快一个月的做法直接打翻了。

我有个脚本,每天早上替我把排好的内容一条条发出去。发完它会自己判断「到底发出去没有」,往日志里写一行结果。

今天日志长这样:

=== posting 1 at 2026-10-05 10:53:40 ===
URL https://x.com/home
POSTED_OK: false

=== posting 2 at 2026-10-05 10:56:21 ===
URL https://x.com/home
POSTED_OK: false

=== posting 3 at 2026-10-05 10:57:12 ===
URL https://x.com/home
POSTED_OK: false

连着三行 false。

麻烦的地方在于:false 有两种可能。

一种是真的没发出去,一种是发出去了但它没认出来。

这两种情况我要干的事完全相反,但日志告诉我的信息是一样的。

我原来的办法:让脚本自己点一遍去确认

我当时的想法很简单,既然不确定,那就再点一次发帖按钮,看它报不报错。

结果那条「确认用」的半截正文,真的发出去了。

多出来一条 0 张图、正文说了一半的推文,id 是 2106920089501282552。

我用 scripts/delete_many.js 把它删了,再数一遍,时间线上只剩本来就该有的那 1 条。

从误发到删干净,前后 6 分钟。

6 分钟不算多,但这件事真正吓人的是:我为了「确认」而做的一个动作,本身是会改数据的。

验证动作不只读,那它就不叫验证,它叫又执行了一遍。

为什么它连「发没发出去」都判断不准

我去翻了它的判定逻辑,问题出在读哪儿。

它读的是我的主页时间线,抓最近几条,比对正文前 18 个字。

听着挺合理,实测两次全废:

  • 第一次:读到了东西,但全是 2022 年的旧推文,当天的没有
  • 第二次:直接返回一个空数组 [],什么都没读到

同一个脚本,两次结果不一样,第三次我也不敢信了。

页面结构变了、元素没渲染完、登录态掉了,任何一个原因都能让主页抓取静默失败——它不报错,它只是给你一个空结果。

这比报错难查多了。

换成只读的查法

我把判定改成查搜索页,不再读主页:

https://x.com/search?q=<正文前几个字> from:kaiz_amm&f=live

三个参数说一下:

  • q 放正文开头那几个字,别放全句,标点一多就搜不到
  • from:kaiz_amm 限定账号,不然会搜出别人的同名内容
  • f=live 是按时间排,默认那个排序会把你要找的推到后面去

搜索页比主页稳的原因很土:它返回的是数据,不是页面上那堆会变的 DOM。

改完之后,一次就查到了当天那条,没再出现过空结果。

四条规矩,可以直接抄

这轮之后我给所有「让 agent 自己验收」的活定了四条,都是踩出来的。

一、验收脚本里不许出现提交类动作。

点「发帖」「发送」「提交」「确认」,敲回车提交表单,全部禁掉。

只允许 goto 打开页面、evaluate 读 DOM、读接口返回。

我在提示词里是这么写的:

验证阶段只读,不许做任何会改变状态的操作:
- 不许 click 任何按钮
- 不许 submit 表单
- 不许敲 Enter
如果发现状态不对,停下来告诉我,不要自己重试。

二、判定要看终态数据源,不看执行者自己的报告。

脚本说「完成了」,那是它的自述,不算证据。

证据是另一个地方能看到的东西:搜索页、线上列表、数据库里那行记录、文件真的多了几个字节。

这两条得分开,放同一条链路上等于没验。

三、每个动作留一行日志,三个字段就够。

=== posting 1 at 2026-10-05 10:53:40 ===
URL https://x.com/home
POSTED_OK: false

时间、URL、结果。

日志的作用是让你事后能复盘,不是让你当时盯着看。

今天我能把误发那条揪出来,靠的就是日志里有时间戳和 id,翻一下就知道是哪一条。

四、跑之前先确认没有残留进程。

pgrep -fa "ego-browser|run_queue" | wc -l

不是 0 就先清干净再跑。

今天遇到的两个报错,根因都是上一轮的进程没退干净:

Error: page label not found: p1
Error: task.listTabs: Task space not found: 134

看着像页面没了,其实是两个进程在抢同一个浏览器会话。page label not found 这条我一开始还以为是页面崩了,查了半天方向都错了。

前后对比

环节 改之前 改之后
判断发出去没 脚本再点一遍按钮 搜索页读一次
误发条数 1 条 0 条
读主页抓当天内容 2 次,0 次成功 不用了
出错后排查 翻聊天记录猜 grep 日志定位
跑之前 直接起 先 pgrep 确认为 0

两条能带走的原则

第一,验证动作必须只读。

这条放到哪儿都成立。CI 里让 agent 检查部署结果,就别让它顺便点「发布」;让它核对数据库,用 SELECT 别用 UPDATE。

判断「我做没做成」的这个动作,如果自己也会改变状态,那它每跑一次就多制造一个问题。

第二,判定要看终态,不看自述。

agent 说做完了、脚本返回 true、日志写着 OK,这些都是执行者自己的说法。

真正算数的是另一个地方能独立看到的那个结果。

这两条其实是一句话:让检查的人和被检查的人,别是同一个人。


顺带说一句,这套方法不是万能的。

搜索页也有抓不到的时候,比如刚发完那几十秒索引还没建好,这时候查出来会是空的。我现在遇到空结果会等 30 秒再查一次,连续两次空才判定没发。

所以这篇讲的是「怎么少踩坑」,不是「怎么做到零错误」。零错误我做不到,谁说能做到我都不太信。

你让 agent 自动干活的时候,踩过哪种「验证反而搞出事」的坑?或者你有什么更稳的判定办法,欢迎留言聊聊,我挑几个试试。

我是 Zack,关注公众号「Zack说AI」,一起把工具用明白。