Skip to content

Commit f86e5c0

Browse files
authored
ClawFlow: add linear flow control surface (openclaw#58227)
* ClawFlow: add linear flow control surface * Flows: clear blocked metadata on resume
1 parent ab4ddff commit f86e5c0

21 files changed

Lines changed: 1108 additions & 8 deletions

‎CHANGELOG.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ Docs: https://docs.openclaw.ai
2525
- Flows/tasks: add a minimal SQLite-backed flow registry plus task-to-flow linkage scaffolding, so orchestrated work can start gaining a first-class parent record without changing current task delivery behavior.
2626
- Flows/tasks: route one-task ACP and subagent updates through a parent flow owner context, so detached work can emerge back through the intended parent thread/session instead of speaking only as a raw child task.
2727
- Flows/tasks: persist blocked state on one-task flows and let the same flow reopen cleanly on retry, so blocked detached work can carry a parent-level reason and continue without fragmenting into a new job.
28+
- ClawFlow: add the first linear flow control surface with `openclaw flows list|show|cancel`, keep manual multi-task flows separate from one-task auto-sync flows, and surface doctor recovery hints for obviously orphaned or broken flow/task linkage.
2829
- Matrix/history: add optional room history context for Matrix group triggers via `channels.matrix.historyLimit`, with per-agent watermarks and retry-safe snapshots so failed trigger retries do not drift into newer room messages. (#57022) thanks @chain710.
2930
- Diffs: skip unused viewer-versus-file SSR preload work so `diffs` view-only and file-only runs do less render work while keeping mode outputs aligned. (#57909) thanks @gumadeiras.
3031
- Matrix/threads: add per-DM `threadReplies` overrides and keep thread session isolation aligned with the effective room or DM thread policy from the triggering message onward. (#57995) thanks @teconomix.
@@ -33,6 +34,7 @@ Docs: https://docs.openclaw.ai
3334
- Slack/exec approvals: add native Slack approval routing and approver authorization so exec approval prompts can stay in Slack instead of falling back to the Web UI or terminal. Thanks @vincentkoc.
3435

3536
### Fixes
37+
3638
- Image generation/build: write stable runtime alias files into `dist/` and route provider-auth runtime lookups through those aliases so image-generation providers keep resolving auth/runtime modules after rebuilds instead of crashing on missing hashed chunk files.
3739
- Config/runtime: pin the first successful config load in memory for the running process and refresh that snapshot on successful writes/reloads, so hot paths stop reparsing `openclaw.json` between watcher-driven swaps.
3840
- Config/legacy cleanup: stop probing obsolete alternate legacy config names and service labels during local config/service detection, while keeping the active `~/.openclaw/openclaw.json` path canonical.

‎docs/automation/clawflow.md‎

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
---
2+
summary: "ClawFlow workflow orchestration for background tasks and detached runs"
3+
read_when:
4+
- You want a flow to own one or more detached tasks
5+
- You want to inspect or cancel a background job as a unit
6+
- You want to understand how flows relate to tasks and background work
7+
title: "ClawFlow"
8+
---
9+
10+
# ClawFlow
11+
12+
ClawFlow is the flow layer above [Background Tasks](/automation/tasks). Tasks still track detached work. ClawFlow groups those task runs into a single job, keeps the parent owner context, and gives you a flow-level control surface.
13+
14+
Use ClawFlow when the work is more than a single detached run. A flow can still be one task, but it can also coordinate multiple tasks in a simple linear sequence.
15+
16+
## TL;DR
17+
18+
- Tasks are the execution records.
19+
- ClawFlow is the job-level wrapper above tasks.
20+
- A flow keeps one owner/session context for the whole job.
21+
- Use `openclaw flows list`, `openclaw flows show`, and `openclaw flows cancel` to inspect or manage flows.
22+
23+
## Quick start
24+
25+
```bash
26+
openclaw flows list
27+
openclaw flows show <flow-id-or-owner-session>
28+
openclaw flows cancel <flow-id-or-owner-session>
29+
```
30+
31+
## How it relates to tasks
32+
33+
Background tasks still do the low-level work:
34+
35+
- ACP runs
36+
- subagent runs
37+
- cron executions
38+
- CLI-initiated runs
39+
40+
ClawFlow sits above that ledger:
41+
42+
- it keeps related task runs under one flow id
43+
- it tracks the flow state separately from the individual task state
44+
- it makes blocked or multi-step work easier to inspect from one place
45+
46+
For a single detached run, the flow can be a one-task flow. For more structured work, ClawFlow can keep multiple task runs under the same job.
47+
48+
## CLI surface
49+
50+
The flow CLI is intentionally small:
51+
52+
- `openclaw flows list` shows active and recent flows
53+
- `openclaw flows show <lookup>` shows one flow and its linked tasks
54+
- `openclaw flows cancel <lookup>` cancels the flow and any active child tasks
55+
56+
The lookup token accepts either a flow id or the owner session key.
57+
58+
## Related
59+
60+
- [Background Tasks](/automation/tasks) — detached work ledger
61+
- [CLI: flows](/cli/flows) — flow inspection and control commands
62+
- [Cron Jobs](/automation/cron-jobs) — scheduled jobs that may create tasks

‎docs/automation/index.md‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,19 @@ The most effective setups combine multiple mechanisms:
5151
3. **Hooks** react to specific events (tool calls, session resets, compaction) with custom scripts.
5252
4. **Standing Orders** give the agent persistent context ("always check the project board before replying").
5353
5. **Background Tasks** automatically track all detached work so you can inspect and audit it.
54+
6. **ClawFlow** groups related detached tasks into a single flow when the work needs a higher-level job view.
5455

5556
See [Cron vs Heartbeat](/automation/cron-vs-heartbeat) for a detailed comparison of the two scheduling mechanisms.
5657

58+
## ClawFlow
59+
60+
ClawFlow sits above [Background Tasks](/automation/tasks). Tasks still track the detached runs, while ClawFlow groups related task runs into one job that you can inspect or cancel from the CLI.
61+
62+
See [ClawFlow](/automation/clawflow) for the flow overview and [CLI: flows](/cli/flows) for the command surface.
63+
5764
## Related
5865

5966
- [Cron vs Heartbeat](/automation/cron-vs-heartbeat) — detailed comparison guide
67+
- [ClawFlow](/automation/clawflow) — flow-level orchestration above tasks
6068
- [Troubleshooting](/automation/troubleshooting) — debugging automation issues
6169
- [Configuration Reference](/gateway/configuration-reference) — all config keys

‎docs/automation/tasks.md‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,12 @@ A sweeper runs every **60 seconds** and handles three things:
210210

211211
## How tasks relate to other systems
212212

213+
### Tasks and ClawFlow
214+
215+
ClawFlow is the flow layer above tasks. A flow groups one or more task runs into a single job, owns the parent session context, and gives you a higher-level control surface for blocked or multi-step work.
216+
217+
See [ClawFlow](/automation/clawflow) for the flow overview and [CLI: flows](/cli/flows) for the command surface.
218+
213219
### Tasks and cron
214220

215221
A cron job **definition** lives in `~/.openclaw/cron/jobs.json`. **Every** cron execution creates a task record — both main-session and isolated. Main-session cron tasks default to `silent` notify policy so they track without generating notifications.
@@ -233,7 +239,9 @@ A task's `runId` links to the agent run doing the work. Agent lifecycle events (
233239
## Related
234240

235241
- [Automation Overview](/automation) — all automation mechanisms at a glance
242+
- [ClawFlow](/automation/clawflow) — job-level orchestration above tasks
236243
- [Cron Jobs](/automation/cron-jobs) — scheduling background work
237244
- [Cron vs Heartbeat](/automation/cron-vs-heartbeat) — choosing the right mechanism
238245
- [Heartbeat](/gateway/heartbeat) — periodic main-session turns
246+
- [CLI: flows](/cli/flows) — flow inspection and control commands
239247
- [CLI: Tasks](/cli/index#tasks) — CLI command reference

‎docs/cli/flows.md‎

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
---
2+
summary: "CLI reference for `openclaw flows` (list, inspect, cancel)"
3+
read_when:
4+
- You want to inspect or cancel a flow
5+
- You want to see how background tasks roll up into a higher-level job
6+
title: "flows"
7+
---
8+
9+
# `openclaw flows`
10+
11+
Inspect and manage [ClawFlow](/automation/clawflow) jobs.
12+
13+
```bash
14+
openclaw flows list
15+
openclaw flows show <lookup>
16+
openclaw flows cancel <lookup>
17+
```
18+
19+
## Commands
20+
21+
### `flows list`
22+
23+
List tracked flows and their task counts.
24+
25+
```bash
26+
openclaw flows list
27+
openclaw flows list --status blocked
28+
openclaw flows list --json
29+
```
30+
31+
### `flows show`
32+
33+
Show one flow by flow id or owner session key.
34+
35+
```bash
36+
openclaw flows show <lookup>
37+
openclaw flows show <lookup> --json
38+
```
39+
40+
The output includes the flow status, current step, blocked summary when present, and linked tasks.
41+
42+
### `flows cancel`
43+
44+
Cancel a flow and any active child tasks.
45+
46+
```bash
47+
openclaw flows cancel <lookup>
48+
```
49+
50+
## Related
51+
52+
- [ClawFlow](/automation/clawflow) — job-level orchestration above tasks
53+
- [Background Tasks](/automation/tasks) — detached work ledger
54+
- [CLI reference](/cli/index) — full command tree

‎docs/cli/index.md‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ This page describes the current CLI behavior. If commands change, update this do
4545
- [`tui`](/cli/tui)
4646
- [`browser`](/cli/browser)
4747
- [`cron`](/cli/cron)
48+
- [`flows`](/cli/flows)
4849
- [`dns`](/cli/dns)
4950
- [`docs`](/cli/docs)
5051
- [`hooks`](/cli/hooks)
@@ -171,6 +172,10 @@ openclaw [--dev] [--profile <name>] <command>
171172
show
172173
notify
173174
cancel
175+
flows
176+
list
177+
show
178+
cancel
174179
gateway
175180
call
176181
health
@@ -809,6 +814,14 @@ List and manage [background task](/automation/tasks) runs across agents.
809814
- `tasks cancel <id>` — cancel a running task
810815
- `tasks audit` — surface operational issues (stale, lost, delivery failures)
811816

817+
### `flows`
818+
819+
List and manage [ClawFlow](/automation/clawflow) jobs across agents.
820+
821+
- `flows list` — show active and recent flows
822+
- `flows show <id>` — show details for a specific flow
823+
- `flows cancel <id>` — cancel a flow and its active child tasks
824+
812825
## Gateway
813826

814827
### `gateway`

‎docs/docs.json‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1121,6 +1121,7 @@
11211121
"automation/cron-jobs",
11221122
"automation/cron-vs-heartbeat",
11231123
"automation/tasks",
1124+
"automation/clawflow",
11241125
"automation/troubleshooting",
11251126
"automation/webhook",
11261127
"automation/gmail-pubsub",
@@ -1432,6 +1433,7 @@
14321433
"cli/approvals",
14331434
"cli/browser",
14341435
"cli/cron",
1436+
"cli/flows",
14351437
"cli/node",
14361438
"cli/nodes",
14371439
"cli/sandbox"

‎src/cli/program/register.status-health-sessions.test.ts‎

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import { registerStatusHealthSessionsCommands } from "./register.status-health-s
55
const mocks = vi.hoisted(() => ({
66
statusCommand: vi.fn(),
77
healthCommand: vi.fn(),
8+
flowsListCommand: vi.fn(),
9+
flowsShowCommand: vi.fn(),
10+
flowsCancelCommand: vi.fn(),
811
sessionsCommand: vi.fn(),
912
sessionsCleanupCommand: vi.fn(),
1013
tasksListCommand: vi.fn(),
@@ -23,6 +26,9 @@ const mocks = vi.hoisted(() => ({
2326

2427
const statusCommand = mocks.statusCommand;
2528
const healthCommand = mocks.healthCommand;
29+
const flowsListCommand = mocks.flowsListCommand;
30+
const flowsShowCommand = mocks.flowsShowCommand;
31+
const flowsCancelCommand = mocks.flowsCancelCommand;
2632
const sessionsCommand = mocks.sessionsCommand;
2733
const sessionsCleanupCommand = mocks.sessionsCleanupCommand;
2834
const tasksListCommand = mocks.tasksListCommand;
@@ -42,6 +48,12 @@ vi.mock("../../commands/health.js", () => ({
4248
healthCommand: mocks.healthCommand,
4349
}));
4450

51+
vi.mock("../../commands/flows.js", () => ({
52+
flowsListCommand: mocks.flowsListCommand,
53+
flowsShowCommand: mocks.flowsShowCommand,
54+
flowsCancelCommand: mocks.flowsCancelCommand,
55+
}));
56+
4557
vi.mock("../../commands/sessions.js", () => ({
4658
sessionsCommand: mocks.sessionsCommand,
4759
}));
@@ -79,6 +91,9 @@ describe("registerStatusHealthSessionsCommands", () => {
7991
runtime.exit.mockImplementation(() => {});
8092
statusCommand.mockResolvedValue(undefined);
8193
healthCommand.mockResolvedValue(undefined);
94+
flowsListCommand.mockResolvedValue(undefined);
95+
flowsShowCommand.mockResolvedValue(undefined);
96+
flowsCancelCommand.mockResolvedValue(undefined);
8297
sessionsCommand.mockResolvedValue(undefined);
8398
sessionsCleanupCommand.mockResolvedValue(undefined);
8499
tasksListCommand.mockResolvedValue(undefined);
@@ -317,4 +332,39 @@ describe("registerStatusHealthSessionsCommands", () => {
317332
runtime,
318333
);
319334
});
335+
336+
it("runs flows list from the parent command", async () => {
337+
await runCli(["flows", "--json", "--status", "blocked"]);
338+
339+
expect(flowsListCommand).toHaveBeenCalledWith(
340+
expect.objectContaining({
341+
json: true,
342+
status: "blocked",
343+
}),
344+
runtime,
345+
);
346+
});
347+
348+
it("runs flows show subcommand with lookup forwarding", async () => {
349+
await runCli(["flows", "show", "flow-123", "--json"]);
350+
351+
expect(flowsShowCommand).toHaveBeenCalledWith(
352+
expect.objectContaining({
353+
lookup: "flow-123",
354+
json: true,
355+
}),
356+
runtime,
357+
);
358+
});
359+
360+
it("runs flows cancel subcommand with lookup forwarding", async () => {
361+
await runCli(["flows", "cancel", "flow-123"]);
362+
363+
expect(flowsCancelCommand).toHaveBeenCalledWith(
364+
expect.objectContaining({
365+
lookup: "flow-123",
366+
}),
367+
runtime,
368+
);
369+
});
320370
});

0 commit comments

Comments
 (0)