From f32a5b30dbfcb4d196d7d1067eba761e47cdd41a Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Fri, 3 Apr 2026 03:37:11 +0900 Subject: [PATCH] chore(skills): align taskflow skill with runtime --- skills/clawflow-inbox-triage/SKILL.md | 62 -------- skills/clawflow/SKILL.md | 76 --------- skills/taskflow-inbox-triage/SKILL.md | 119 ++++++++++++++ skills/taskflow/SKILL.md | 149 ++++++++++++++++++ .../examples/inbox-triage.lobster | 2 +- .../examples/pr-intake.lobster | 2 +- 6 files changed, 270 insertions(+), 140 deletions(-) delete mode 100644 skills/clawflow-inbox-triage/SKILL.md delete mode 100644 skills/clawflow/SKILL.md create mode 100644 skills/taskflow-inbox-triage/SKILL.md create mode 100644 skills/taskflow/SKILL.md rename skills/{clawflow => taskflow}/examples/inbox-triage.lobster (95%) rename skills/{clawflow => taskflow}/examples/pr-intake.lobster (94%) diff --git a/skills/clawflow-inbox-triage/SKILL.md b/skills/clawflow-inbox-triage/SKILL.md deleted file mode 100644 index a03985945ce..00000000000 --- a/skills/clawflow-inbox-triage/SKILL.md +++ /dev/null @@ -1,62 +0,0 @@ ---- -name: clawflow-inbox-triage -description: Example ClawFlow authoring pattern for inbox triage. Use when messages need different treatment based on intent, with some routes notifying immediately, some waiting on outside answers, and others rolling into a later summary. -metadata: { "openclaw": { "emoji": "📥" } } ---- - -# ClawFlow inbox triage - -This is a concrete example of how to think about ClawFlow without turning the core runtime into a DSL. - -## Goal - -Triage inbox items with one owner flow: - -- business → post to Slack and wait for reply -- personal → notify the owner now -- everything else → keep for end-of-day summary - -## Pattern - -1. Create one flow for the inbox batch. -2. Run one detached task to classify new items. -3. Resume the flow when classification completes. -4. Route each item in the calling logic. -5. Persist only the summary bucket and the current wait target. - -## Suggested flow outputs - -- `business_threads` -- `personal_items` -- `eod_summary` - -## Minimal runtime calls - -```ts -const flow = createFlow({ - ownerSessionKey, - goal: "triage inbox", -}); - -runTaskInFlow({ - flowId: flow.flowId, - runtime: "acp", - task: "Classify inbox messages", - currentStep: "wait_for_classification", -}); - -resumeFlow({ - flowId: flow.flowId, - currentStep: "route_items", -}); - -appendFlowOutput({ - flowId: flow.flowId, - key: "eod_summary", - value: { subject: "Newsletter", route: "later" }, -}); -``` - -## Related example - -- `skills/clawflow/examples/inbox-triage.lobster` diff --git a/skills/clawflow/SKILL.md b/skills/clawflow/SKILL.md deleted file mode 100644 index bfb2bc6a56b..00000000000 --- a/skills/clawflow/SKILL.md +++ /dev/null @@ -1,76 +0,0 @@ ---- -name: clawflow -description: Use when work should span one or more detached tasks but still behave like one job with a single owner context. ClawFlow is the runtime substrate under authoring layers like Lobster, acpx, or plain code. Keep conditional logic in the caller; use ClawFlow for flow identity, waiting, outputs, and user-facing emergence. -metadata: { "openclaw": { "emoji": "🪝" } } ---- - -# ClawFlow - -Use ClawFlow when a job needs to outlive one prompt or one detached run, but you still want one owner session, one thread context, and one place to inspect or resume the work. - -## When to use it - -- Multi-step background work with one owner -- Work that waits on detached ACP or subagent tasks -- Jobs that may need to emit one clear update back to the owner -- Jobs that need a small persisted output bag between steps - -## What ClawFlow owns - -- flow identity -- owner session and return context -- waiting state -- small persisted outputs -- finish, fail, cancel, and blocked state - -It does **not** own branching or business logic. Put that in Lobster, acpx, or the calling code. - -## Runtime pattern - -1. `createFlow(...)` -2. `runTaskInFlow(...)` -3. `setFlowWaiting(...)` or `setFlowOutput(...)` -4. `resumeFlow(...)` -5. `emitFlowUpdate(...)` only when needed -6. `finishFlow(...)` or `failFlow(...)` - -## Example shape - -```ts -const flow = createFlow({ - ownerSessionKey, - goal: "triage inbox", -}); - -const classify = runTaskInFlow({ - flowId: flow.flowId, - runtime: "acp", - task: "Classify inbox messages", - currentStep: "wait_for_classification", -}); - -resumeFlow({ - flowId: flow.flowId, - currentStep: "route_results", -}); - -setFlowOutput({ - flowId: flow.flowId, - key: "classification", - value: { route: "business" }, -}); -``` - -## Keep conditionals above the runtime - -Use the flow runtime for state and task linkage. Keep decisions in the authoring layer: - -- `business` → post to Slack and wait -- `personal` → notify the owner now -- `later` → append to an end-of-day summary bucket - -## Examples - -- See `skills/clawflow/examples/inbox-triage.lobster` -- See `skills/clawflow/examples/pr-intake.lobster` -- See `skills/clawflow-inbox-triage/SKILL.md` for a concrete routing pattern diff --git a/skills/taskflow-inbox-triage/SKILL.md b/skills/taskflow-inbox-triage/SKILL.md new file mode 100644 index 00000000000..8a6c5e3160f --- /dev/null +++ b/skills/taskflow-inbox-triage/SKILL.md @@ -0,0 +1,119 @@ +name: taskflow-inbox-triage +description: Example TaskFlow authoring pattern for inbox triage. Use when messages need different treatment based on intent, with some routes notifying immediately, some waiting on outside answers, and others rolling into a later summary. +metadata: { "openclaw": { "emoji": "📥" } } + +--- + +# TaskFlow inbox triage + +This is a concrete example of how to think about TaskFlow without turning the core runtime into a DSL. + +## Goal + +Triage inbox items with one owner flow: + +- business → post to Slack and wait for reply +- personal → notify the owner now +- everything else → keep for end-of-day summary + +## Pattern + +1. Create one flow for the inbox batch. +2. Run one detached task to classify new items. +3. Persist the routing state in `stateJson`. +4. Move to `waiting` only when an outside reply is required. +5. Resume the flow when classification or human input completes. +6. Finish when the batch has been routed. + +## Suggested `stateJson` shape + +```json +{ + "businessThreads": [], + "personalItems": [], + "eodSummary": [] +} +``` + +Suggested `waitJson` when blocked on Slack: + +```json +{ + "kind": "reply", + "channel": "slack", + "threadKey": "slack:thread-1" +} +``` + +## Minimal runtime calls + +```ts +const taskFlow = api.runtime.tasks.flow.fromToolContext(ctx); + +const created = taskFlow.createManaged({ + controllerId: "my-plugin/inbox-triage", + goal: "triage inbox", + currentStep: "classify", + stateJson: { + businessThreads: [], + personalItems: [], + eodSummary: [], + }, +}); + +const child = taskFlow.runTask({ + flowId: created.flowId, + runtime: "acp", + childSessionKey: "agent:main:subagent:classifier", + task: "Classify inbox messages", + status: "running", + startedAt: Date.now(), + lastEventAt: Date.now(), +}); + +if (!child.created) { + throw new Error(child.reason); +} + +const waiting = taskFlow.setWaiting({ + flowId: created.flowId, + expectedRevision: created.revision, + currentStep: "await_business_reply", + stateJson: { + businessThreads: ["slack:thread-1"], + personalItems: [], + eodSummary: [], + }, + waitJson: { + kind: "reply", + channel: "slack", + threadKey: "slack:thread-1", + }, +}); + +if (!waiting.applied) { + throw new Error(waiting.code); +} + +const resumed = taskFlow.resume({ + flowId: waiting.flow.flowId, + expectedRevision: waiting.flow.revision, + status: "running", + currentStep: "route_items", + stateJson: waiting.flow.stateJson, +}); + +if (!resumed.applied) { + throw new Error(resumed.code); +} + +taskFlow.finish({ + flowId: resumed.flow.flowId, + expectedRevision: resumed.flow.revision, + stateJson: resumed.flow.stateJson, +}); +``` + +## Related example + +- `skills/taskflow/examples/inbox-triage.lobster` diff --git a/skills/taskflow/SKILL.md b/skills/taskflow/SKILL.md new file mode 100644 index 00000000000..d2e0054d148 --- /dev/null +++ b/skills/taskflow/SKILL.md @@ -0,0 +1,149 @@ +name: taskflow +description: Use when work should span one or more detached tasks but still behave like one job with a single owner context. TaskFlow is the durable flow substrate under authoring layers like Lobster, ACPX, plugins, or plain code. Keep conditional logic in the caller; use TaskFlow for flow identity, child-task linkage, waiting state, revision-checked mutations, and user-facing emergence. +metadata: { "openclaw": { "emoji": "🪝" } } + +--- + +# TaskFlow + +Use TaskFlow when a job needs to outlive one prompt or one detached run, but you still want one owner session, one return context, and one place to inspect or resume the work. + +## When to use it + +- Multi-step background work with one owner +- Work that waits on detached ACP or subagent tasks +- Jobs that may need to emit one clear update back to the owner +- Jobs that need small persisted state between steps +- Plugin or tool work that must survive restarts and revision conflicts cleanly + +## What TaskFlow owns + +- flow identity +- owner session and requester origin +- `currentStep`, `stateJson`, and `waitJson` +- linked child tasks and their parent flow id +- finish, fail, cancel, waiting, and blocked state +- revision tracking for conflict-safe mutations + +It does **not** own branching or business logic. Put that in Lobster, acpx, or the calling code. + +## Current runtime shape + +Canonical plugin/runtime entrypoint: + +- `api.runtime.tasks.flow` +- `api.runtime.taskFlow` still exists as an alias, but `api.runtime.tasks.flow` is the canonical shape + +Binding: + +- `api.runtime.tasks.flow.fromToolContext(ctx)` when you already have trusted tool context with `sessionKey` +- `api.runtime.tasks.flow.bindSession({ sessionKey, requesterOrigin })` when your binding layer already resolved the session and delivery context + +Managed-flow lifecycle: + +1. `createManaged(...)` +2. `runTask(...)` +3. `setWaiting(...)` when waiting on a person or an external system +4. `resume(...)` when work can continue +5. `finish(...)` or `fail(...)` +6. `requestCancel(...)` or `cancel(...)` when the whole job should stop + +## Design constraints + +- Use **managed** TaskFlows when your code owns the orchestration. +- One-task **mirrored** flows are created by core runtime for detached ACP/subagent work; this skill is mainly about managed flows. +- Treat `stateJson` as the persisted state bag. There is no separate `setFlowOutput` or `appendFlowOutput` API. +- Every mutating method after creation is revision-checked. Carry forward the latest `flow.revision` after each successful mutation. +- `runTask(...)` links the child task to the flow. Use it instead of manually creating detached tasks when you want parent orchestration. + +## Example shape + +```ts +const taskFlow = api.runtime.tasks.flow.fromToolContext(ctx); + +const created = taskFlow.createManaged({ + controllerId: "my-plugin/inbox-triage", + goal: "triage inbox", + currentStep: "classify", + stateJson: { + businessThreads: [], + personalItems: [], + eodSummary: [], + }, +}); + +const classify = taskFlow.runTask({ + flowId: created.flowId, + runtime: "acp", + childSessionKey: "agent:main:subagent:classifier", + runId: "inbox-classify-1", + task: "Classify inbox messages", + status: "running", + startedAt: Date.now(), + lastEventAt: Date.now(), +}); + +if (!classify.created) { + throw new Error(classify.reason); +} + +const waiting = taskFlow.setWaiting({ + flowId: created.flowId, + expectedRevision: created.revision, + currentStep: "await_business_reply", + stateJson: { + businessThreads: ["slack:thread-1"], + personalItems: [], + eodSummary: [], + }, + waitJson: { + kind: "reply", + channel: "slack", + threadKey: "slack:thread-1", + }, +}); + +if (!waiting.applied) { + throw new Error(waiting.code); +} + +const resumed = taskFlow.resume({ + flowId: waiting.flow.flowId, + expectedRevision: waiting.flow.revision, + status: "running", + currentStep: "finalize", + stateJson: waiting.flow.stateJson, +}); + +if (!resumed.applied) { + throw new Error(resumed.code); +} + +taskFlow.finish({ + flowId: resumed.flow.flowId, + expectedRevision: resumed.flow.revision, + stateJson: resumed.flow.stateJson, +}); +``` + +## Keep conditionals above the runtime + +Use the flow runtime for state and task linkage. Keep decisions in the authoring layer: + +- `business` → post to Slack and wait +- `personal` → notify the owner now +- `later` → append to an end-of-day summary bucket + +## Operational pattern + +- Store only the minimum state needed to resume. +- Put human-readable wait reasons in `blockedSummary` or structured wait metadata in `waitJson`. +- Use `getTaskSummary(flowId)` when the orchestrator needs a compact health view of child work. +- Use `requestCancel(...)` when a caller wants the flow to stop scheduling immediately. +- Use `cancel(...)` when you also want active linked child tasks cancelled. + +## Examples + +- See `skills/taskflow/examples/inbox-triage.lobster` +- See `skills/taskflow/examples/pr-intake.lobster` +- See `skills/taskflow-inbox-triage/SKILL.md` for a concrete routing pattern diff --git a/skills/clawflow/examples/inbox-triage.lobster b/skills/taskflow/examples/inbox-triage.lobster similarity index 95% rename from skills/clawflow/examples/inbox-triage.lobster rename to skills/taskflow/examples/inbox-triage.lobster index cc462b05a96..74d431728da 100644 --- a/skills/clawflow/examples/inbox-triage.lobster +++ b/skills/taskflow/examples/inbox-triage.lobster @@ -1,4 +1,4 @@ -# Illustrative Lobster authoring example for a ClawFlow-style inbox triage job. +# Illustrative Lobster authoring example for a TaskFlow-style inbox triage job. # Swap the placeholder commands for your own tools or scripts. name: inbox-triage diff --git a/skills/clawflow/examples/pr-intake.lobster b/skills/taskflow/examples/pr-intake.lobster similarity index 94% rename from skills/clawflow/examples/pr-intake.lobster rename to skills/taskflow/examples/pr-intake.lobster index db8c58e6611..5d0ee66d575 100644 --- a/skills/clawflow/examples/pr-intake.lobster +++ b/skills/taskflow/examples/pr-intake.lobster @@ -1,4 +1,4 @@ -# Illustrative Lobster authoring example for a ClawFlow-style PR intake lane. +# Illustrative Lobster authoring example for a TaskFlow-style PR intake lane. # Replace the placeholder commands with repo-specific tooling. name: pr-intake