← Back to Plugins
Tools

Dynamic Workflow

502399493zjw-lgtm By 502399493zjw-lgtm 👁 135 views ▲ 0 votes

Claude-Code-style dynamic workflows for OpenClaw โ€” agent-authored JS orchestration that fans out across isolated sub-agents. A plugin, not a core fork.

GitHub

Install

npm install &&

Configuration Example

// "audit each route file for missing auth, then keep only verified findings"
phase("scan");
const findings = await parallel(
  args.files.map((f) => () => agent(`Does ${f} have a missing auth check? Answer the file + a one-line reason, or "clean".`)),
);

phase("verify");
const confirmed = await parallel(
  findings.filter((x) => x && !/clean/i.test(x)).map((finding) => () =>
    agent(`A reviewer claims: "${finding}". Try to REFUTE it. Reply REAL or FALSE-POSITIVE with one line.`),
  ),
);

return confirmed.filter((v) => v && /REAL/i.test(v));

README

# openclaw-plugin-workflows

**Claude-Code-style dynamic workflows for [OpenClaw](https://github.com/openclaw/openclaw) โ€” as a plugin, no core fork.**

Your OpenClaw agent writes a short JavaScript *orchestration script*; a runtime executes it and fans the work out across many isolated OpenClaw sub-agents. Intermediate results live in script variables, so the main conversation only sees the final, coordinated answer. Use it for codebase-wide sweeps, large migrations, and cross-checked research with adversarial verification โ€” things one turn-by-turn conversation can't coordinate.

> **Status: research-preview.** The core is live-proven on a real `[email protected]` gateway (a sub-agent spawnโ†’awaitโ†’collect round-trip and a real parallel fan-out both pass end-to-end). Some surface/lifecycle pieces are unit-tested and pending manual demo โ€” see [Status & limitations](#status--limitations).

---

## How it works

```
You โ†’ your OpenClaw assistant: "use a workflow to audit every route for missing auth"
        โ”‚
        โ–ผ  the agent writes a JS script and calls the `workflow` tool (you approve first)
   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
   โ”‚  workflow runtime (vm-scoped JS โ€” speed-bump, not a boundary)  โ”‚
   โ”‚    agent() โ”€โ–บ real OpenClaw sub-session (spawn โ†’ await โ†’ read) โ”‚
   โ”‚    parallel() / pipeline() โ”€โ–บ fan out (โ‰ค16 concurrent)         โ”‚
   โ”‚    results converge in script variables                        โ”‚
   โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
        โ”‚  progress โ†’ tool cards (TUI/WebChat/IM) + Canvas phase-tree
        โ–ผ
   one coordinated answer back in your chat
```

## Requirements

- **OpenClaw `2026.6.1` or newer.** The plugin imports `openclaw/plugin-sdk/*`; older end-user builds (e.g. `2026.1.30`) ship only `plugin-sdk/index.js` and **cannot host this plugin**. Check with `openclaw --version`.
- **Node 22.19+** (OpenClaw's own requirement).

## Install

The repo ships a prebuilt, self-contained `dist/` (typebox bundled in; `openclaw` is a
linked peer), so **no build or `npm install` is needed to use it** โ€” clone and install
from the local path. (`openclaw plugins install <git-url>` is not accepted by openclaw
2026.6.1, so clone first.)

```bash
git clone https://github.com/502399493zjw-lgtm/openclaw-dynamic-workflow-plugin.git
openclaw plugins install ./openclaw-dynamic-workflow-plugin --force
openclaw gateway restart
openclaw plugins inspect workflows   # confirm Status: enabled
```

**Update to a newer version:**

```bash
cd openclaw-dynamic-workflow-plugin && git pull
openclaw plugins install . --force && openclaw gateway restart
```

**Working on the plugin itself** (editing source): `pnpm install && pnpm build` rebuilds
`dist/` (esbuild bundle); commit the rebuilt `dist/` so installs stay build-free.

## Using it

Just ask your assistant in natural language โ€” "**use a workflow to โ€ฆ**", "**fan this out across sub-agents**", etc. The agent writes the script and calls the `workflow` tool; you get an **approval prompt** showing the planned script before anything runs.

The `workflow` tool accepts:

| param | type | meaning |
| --- | --- | --- |
| `action` | `"run"` \| `"save"` \| `"run-saved"` | default `"run"` |
| `script` | string | the orchestration script body (required for `run`/`save`) |
| `args` | any | input data exposed to the script as the global `args` |
| `id` | string | saved-workflow id (for `save` / `run-saved`) |
| `name` | string | human label (for `save`) |

## Writing a workflow script

The script body runs in a `node:vm` context whose only injected globals are the primitives below โ€” ambient `import`/`require`/`fs`/`shell`/network are out of scope. **This is a speed-bump, not a security boundary** (`node:vm` is escapable; see [Security model](#security-model)); the real protection is a trusted authoring agent + the approval gate. The only intended I/O is through these primitives:

| primitive | behavior |
| --- | --- |
| `await agent(prompt, { schema?, label? })` | Spawn one isolated sub-agent; returns its final text. With `schema` (a TypeBox/JSON-schema), returns a **validated object** (re-prompts up to 2ร— on mismatch). Returns `null` if the agent fails. |
| `await parallel([() => agent(...), ...])` | **Barrier**: start all, resolve when all settle; order preserved; a failed thunk โ†’ `null`. |
| `await pipeline(items, stage1, stage2, โ€ฆ)` | **No-barrier streaming**: each item flows through all stages independently; a stage gets `(prevResult, originalItem, index)`. Wall-clock โ‰ˆ slowest single chain. |
| `phase(name)` | Open a named phase; subsequent agents group under it (progress + Canvas tree). |
| `log(message)` | Emit a progress line. |
| `args` | The caller-supplied input (or `undefined`). |
| `budget` | `{ total, spent(), remaining() }` token budget (hard ceiling). |

**Limits:** โ‰ค16 concurrent sub-agents, โ‰ค1000 total per run. Sub-agents cannot themselves start workflows (one level of nesting, by design).

### Quick example

```js
// "audit each route file for missing auth, then keep only verified findings"
phase("scan");
const findings = await parallel(
  args.files.map((f) => () => agent(`Does ${f} have a missing auth check? Answer the file + a one-line reason, or "clean".`)),
);

phase("verify");
const confirmed = await parallel(
  findings.filter((x) => x && !/clean/i.test(x)).map((finding) => () =>
    agent(`A reviewer claims: "${finding}". Try to REFUTE it. Reply REAL or FALSE-POSITIVE with one line.`),
  ),
);

return confirmed.filter((v) => v && /REAL/i.test(v));
```

See **[docs/examples.md](docs/examples.md)** for more (pipeline, structured `schema` output, the adversarial-verify pattern, save/run-saved).

## Surfaces

- **Progress** streams as typed tool-progress โ†’ live tool cards on every OpenClaw surface (TUI, WebChat, IM channels).
- **Canvas phase-tree panel**: when a Canvas-capable node is paired, a live phaseโ†’agent tree renders via `canvas.a2ui.pushJSONL`.
- **Approval gate**: the run is held until you approve (shows the planned script).

## Save & resume

- `action: "save"` stores a script under an `id`; `action: "run-saved"` replays it with fresh `args`. Saved defs persist in an OpenClaw managed-flow store.
- A **resume journal** (keyed by script + args + call-site) lets a re-run reuse already-completed sub-agent results instead of re-spawning them.

## Security model

The `node:vm` context is **not** a security boundary โ€” it is escapable (a host-realm
`Function` is reachable through the injected primitives), so it only stops *accidental*
host access and naive escapes. The actual safety controls โ€” the **same model as Claude
Code's workflow tool** โ€” are: (1) the script is authored by a **trusted, aligned agent**,
and (2) the **`before_tool_call` approval gate** (default ON) puts a human in the loop
per run. For a personal, single-user, local gateway this is sufficient (escaping the vm
grants nothing you don't already have on your own machine). If you ever deploy this
gateway **multi-user or exposed to untrusted script authors**, replace the vm with a real
isolate (`isolated-vm`) or a sandboxed subprocess. See `docs/superpowers/plans/api-findings.md` ยง16.

## Status & limitations

- **Live-proven** on a real isolated `[email protected]` gateway: the `agent()` spine
  (spawn โ†’ in-code await via `agent.wait` โ†’ collect), `parallel`/`pipeline` fan-out,
  schema-validated output, per-helper model via a **named agent** (`agent(p,{agent:'id'})`),
  `save` โ†’ restart โ†’ `run-saved`, and **detached background** (returns a `flowId`, polled
  via `action:"status"`) all return real results end-to-end.
- **Pending manual demo** (not auto-verifiable headless): the Canvas render (needs a paired
  Canvas node) and the interactive approval block (needs a human approver).
- **Detached completion is PULL, not push.** OpenClaw's `scheduleSessionTurn` is trust-gated
  to bundled plugins (a no-op for an externally-installed plugin), so a detached run does
  not push a "finished" turn; the caller retrieves the result with `action:"status"`. Per-call
  `{ model }` overrides are gateway-auth-gated and rejected for our caller โ€” use a **named
  agent** instead (the supported way to pick a model/tools/persona).
- External plugins can't use OpenClaw's trust-gated `openKeyedStore` or core-internal task
  ledger, so background/resume/save run on the sanctioned `api.runtime.tasks.managedFlows`
  surface, with an inline + in-memory fallback when it isn't present.
- Requires `2026.6.1+`; will not load on older end-user builds.

## Development

```bash
pnpm install
pnpm test          # unit suite (no OpenClaw needed)
pnpm build         # tsc

# live tests (opt-in) against an isolated dev gateway โ€” never the production one:
OPENCLAW_LIVE_TEST=1 OPENCLAW_GATEWAY_URL=ws://127.0.0.1:18790 \
OPENCLAW_GATEWAY_TOKEN=<token> OPENCLAW_HOME=<isolated home> \
pnpm test src/skeleton/spawn-bridge.live.test.ts
```

Design, acceptance rubric, and the verified SDK contract notes live in **[`docs/superpowers/`](docs/superpowers/)** (`specs/` + `plans/`, incl. `plans/api-findings.md`).

## License

TBD โ€” choose a license before publishing.
tools

Comments

Sign in to leave a comment

Loading comments...