OpenClaw Lobster 工作流工具:确定性多步管线与可恢复审批门实战指南
Lobster 是 OpenClaw 的可选插件工具,用于将多步工具调用封装为一次确定性执行的工作流管线,并在副作用操作(发送、发布、删除)前设置人工审批检查点。本文基于 Lobster 技能文档 展开,结合 插件源码、运行器实现 与 测试用例,讲清它的适用边界、run/resume 完整调用协议、参数默认值与底层执行机制,读完后你可以独立编写带审批门的工作流,并理解每个错误信息的来源。
一、Lobster 解决什么问题:一次调用替代多次往返
没有 Lobster 时,一个多步骤任务(如邮件分诊)意味着模型要编排多次往返的工具调用:先列邮件、再总结、再按用户指示逐条回复,且每轮之间没有状态记忆。Lobster 把这种编排移入一个类型化的工作流运行时:
- 一次调用而非多次:一次
lobster工具调用即可返回整条管线的结构化结果; - 内置审批:副作用步骤会暂停工作流,直到显式批准;
- 可恢复:暂停的工作流返回 resume token,批准后继续执行而无需重跑前面的步骤。
Lobster 刻意设计为一个小而受限的 DSL,而非通用脚本语言:管线是数据(便于记录、diff、回放、审查),approve/resume 是持久化的内建原语,超时、输出上限、沙箱检查与白名单由运行时统一强制。
何时该用 Lobster(决策表)
原文档给出了一个清晰的意图判断表,直接继承如下:
| 用户意图 | 是否使用 Lobster |
|---|---|
| “Triage my email”(分诊我的邮件) | 是 — 多步骤,可能发送回复 |
| “Send a message”(发送一条消息) | 否 — 单一动作,直接用消息工具 |
| “Check my email every morning and ask before replying”(每天早上检查邮件并在回复前询问) | 是 — 带审批的定时工作流 |
| “What's the weather?”(天气如何) | 否 — 简单查询 |
| “Monitor this PR and notify me of changes”(监控这个 PR 并通知变更) | 是 — 有状态的、周期性的 |
反过来,不要在以下场景使用 Lobster:简单单动作请求(直接调用工具即可)、流程中途需要 LLM 解释的查询、以及一次性的不会重复执行的任务。
二、插件安装与启用:可选工具与白名单机制
Lobster 以插件形式存在,包名为 @openclaw/lobster,插件 id 为 lobster。从 插件清单 可以看到其关键元数据:
{
"id": "lobster",
"activation": { "onStartup": true },
"contracts": { "tools": ["lobster"] },
"toolMetadata": { "lobster": { "optional": true } }
}
其中 optional: true 是关键设计:因为该工具可能通过工作流触发副作用,它默认不开放,需要显式加入 agent 的工具白名单。按 插件 README 的安装方式为:
openclaw plugins install @openclaw/lobster
安装或更新插件后需重启 Gateway。启用时把插件 id 加入 agent 的 tools.allow(以插件 id 为单位启用该插件全部工具):
{
"agents": {
"list": [
{
"id": "main",
"tools": { "allow": ["lobster"] }
}
]
}
}
README 特别建议:如果工作流会通过 openclaw.invoke 回调用 OpenClaw 工具,应对承载它的 agent 设置紧白名单(如仅放行 lobster、web_fetch、web_search、gog、gh,并 deny 掉 gateway),避免工作流调用任意工具。注意:tools.allow 若省略或为空,行为等价于“除 deny 外全部放行”,因此真正的白名单必须是非空的。
从 插件入口 可以看到两条硬约束:
register(api: OpenClawPluginApi) {
api.registerTool(
((ctx) => {
if (ctx.sandboxed) {
return null; // 沙箱上下文中直接不注册该工具
}
...
}) as OpenClawPluginToolFactory,
{ optional: true },
);
}
即沙箱化的工具上下文中 Lobster 被完全禁用——这是安全模型的一部分:只有非沙箱、受信的执行环境才能运行管线。
三、基本用法:run 运行一条管线
工具参数在 lobster-tool.ts 中以 TypeBox 模式声明。最基础的运行调用(继承自 SKILL.md 的示例):
{
"action": "run",
"pipeline": "gog.gmail.search --query 'newer_than:1d' --max 20 | email.triage"
}
成功时返回结构化结果:
{
"protocolVersion": 1,
"ok": true,
"status": "ok",
"output": [{ "summary": { ... }, "items": [ ... ] }],
"requiresApproval": null
}
完整参数表(源码默认值)
run 的完整字段与默认值如下,默认值取自 lobster-tool.ts 的参数解析代码:
| 字段 | 默认值 | 说明 |
|---|---|---|
action |
必填 | run 或 resume 二选一,其他值直接报错 Unknown action: … |
pipeline |
run 时必填 |
内联管线字符串(`a |
cwd |
Gateway 工作目录 | 相对路径;必须解析到 Gateway 工作目录内部,绝对路径被拒绝(见下文沙箱检查) |
timeoutMs |
20000 |
超时中止执行 |
maxStdoutBytes |
512000 |
捕获的 stdout/stderr 或内嵌 JSON 结果超过该字节数即中止 |
argsJson |
— | 传给工作流文件的 JSON 字符串参数(内联管线时忽略),非法 JSON 报 run --args-json must be valid JSON |
token / approvalId / approve |
resume 时使用 |
见下一节 |
flowControllerId 等 flow* 字段 |
— | 托管 TaskFlow 模式,见第七节 |
几个容易踩坑的实现细节:
- cwd 防逃逸:resolveLobsterCwd 会先拒绝绝对路径(
cwd must be a relative path),再用isPathInside检查解析后的路径是否仍在 Gateway 工作目录内,否则抛出cwd must stay within the gateway working directory。测试用例 覆盖了“默认 cwd”与“相对路径保持在仓库根内”两个场景。 - 超时下限:withTimeout 把实际超时钳制为
Math.max(200, timeoutMs),超时后通过AbortController中止嵌入运行时的执行并抛出lobster runtime timed out。 - 工作流文件识别:detectWorkflowFile 只在候选字符串不含
|(即不是内联管线)、扩展名属于workflowExts且对应文件真实存在时才按文件执行;含空格的路径也会走stat探测,文件不存在时回退为内联管线解释。
四、审批门:needs_approval 状态与 resume 协议
这是 SKILL.md 的核心内容。当管线中包含 approve 门(或工作流步骤声明了 approval: required)时,执行在门处暂停,返回如下信封:
{
"status": "needs_approval",
"output": [],
"requiresApproval": {
"prompt": "Send 3 draft replies?",
"items": [ ... ],
"resumeToken": "..."
}
}
正确流程是:把 prompt 呈现给用户,拿到用户决定后发起 resume:
{
"action": "resume",
"token": "<resumeToken>",
"approve": true
}
源码层面,resume 的参数校验在 lobster-runner.ts 中非常严格,三条规则都有对应测试用例:
- 必须提供
token或approvalId之一(token or approvalId required);token是完整恢复令牌,approvalId是同一对象中的短 id,二选一即可; approve必须是布尔值(approve required),approve: false表示拒绝并终止工作流,对应终态cancelled;- resume 不接受
pipeline,它只通过令牌指向暂停时的持久化状态。
信封的状态空间
从 LobsterEnvelope 类型 可以看到工具对外只有三种成功状态:
| status | 含义 |
|---|---|
ok |
管线成功执行完毕,output 携带各步结果 |
needs_approval |
在审批门暂停;requiresApproval 携带 prompt、items、resumeToken(及可选 approvalId) |
cancelled |
被显式拒绝或取消 |
失败路径则返回 ok: false 加 { type, message } 错误对象;工具层会把它转成异常抛出(lobster-tool.ts)。另外 normalizeEnvelope 对两种情况直接 fail-closed:嵌入运行时请求交互式输入(needs_input)时抛出“暂不支持”错误;信封序列化后超过 maxStdoutBytes 时抛出 lobster runtime result exceeded maxStdoutBytes。
五、示例工作流:邮件分诊与审批门
SKILL.md 给出两条可直接复制的管线示例:
5.1 基础分诊
gog.gmail.search --query 'newer_than:1d' --max 20 | email.triage
拉取近一天的邮件并分类到 needs_reply、needs_action、fyi 三个桶。
5.2 带审批门的分诊
gog.gmail.search --query 'newer_than:1d' | email.triage | approve --prompt 'Process these?'
与上面相同的分诊,但在返回结果前先暂停等待审批——即上文第四节描述的 needs_approval → resume 流程。
5.3 组合模式:小 CLI + JSON 管道 + 审批
OpenClaw 官方文档 tools/lobster.md 推荐的工程模式是:写一堆只吐 JSON 的小命令,再用 Lobster 把它们串成一条管线,最后用 approve --preview-from-stdin 附带预览:
{
"action": "run",
"pipeline": "exec --json --shell 'inbox list --json' | exec --stdin json --shell 'inbox categorize --json' | exec --stdin json --shell 'inbox apply --json' | approve --preview-from-stdin --limit 5 --prompt 'Apply changes?'",
"timeoutMs": 30000
}
对应的小命令组:
inbox list --json
inbox categorize --json
inbox apply --json
审批通过后用 token resume:
{
"action": "resume",
"token": "<resumeToken>",
"approve": true
}
5.4 工作流文件(.lobster)
pipeline 也可以指向一个 YAML 工作流文件,支持 name、args、steps、env、condition、approval 字段(官方文档示例):
name: inbox-triage
args:
tag:
default: "family"
steps:
- id: collect
command: inbox list --json
- id: categorize
command: inbox categorize --json
stdin: $collect.stdout
- id: approve
command: inbox apply --approve
stdin: $categorize.stdout
approval: required
- id: execute
command: inbox apply --execute
stdin: $categorize.stdout
condition: $approve.approved
配套约定:stdin: $step.stdout / $step.json 传递前序步骤输出;condition(或 when)可以基于 $step.approved 门控步骤。运行时还会向每个步骤的 shell 注入 LOBSTER_ARG_<NAME>(工作流参数,参数名大写、非字母数字折叠为 _)和 LOBSTER_ARGS_JSON 两类环境变量,便于命令引用解析后的参数而不必把原始值内嵌进命令字符串。文件路径形式调用示例:
{
"action": "run",
"pipeline": "workspace/inbox-triage.lobster",
"argsJson": "{\"tag\":\"family\"}"
}
六、关键行为与底层执行机制
SKILL.md 总结了四条关键行为:确定性(相同输入→相同输出,管线执行中无 LLM 方差)、审批门(approve 命令暂停执行并返回 token)、可恢复(用 resume + token 继续)、结构化输出(始终返回带 protocolVersion 的 JSON 信封)。结合源码,可以进一步说明其实现机制:
- 进程内嵌入运行,无子进程:Lobster 不是通过 spawn 外部
lobster命令实现的。loadEmbeddedToolRuntimeFromPackage 动态加载已发布的@clawdbot/lobster/core包(拼接 specifier 是为了避开打包器的静态解析),并在 createEmbeddedLobsterRunner 中每 runner 只加载一次运行时(测试用例loads the embedded runtime once per runner验证了这一点)。工具调用直接拿到 JSON 信封,不经过 stdout 文本解析。 - 输出上限双保险:createLimitedSink 为 stdout/stderr 各建一个有界 Writable 流,累计字节超过
maxStdoutBytes(下限钳制到 1024)立即以lobster stdout exceeded maxStdoutBytes(或 stderr 版本)终止;信封序列化结果还会再校验一次总大小。 - 状态持久化:resume 状态以小 JSON 文件保存在 Lobster 状态目录(默认
~/.lobster/state,可用LOBSTER_STATE_DIR覆盖),token 本身只编码指向该状态的指针,而非完整管线状态——这也是“暂停后可随时 resume”能跨轮次成立的原因。 - 失败即终止:嵌入运行时返回错误信封时,测试 中有
throws when the embedded runtime returns an error envelope与aborts long-running embedded work(超时中止长任务)两个用例对应验证。
常见错误排查表
| 错误信息 | 原因 / 处理 |
|---|---|
lobster runtime timed out |
管线超过 timeoutMs(默认 20 秒)。调大该值或拆分管线 |
lobster stdout exceeded maxStdoutBytes(或 stderr) |
捕获输出超过上限。调大 maxStdoutBytes 或减少输出 |
lobster runtime result exceeded maxStdoutBytes |
JSON 结果本身超上限。调大上限或减少输出 |
run --args-json must be valid JSON |
argsJson 解析失败。修正 JSON 字符串 |
pipeline required |
run 未提供 pipeline |
token or approvalId required / approve required |
resume 参数不完整 |
cwd must stay within the gateway working directory |
cwd 逃逸出 Gateway 工作目录 |
Lobster input requests are not supported … |
运行时请求交互式输入(needs_input),当前嵌入工具不支持,属 fail-closed |
七、进阶:托管 TaskFlow 模式(源码补充)
除裸信封外,工具还支持把一次 Lobster 执行挂到 OpenClaw 的托管 TaskFlow 持久化记录上。参数解析规则在 parseManagedFlowParams 中定义得很严格:
run走托管模式时传flowControllerId+flowGoal(可选flowCurrentStep、flowWaitingStep、flowStateJson),此时禁止同时传flowId/flowExpectedRevision;resume走托管模式时传flowId+flowExpectedRevision(乐观锁版本号)+token/approvalId+approve,此时禁止传flowControllerId、flowGoal或flowStateJson;- 两种模式都要求存在已绑定的 TaskFlow 运行时(index.ts 中通过
api.runtime.tasks.managedFlows.fromToolContext(ctx)按会话绑定),否则抛出Managed TaskFlow run mode requires a bound taskFlow runtime。
lobster-taskflow.ts 中的 executeManagedLobsterFlow 展示了信封与流程记录之间的映射:needs_approval → setWaiting(记录审批等待状态,含 prompt/items/resumeToken);ok → finish;cancelled → cancel;任何异常 → fail。返回结构从“裸信封”变为 { ok, envelope, flow, mutation }。该模式面向需要跨 Gateway 重启保留流程状态的插件/控制器代码,普通 ad-hoc agent 使用裸 run/resume 即可。
八、安全边界小结
综合 README 安全章节 与源码,Lobster 的安全模型可以归纳为:
- 本地进程内执行:工作流在 Gateway 进程内跑,插件本身不发起网络调用;
- 不管密钥:Lobster 不托管 OAuth/令牌,它调用的是各自管理凭证的 OpenClaw 工具;
- 沙箱感知:
ctx.sandboxed时工具直接不注册; - 运行时加固:超时(≥200ms 下限钳制)、stdout/stderr 字节上限(≥1024 下限钳制)、严格 JSON 信封解析、cwd 目录边界检查四道防线,全部由嵌入 runner 统一强制,而不是依赖每条管线自觉。
参考文件
- extensions/lobster/SKILL.md — 本文骨架来源:使用时机决策表、run/resume 协议、关键行为
- extensions/lobster/README.md — 安装、白名单启用、
openclaw.invoke回调用与安全说明 - extensions/lobster/openclaw.plugin.json — 插件 id、optional 工具元数据
- extensions/lobster/index.ts — 工具注册、沙箱禁用、TaskFlow 绑定
- extensions/lobster/src/lobster-tool.ts — 参数 schema、默认值、托管流程参数校验
- extensions/lobster/src/lobster-runner.ts — 嵌入运行时、cwd 守卫、超时与输出上限
- extensions/lobster/src/lobster-taskflow.ts — 信封与托管流程记录的映射
- extensions/lobster/src/lobster-runner.test.ts — 各行为约束的测试验证
- docs/tools/lobster.md — 工作流文件语法、环境变量注入、排障表
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00