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`
