Agent 一接筛选结果页就开始改到隐藏项:从 Result Scope 到 Visible Set Proof 的工程实战
很多团队把 Agent 接进后台列表页后,第一反应是让它“学会筛选、勾选、批量提交”。真正上线后,最难接受的错误却不是点错按钮,而是筛选条件已经变了,Agent 还拿着上一次的结果集继续改。一次批量改标签、改状态、改负责人,最后改到隐藏项、跨页旧项,事故往往就从这里开始。⚠️
这类问题高频出现在带虚拟滚动、异步刷新、权限切换和多标签协作的后台。页面上看见的是“当前筛选结果”,Agent 记住的却常是几秒前扫到的一组 DOM 节点。只要列表重排、条件回填或后端结果刷新,之前那批对象就不再等于“当前允许提交的对象”。🚨
问题为什么总发生 😵
很多自动化只验证“有没有勾选到目标行”,却没验证“这些行是否仍属于当前筛选结果”。一旦搜索词、分页游标、组织范围或排序条件变化,旧选择就可能漂到新的列表语义里。此时模型不是不会操作,而是在用过期结果集执行正确动作。🧭
更麻烦的是,现代后台普遍用了虚拟列表。屏幕里可见的 20 行,只是整个结果集的一个窗口;DOM 复用后,同一个行容器几秒后可能已经代表另一条记录。若系统只按位置或旧文本继续操作,误改几乎不可避免。🧨
一套能落地的约束 ✅
先给结果集做Result Scope:把本次提交绑定到筛选参数、排序方式、页码或游标版本,而不是只绑定“选中了哪些行”。接着为每个待提交对象生成Visible Set Proof,至少记录稳定主键、显示名称摘要和当前可见批次指纹。提交前再回证一次:这些对象是否仍在当前结果集、是否仍可见、是否仍满足筛选条件。🔒
下面这段实现的核心不是自动点击,而是提交前二次验明对象身份与结果集边界:
fromdataclassesimportdataclassimporthashlib@dataclassclassScope:query:strsort:strcursor:strresult_hash:strdeffingerprint(ids:list[str])->str:returnhashlib.sha1("|".join(ids).encode()).hexdigest()defcan_commit(scope:Scope,live_query:str,live_sort:str,live_cursor:str,live_ids:list[str],selected_ids:list[str])->bool:if(scope.query,scope.sort,scope.cursor)!=(live_query,live_sort,live_cursor):returnFalseifscope.result_hash!=fingerprint(live_ids):returnFalsereturnall(item_idinlive_idsforitem_idinselected_ids)实战里怎么验 📌
| 方案 | 校验点 | 好处 | 风险 |
|---|---|---|---|
| 只记勾选状态 | checkbox 是否选中 | 实现快 | 列表刷新后最容易误改 |
| 只记对象 ID | 主键是否存在 | 能防部分 DOM 复用 | 无法发现筛选条件已变 |
| Scope + Proof + 提交前回证 | 条件、结果集、对象身份同时校验 | 最稳,适合批量操作 | 要多一次读取与比对 |
线上经验是,批量操作宁可慢一步,也不要省掉提交前回证。尤其在 React 表格、虚拟滚动和服务端筛选页里,“可见”不是视觉状态,而是一次可验证的提交前契约。📎
值不值得这样做 🤔
笔者认为,这类约束看上去像给 Agent 加刹车,本质上是在给批量操作补交易边界。很多团队一开始只盯点击成功率,后面才发现真正昂贵的是误改后的追账、回滚和人工复盘。把 Result Scope 和 Visible Set Proof 接进链路后,系统从“会不会点”升级成“能不能安全提交”。
接下来 3 到 6 个月,后台 Agent 会越来越多地进入高风险批量场景:营销名单、权限对象、工单集合、实验目标组都会被自动化接管。谁先把“结果集边界校验”做成默认能力,谁的 Agent 就更像生产系统,而不是会操作页面的演示脚本。🚀
如果团队也在做筛选页自动化,最该先补的不是更长的提示词,而是提交前这一次结果集回证。你们现在的批量操作,真的是对“当前可见对象”生效吗?💬
