Octo 上手系列(十):Record & Replay 实战——录一遍你的操作,以后让 octo 自己回放

上一篇把浏览器接上了,遇到一次性的任务,直接让 octo 边看边点就够了。但同一套操作如果你自己每周都要点一遍,每次都让模型重新“观察-决定-点击”就是在浪费——这一篇讲怎么录一遍,让它以后自己回放,选择器失效了还能自己修。


录的是你做的操作,不是模型做的操作

这是理解这套机制最关键的一点:调用 record_start 之后,octo 会把浏览器交还给你,由你亲手完成这几步操作,说一声“做完了”之后,它才调用 record_stop <名字> 把过程保存下来。录制期间 octo 不会自己驱动页面——它在旁边看着记,不代劳。

每一步会记录:动作类型(click/type/select/upload/navigate 这些)、一个锚定在最近的、带稳定 id 的祖先元素上的选择器(而不是那种一变页面结构就失效的、按位置算出来的选择器链)、这个元素当时的可见文本,以及所在的 URL。

网页界面一键开录

不用记住 record_start/record_stop 这两个动作名——网页界面的“浏览器”面板里有一个“● 录制“按钮,点一下会开一个新的对话,第一句话就是把这套流程说给 octo 听:先确认浏览器已连接,问你想录什么、起什么名字,调 record_start 之后把控制权交给你,等你说完成了再 record_stop 保存。

同一个面板上,每条已保存的录制旁边还有:

  • “▶ 回放”——按名字调 run_skill,走的是完整的 agent 路径(后面会讲到的自愈机制也在这条路径里生效),不是绕开模型的服务端直接重放。
  • “✎ 编辑”——同样是对话式的:读取录制对应的 YAML,把步骤列给你看,你说要改哪一步、加/删/重排哪一步、给哪个参数设默认值,改完写回文件,不会顺手帮你回放一遍。

一段录制存下来长什么样

原始录制会经过一次模型处理——把绕路的死胡同去掉、把你当时输入的具体值换成 {{参数名}} 占位符、补一段描述——但这一步只能重新排列或重命名真实录到的步骤,不能凭空造一个新目标:任何一步如果它的选择器不在原始录制里,就会被直接拒绝,退回用原始步骤。

蒸馏完存成纯 YAML,放在 ~/.octo/browser-skills/<名字>.yaml——可读、可手改、能用 git 追踪差异。大致长这样:

name: submit-expense-report
description: 登录报销系统,填一张标准报销单并提交
params:
  - name: amount
    description: 报销金额
    default: "128.00"
  - name: memo
    description: 报销事由
outputs:
  - name: receipt_url
    type: string
steps:
  - action: navigate
    url: https://expense.example.com/new
  - action: click
    selector: "#category-travel"
    label: 选择"差旅"分类
  - action: type
    selector: "#amount"
    hint: 报销金额
    value: "{{amount}}"
  - action: type
    selector: "#memo"
    hint: 事由说明
    value: "{{memo}}"
  - action: click
    selector: "#submit-btn"
    verify:
      text: 提交成功
  - action: extract
    js: document.querySelector('.receipt-link').href
    bind: receipt_url

params 是回放时可以覆盖的输入,步骤里的 {{amount}}/{{memo}} 会被替换成调用时传的值,没传就退回 defaulthint 是这个表单字段的无障碍名字(placeholder/name/aria-label/id 或者关联的 <label> 文字)——当按位置记下的 selector 失效时,回放会先按这个再定位一次,实在不行才交给下面说的自愈;outputs 声明了这段录制对外暴露的值,extract/download 这类步骤可以把结果通过 bind 绑定给一个具名的 output,供下游取用。

回放怎么保证靠谱

browser(action: "run_skill", name: "<录制名>", params: { ... })

常见情况下回放是确定性的,不涉及模型调用:每一步先等目标出现再执行,声明了 verify 的话还会做一次校验。几个值得知道的健壮细节:

  • 点击先按原始录制里的选择器找;如果页面结构有点变化,但一个带着录制时那段可见文字的元素还在,就点那个元素,而不是直接判失败。
  • 某一步打开了新标签页,后续步骤会自动跟到新标签页上。
  • 输入之后如果字段意外变成空的,会先清空重打一次,再失败才真正判定这一步失败。

回放是“要么整段做完,要么不做”——octo 被明确指示只有当请求和一段录制从头到尾完全匹配时才用它,不会只执行一部分、也不会在声明的参数之外自己发挥。录制也只能显式回放,没有关键词触发这条路(早期版本试过又撤了,不够可靠)。

选择器彻底失效了呢——自愈

如果某一步的选择器和 hint 都找不到东西了——并且只有在配置了专门用于这个用途的模型时——octo 会对页面当前可交互的元素取一份纯文本摘要(选择器 + 可见文本,不截图),连同预期动作、预期的文字标签、失效的旧选择器一起发给模型,让它只回一个修正后的 CSS 选择器。修正会被重试一次,成功的话会直接写回这段录制的 YAML 文件——所以一次自愈是持久生效的,不是临时补丁,以后每次回放都不用再修一遍。

接进 workflow

一段录制的 outputs 可以通过 skill("browser:<名字>", params) 直接接进上一篇系列讲过的 workflow 脚本——比如录一段“登录后台导出账单”,把导出的文件路径当 output 绑出来,下一步再交给另一个 agent 去解析、汇总,整条链路就不用你手动衔接。


系列到这里

十篇下来,装机、Skills、MCP、Loop、Cron、实战合体、Workflow、Goal、Browser、Record & Replay——覆盖了从“问一次答一次”到“持续自主推进”,也覆盖了从“调用工具”到“真的替你操作一个网页”这两条完全不同的能力线。挑哪一种,取决于你手上的任务长什么样:一次性的问题直接问;要反复盯着用 /loop;按时间表触发用 cron;能拆开并行跑用 workflow;说不清楚要几轮的长任务交给 goal;没有 API 只有网页的操作,接上浏览器直接让它点,重复做的事录一遍让它自己回放。

系列上一篇Octo 上手系列(九):Browser 实战——把你自己的浏览器接给 octo 系列开头Octo 上手系列(一):装好它,跟它说上第一句话