Lobster: Typed Workflow Runtime for OpenClaw with Resumable Approval Gates

This page covers Lobster, a typed runtime that executes multi-step tool pipelines as a single deterministic call with built-in approval checkpoints and resume tokens. It is intended for developers needing to orchestrate complex workflows without round-trip tool calls.

Read this when

  • You want deterministic multi-step workflows with explicit approvals
  • You need to resume a workflow without re-running earlier steps

Lobster executes multi-step tool pipelines as a single deterministic tool call, incorporating explicit approval checkpoints and resume tokens. It operates one level above detached background work: for orchestrating flows across multiple detached tasks, refer to Task Flow (openclaw tasks flow); for the task activity ledger, see Background Tasks.

Why

Without Lobster, a multi-step job requires many round-trip tool calls, with the model orchestrating each step. Lobster shifts that orchestration into a typed runtime:

  • One call instead of many: a single Lobster tool call returns a structured result for the entire pipeline.
  • Approvals built in: side effects (send, post, delete) pause the workflow until explicit approval is given.
  • Resumable: a paused workflow returns a token; approve and resume without re-executing earlier steps.

Lobster is a small, constrained DSL rather than a general scripting language: approve/resume is a durable, built-in primitive; pipelines are data (easy to log, diff, replay, review); the tiny grammar limits "creative" code paths so validation stays realistic; timeouts, output caps, sandbox checks, and allowlists are enforced by the runtime, not by each script. Each step can still call any CLI or script - generate .lobster files from other tooling if you want a richer authoring language.

Without Lobster, a recurring email triage looks like:

User: "Check my email and draft replies"
→ openclaw calls gmail.list
→ LLM summarizes
→ User: "draft replies to #2 and #5"
→ LLM drafts
→ User: "send #2"
→ openclaw calls gmail.send
(repeat daily, no memory of what was triaged)

With Lobster, the same job is one call that halts for approval and resumes:

{ "action": "run", "pipeline": "email.triage --limit 20", "timeoutMs": 30000 }
{
  "ok": true,
  "status": "needs_approval",
  "output": [{ "summary": "5 need replies, 2 need action" }],
  "requiresApproval": {
    "type": "approval_request",
    "prompt": "Send 2 draft replies?",
    "items": [],
    "resumeToken": "..."
  }
}

How it works

OpenClaw runs Lobster workflows in-process using the bundled @clawdbot/lobster package as an embedded runner. No external lobster subprocess is spawned; the tool call returns a JSON envelope directly. If the pipeline halts for approval, the envelope carries a resume token (or a short approval ID) so you can continue later.

Enable

Lobster is an optional plugin tool, not enabled by default. It ships bundled, so no separate install step is required - just allow the tool:

{
  "tools": {
    "alsoAllow": ["lobster"]
  }
}

Or per-agent:

{
  "agents": {
    "list": [
      {
        "id": "main",
        "tools": {
          "alsoAllow": ["lobster"]
        }
      }
    ]
  }
}

Note

alsoAllow adds lobster on top of the active tool profile without restricting other core tools. Use tools.allow only if you want a restrictive allowlist mode instead.

The tool is disabled entirely for sandboxed tool contexts.

If you need the standalone Lobster CLI for development or external pipelines (outside the embedded gateway runner), install it from the Lobster repo and put lobster on PATH.

Pattern: small CLI + JSON pipes + approvals

Build tiny commands that speak JSON, then chain them into one Lobster call. (Example command names below - swap in your own.)

inbox list --json
inbox categorize --json
inbox apply --json
{
  "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
}

If the pipeline requests approval, resume with the token:

{
  "action": "resume",
  "token": "<resumeToken>",
  "approve": true
}

Example: map input items into tool calls:

gog.gmail.search --query 'newer_than:1d' \
  | openclaw.invoke --tool message --action send --each --item-key message --args-json '{"provider":"telegram","to":"..."}'

JSON-only LLM steps (llm-task)

For a structured LLM step inside a workflow, enable the optional llm-task plugin tool and call it from Lobster:

{
  "plugins": {
    "entries": {
      "llm-task": { "enabled": true }
    }
  },
  "agents": {
    "list": [
      {
        "id": "main",
        "tools": { "alsoAllow": ["llm-task"] }
      }
    ]
  }
}

Important limitation: embedded Lobster vs openclaw.invoke

The bundled Lobster plugin runs workflows in-process inside the gateway. In that embedded mode, openclaw.invoke does not automatically inherit a gateway URL/auth context for nested OpenClaw CLI tool calls.

That means this pattern is not currently reliable in the embedded runner:

openclaw.invoke --tool llm-task --action json --args-json '{ ... }'

Use the example below only when running the standalone Lobster CLI in an environment where openclaw.invoke is already configured with the correct gateway/auth context.

openclaw.invoke --tool llm-task --action json --args-json '{
  "prompt": "Given the input email, return intent and draft.",
  "thinking": "low",
  "input": { "subject": "Hello", "body": "Can you help?" },
  "schema": {
    "type": "object",
    "properties": {
      "intent": { "type": "string" },
      "draft": { "type": "string" }
    },
    "required": ["intent", "draft"],
    "additionalProperties": false
  }
}'

If you are using the embedded Lobster plugin today, prefer either:

  • a direct llm-task tool call outside Lobster, or
  • non-openclaw.invoke steps inside the Lobster pipeline until a supported embedded bridge is added.

