| name | taskflow-inbox-triage |
| description | Example TaskFlow pattern for inbox triage, intent routing, waiting on replies, and later summaries. |
| 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
- Create one flow for the inbox batch.
- Run one detached task to classify new items.
- Persist the routing state in
stateJson.
- Move to
waiting only when an outside reply is required.
- Resume the flow when classification or human input completes.
- Finish when the batch has been routed.
Suggested stateJson shape
{
"businessThreads": [],
"personalItems": [],
"eodSummary": []
}
Suggested waitJson when blocked on Slack:
{
"kind": "reply",
"channel": "slack",
"threadKey": "slack:thread-1"
}
Minimal runtime calls
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: {
: [],
: [],
: [],
},
: {
: ,
: ,
: ,
},
});
(!waiting.) {
(waiting.);
}
resumed = taskFlow.({
: waiting..,
: waiting..,
: ,
: ,
: waiting..,
});
(!resumed.) {
(resumed.);
}
taskFlow.({
: resumed..,
: resumed..,
: resumed..,
});
Related example
skills/taskflow/examples/inbox-triage.lobster