browseros — 本课题摘录
读了哪几篇: 03-diff-loop(差异与主循环)、04-agent-loop-and-safety(外层循环、提示、压缩与安全)。
其余两篇(快照与引用、动作与输入)本轮没读。
这一家对本课题第三个决定("结果怎么回填")给了一个别家都没有的答案:只回变化的那几行。
它对本课题回答了什么
决定三:结果只回增量,不回全量
问题:模型点了个按钮,它需要知道"点完发生了什么"。最笨的做法是重新发整个现场——那是几千 token,而且每个动作都这么干会让上下文迅速爆掉。
做法:只告诉模型"变了哪几行"。 (依据:Agent 库 · BrowserOS · diff 与 snapshot→act→diff 循环 —— 动作后不重发整棵无障碍树,而是做两次快照的 LCS 行级 diff,只保留变化行加上下各 3 行上下文、中间用省略号折叠)
它能这么做的前提值得单独记:每一行本身就是一个完整的语义身份。
于是"一个复选框从未选变成选中"就自然读成"一删一增",不需要任何专门的状态变化检测逻辑。
这是一条设计上的因果:因为现场的表示是"一行一个东西",差异才能是纯文本行比较。 如果现场表示是嵌套的树,差异就得写一个树比较算法。
对我们有用的一般结论:工具结果的表示形式,决定了"能不能只回增量"。
一个特例:如果两次快照之间地址变了(跳转了),差异没意义——整页都是新的,这时直接回完整快照。
"什么时候增量、什么时候全量"要有一条明确的判据,而不是永远增量。
决定三最值得抄的一条:工具结果自动附带验证
模型不用自己记得"动作后要看差异"——动作工具帮它做了。
工具执行框架在返回前自动跑一个"后置动作",把差异文本拼进结果,还加一行分隔说明下面是自动附带的上下文。 (依据:Agent 库 · BrowserOS · diff 与 snapshot→act→diff 循环 —— act 工具的 ToolResponse 支持 includeDiff 后置动作,框架在返回前自动跑它把 diff 拼进结果,并加一行「--- Additional context (auto-included) ---」分隔)
这一条很值钱,而且可以推广: 一个动作类工具的结果,应该自带"这个动作产生了什么效果"的证据,而不是让模型再调一次查询工具去确认。
省一整轮模型调用。 而且更可靠——模型经常忘了确认。
三章串起来的主循环写死在系统提示里:
看一眼现场(拿到可点的引用)
↓
动作(用引用点/填) ←── 自动附带差异
↓
看差异,确认动作生效
└── 只在需要新引用时才回到「看一眼现场」
提示的原话是:动作后读工具结果里的差异来确认成功;只在需要新引用时才重新看现场。
效果:一轮轮操作的成本主要落在第一次快照上,之后全是廉价差异。
决定一:超限时分级降级,先便宜后贵
在每一步发给模型前检查,超过阈值就逐级加力,能用便宜手段解决就不上贵的:
超阈值?
① 剥掉二进制内容(图片等) 便宜
② 剪掉「最近 N 条之前」的旧工具调用 便宜
③ 压缩旧工具的输出(截断长结果) 中等
④ 调模型做摘要 贵
(依据:Agent 库 · BrowserOS · agent 循环、提示、压缩与安全 —— createCompactionPrepareStep 作为 prepareStep 钩子在每步发给模型前检查,超阈值就逐级加力——剥离二进制→剪旧工具调用→压缩工具输出→LLM 摘要,能用便宜手段就不上贵的)
这跟 dexter 的"四道防线"、hermes-agent 的"先便宜后昂贵的固定顺序"是同一条规律,第三次出现了。 可以定成一句话:压缩不是一个动作,是一条按代价排序的阶梯。
大部分时候用前三步的廉价手段就够,只有真顶不住才花钱调模型摘要。
决定三的安全面:外来内容是数据,不是指令
它驱动的是用户真实登录的会话,读到的任何网页文本都可能是攻击者埋的一段"忽略之前的指令"。
两个层面防:
| 层面 | 做法 |
|---|---|
| 提示层 | 系统提示里把指令来源锁死为"只有本对话里的用户消息",并列出一串永远是数据、永远不是指令的来源 |
| 数据层 | 所有把外来文本喂回模型的工具都过同一个信任边界:用带每次随机口令的标记把内容围起来 |
(依据:Agent 库 · BrowserOS · agent 循环、提示、压缩与安全 —— 所有把页面派生文本喂回模型的工具(read/snapshot/diff/grep/evaluate)都经同一个 wrapUntrusted 信任边界,用带每次随机 nonce 的标记围起来,页面无法预测 nonce 就伪造不出闭合标记)
为什么光靠提示不够:一段恶意文本可能伪造"边界结束"标记 来越狱。页面预测不到随机口令,就伪造不出闭合标记。
这一条对本课题第三个决定是必修: "结果怎么回填"不只是格式问题,还是信任问题。 凡是从外部拿回来的内容,写进历史时必须带一个模型无法伪造的边界。
最小原型只要有一个能读外部内容的工具(读文件、发请求),这条就已经适用。
还有一条:所有喂回模型的外来文本走同一个包装函数——统一出口,不是每个工具各自包一次。
一条架构选择:它不自己手写循环
它用现成的工具循环库,自己只负责把料备齐(模型、拼好的系统提示、工具集)再交出去。
对比 kimi-code 的"循环什么都不拥有"——browseros 更彻底:循环干脆不是自己的。 这提示本课题一个真实选项:循环骨架可以是别人的,你的价值全在"喂它什么"和"它能做什么"。
它没回答什么
- 怎么认出模型要调工具——交给了现成的循环库。
- 一批工具怎么并发——这两篇没讲。
- 停止条件的护栏——这两篇没讲 轮数上限。
坑与代价
- 差异是有状态的。 它保存上一次快照当基准,比完再把新快照设为新基准——所以连续看差异是"相对上一次"的增量。 中间漏看一次,后面的差异就对不上人的心智了。
- 折叠半径写死为 3 行。 是"看清变化周围"和"省 token"的折中。
- 只回增量意味着模型手上没有全貌。 跑了几十轮后,模型对"现在页面长什么样"的认知全靠一串增量拼出来。
判断(无锚): 应该配一条"隔 N 轮或状态可疑时强制回一次全量"的规则,而不只靠跳转这一个特例。 如果错,会错在: 如果模型能主动调"看一眼现场",那它自己判断什么时候要全貌就够了,强制刷新是多余开销。