See LLM Task for details and configuration options.

Workflow files (.lobster)

Lobster can run YAML/JSON workflow files with name, args, steps, env, condition, and approval fields. Set pipeline to the file path in the tool call.

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

Notes:

  • stdin: $step.stdout and stdin: $step.json pass a prior step's output.
  • condition (or when) can gate steps on $step.approved.

Injected environment variables

Every step shell inherits the parent environment plus these Lobster-injected variables, so commands can reference resolved workflow args without embedding raw values into the command string:

  • LOBSTER_ARG_<NAME> - one per workflow arg. The name is uppercased with each run of non-alphanumeric characters collapsed to _, so arg user-id becomes LOBSTER_ARG_USER_ID.
  • LOBSTER_ARGS_JSON - every resolved arg as a single JSON string.

This set represents the complete injected collection. No per-step output variables like LOBSTER_STEP_<id>_STDOUT or LOBSTER_STEP_<id>_JSON_<field> exist; shells consider those names undefined, so parameter expansion defaults can mask the error. Instead, access a previous step's output using step references: $step.stdout, $step.json, or $step.json.<field> inside a stdin:, env:, or condition: value. (LOBSTER_STATE_DIR is a separate runtime configuration for the state directory, not a per-run argument.)

Tool parameters

run

{
  "action": "run",
  "pipeline": "gog.gmail.search --query 'newer_than:1d' | email.triage",
  "cwd": "workspace",
  "timeoutMs": 30000,
  "maxStdoutBytes": 512000
}

Execute a workflow file with arguments:

{
  "action": "run",
  "pipeline": "/path/to/inbox-triage.lobster",
  "argsJson": "{\"tag\":\"family\"}"
}
FieldDefaultNotes
pipelinerequiredAn inline pipeline string, or a file path ending in .lobster/.yaml/.yml/.json pointing to a workflow file.
cwdgateway cwdRelative working directory that must resolve within the gateway working directory (absolute paths are not allowed).
timeoutMs20000Kills the run when this limit is reached.
maxStdoutBytes512000Kills the run when captured stdout or stderr exceeds this size.
argsJson-A JSON string of arguments for a workflow file (ignored for inline pipelines).

resume

{
  "action": "resume",
  "token": "<resumeToken>",
  "approve": true
}

resume takes either token (the full resume token from requiresApproval) or approvalId (the short identifier from that same object). Use whichever value the halted run provided. approve is mandatory.

Managed Task Flow mode

Supplying flowControllerId and flowGoal on run (or flowId and flowExpectedRevision on resume) routes the call through the plugin runtime's managed Task Flow API rather than returning a plain envelope. OpenClaw creates or resumes a durable flow record, applies the Lobster envelope to it (waiting on approval, succeeded/failed on completion), and returns { ok, envelope, flow, mutation }. This mode needs a bound Task Flow runtime and is meant for plugin or controller code that requires persistent flow state across gateway restarts, not for typical ad hoc agent usage.

Output envelope

Lobster returns a JSON envelope with one of three statuses:

  • ok - completed successfully
  • needs_approval - paused; requiresApproval contains a resumeToken and a short approvalId, either of which can resume the run
  • cancelled - explicitly denied or cancelled

The tool exposes the envelope in both content (pretty JSON) and details (raw object).

Approvals

When requiresApproval is present, examine the prompt and choose:

  • approve: true - resume and continue side effects
  • approve: false - cancel and finalize the workflow

Use approve --preview-from-stdin --limit N to attach a JSON preview to approval requests without needing custom jq or heredoc glue. Resume state lives as small JSON files under the Lobster state directory (~/.lobster/state by default, overridden with LOBSTER_STATE_DIR). The token itself encodes only a pointer to that state, not the full pipeline state.

OpenProse

OpenProse works well alongside Lobster: use /prose to coordinate multi-agent preparation, then execute a Lobster pipeline for deterministic approvals. When a Prose program requires Lobster, enable the lobster tool for sub-agents through tools.subagents.tools. Refer to OpenProse.

Safety

  • Local in-process only - workflows operate inside the gateway process; the plugin itself makes no network calls.
  • No secrets - Lobster does not handle OAuth; it calls OpenClaw tools that handle it.
  • Sandbox-aware - deactivated when the tool context is sandboxed.
  • Hardened - the embedded runner enforces timeouts and output limits.

Troubleshooting

ErrorCause / fix
lobster runtime timed outPipeline went over timeoutMs. Increase that limit or break the pipeline apart.
lobster stdout exceeded maxStdoutBytes (or stderr)Output captured exceeded the limit. Increase maxStdoutBytes or reduce the output.
run --args-json must be valid JSONargsJson (workflow-file runs) could not be parsed. Correct the JSON string.
lobster runtime failed (or another runtime_error message)The embedded runtime returned an error envelope. Inspect the gateway logs for more information.

Learn more

Case study: community workflows

A single public example: a "second brain" CLI with Lobster pipelines managing three Markdown vaults (personal, partner, shared). The CLI produces JSON for statistics, inbox listings, and stale scans; Lobster chains those commands into workflows like weekly-review, inbox-triage, memory-consolidation, and shared-task-sync, each including approval gates. AI handles judgment (categorization) when accessible and falls back to deterministic rules otherwise.

1,779 words · updated Jul 27, 2026