Lobster runs multi-step tool pipelines as one deterministic tool call, with explicit approval/input checkpoints and resume tokens. Checkpoints belong to the Lobster runner, not a separate orchestration registry.

Why

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

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"
→ apus calls gmail.list
→ LLM summarizes
→ User: "draft replies to #2 and #5"
→ LLM drafts
→ User: "send #2"
→ apus 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

The separately installed official @openclaw/lobster plugin runs Lobster workflows in-process using its embedded @clawdbot/lobster runtime. No external lobster subprocess is spawned; the tool call returns a JSON envelope directly. If the pipeline halts for approval or input, Lobster saves its continuation and returns a resume token. Approval requests can also carry a short approval ID. The call ends at the checkpoint; no process waits for the user's answer.

Enable

Lobster is an optional plugin tool, not installed or enabled by default. Install the official plugin:

apus plugins install @openclaw/lobster

Installation applies to a running Gateway automatically; otherwise it takes effect on the next startup. See Apply changes and inspect.

Then allow the tool globally:

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

Or per-agent:

{
  "agents": {
    "entries": {
      "main": {
        "tools": {
          "alsoAllow": ["lobster"]
        }
      }
    }
  }
}

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' \
  | apus.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": {
    "entries": {
      "main": {
        "tools": { "alsoAllow": ["llm-task"] }
      }
    }
  }
}

Important limitation: embedded Lobster vs apus.invoke

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

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

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

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

For apus.invoke and clawd.invoke, ambient APUS_TOKEN or CLAWD_TOKEN credentials are accepted only for localhost, 127.0.0.1, or [::1] destinations. To send credentials to another HTTP(S) endpoint, pass --token explicitly. This rule also applies to embedded workflows that explicitly configure a remote connection. This command argument is the remote Gateway credential, not the Lobster tool's approval-resume token parameter. If an invocation times out or fails after dispatch, Lobster does not retry it automatically, because the Gateway may already have performed the action.

apus.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:

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:

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:

That is the complete injected set. There are no per-step output variables such as LOBSTER_STEP_<id>_STDOUT or LOBSTER_STEP_<id>_JSON_<field>; shells treat those names as unset, so parameter-expansion defaults can hide the error. Read a prior step's output through step references instead - $step.stdout, $step.json, or $step.json.<field> - in a stdin:, env:, or condition: value. (LOBSTER_STATE_DIR is a separate runtime setting for the state directory, not a per-run arg.)

Tool parameters

run

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

Run a workflow file with args:

{
  "action": "run",
  "pipeline": "/path/to/inbox-triage.lobster",
  "argsJson": "{\"tag\":\"family\"}"
}
Field Default Notes
pipeline required Inline pipeline string, or a path ending in .lobster/.yaml/.yml/.json for a workflow file.
cwd gateway cwd Relative working directory; must resolve inside the gateway working directory (absolute paths are rejected).
timeoutMs 20000 Aborts the run if exceeded.
maxStdoutBytes 512000 Aborts if captured stdout, stderr, or the embedded JSON result exceeds this size.
argsJson - JSON string of args for a workflow file (ignored for inline pipelines).

resume

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

For approvals, use token or approvalId from requiresApproval and a boolean approve. For input, use token from requiresInput and responseJson. To cancel either kind of checkpoint, use cancel: true instead of a decision. Supply exactly one of approve, responseJson, or cancel: true.

Structured input

A workflow input step or an inline ask stage returns needs_input with the question, a JSON Schema and a resume token. Optional defaults and subject provide suggested values and material to review. For example:

{
  "status": "needs_input",
  "requiresInput": {
    "type": "input_request",
    "prompt": "What feedback should be included?",
    "responseSchema": { "type": "string" },
    "resumeToken": "<resumeToken>"
  }
}

The agent presents the question in chat, then sends the user's answer as JSON:

{
  "action": "resume",
  "token": "<resumeToken>",
  "responseJson": "\"Please shorten the introduction.\""
}

responseJson can encode any value allowed by the returned schema, not just an object. Lobster validates the answer before continuing. Invalid JSON or an answer that does not match the schema leaves the checkpoint available for correction. A resume can return another question or approval request.

This is a chat/tool interaction, not an Inbox card or form. The plugin does not list pending checkpoints; retain the returned token to resume later. As with approval tokens, possession of an input token permits resume by a caller allowed to use the tool; tokens are not bound to an Apus user or session.

Output envelope

Lobster returns a JSON envelope with one of four statuses:

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

Approvals

If requiresApproval is present, inspect the prompt and decide:

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

Safety

Troubleshooting

Error Cause / fix
lobster runtime timed out Pipeline exceeded timeoutMs. Increase it or split the pipeline.
lobster stdout exceeded maxStdoutBytes (or stderr) Captured output exceeded the cap. Raise maxStdoutBytes or reduce output.
lobster runtime result exceeded maxStdoutBytes The JSON result exceeded the cap. Raise maxStdoutBytes or reduce output.
run --args-json must be valid JSON argsJson (workflow-file runs) failed to parse. Fix the JSON string.
lobster runtime failed (or another runtime_error message) The embedded runtime returned an error envelope. Check gateway logs for details.

Learn more

Case study: community workflows

One public example: a "second brain" CLI + Lobster pipelines that manage three Markdown vaults (personal, partner, shared). The CLI emits JSON for stats, inbox listings, and stale scans; Lobster chains those commands into workflows like weekly-review, inbox-triage, memory-consolidation, and shared-task-sync, each with approval gates. AI handles judgment (categorization) when available and falls back to deterministic rules when not.

Related