Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 116 additions & 0 deletions en/docs/workflows/develop/durable-agentic-workflow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
---
sidebar_position: 4
title: "Durable Agentic Workflows"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "## candidate files:"
for f in \
  en/docs/workflows/develop/durable-agentic-workflow.md \
  en/docs/workflows/getting-started/build-a-claim-workflow-agent.md
do
  if [ -f "$f" ]; then
    echo "FOUND $f"
    sed -n '1,4p' "$f" | cat -n
  else
    echo "MISSING $f"
  fi
done

echo
echo "## reference count and sample references:"
python3 - <<'PY'
import os, glob, re
files = [
 "en/docs/workflows/develop/durable-agentic-workflow.md",
 "en/docs/workflows/getting-started/build-a-claim-workflow-agent.md",
]
patterns = [f"workflows/develop/durable-agentic-workflow", f"workflows/getting-started/build-a-claim-workflow-agent"]
for p in patterns:
    hits = [(x, x) for x in glob.glob("**/en/**/*.md", recursive=True) if os.path.isfile(x) for pat in patterns if pat in x]
print("sample paths:")
for f in files:
    if os.path.isfile(f):
        print(f)
        text = open(f, "r", encoding="utf-8").read()
        rel = "/".join(f.split(os.sep)[1:])
        # show headings/frontmatter and markdown link mentions of target filenames/slug forms
        for i,line in enumerate(text.splitlines(),1):
            if line.strip().endswith("]") or "durable-agentic" in line or "claim-workflow-agent" in line or line.startswith("title:"):
                print(f"{rel}:{i}: {line[:220]}")
        basenames = [os.path.basename(x)for x in files]
PY

python3 - <<'PY'
import os
file_to_base = {
 "en/docs/workflows/develop/durable-agentic-workflow.md": "durable-agentic-workflow.md",
 "en/docs/workflows/getting-started/build-a-claim-workflow-agent.md": "build-a-claim-workflow-agent.md",
}
def find_md_refs(base):
    hits = []
    for root, dirs, files in os.walk("."):
        if ".git" in root:
            dirs[:] = []
            continue
        for f in files:
            if f.endswith(".md"):
                path=os.path.join(root,f)
                try:
                    text=open(path, encoding="utf-8").read()
                except Exception:
                    continue
                for i,line in enumerate(text.splitlines(),1):
                    if base in line:
                        hits.append((path,i,line.strip()))
    return hits
for main,base in file_to_base.items():
    print("##", base)
    for path,i,line in find_md_refs(base):
        print(f"{path}:{i}: {line}")
PY

Repository: wso2/docs-integrator

Length of output: 5477


Define concise slugs for these workflow pages and update the links.

These new pages repeat the parent workflows keyword, and the tutorial slug repeats workflow while omitting handling. Use concise, lowercase slugs such as durable-agentic and build-claim-handling-agent, and update the markdown links that point to the current filenames.

📍 Affects 2 files
  • en/docs/workflows/develop/durable-agentic-workflow.md#L3-L3 (this comment)
  • en/docs/workflows/getting-started/build-a-claim-workflow-agent.md#L3-L3
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@en/docs/workflows/develop/durable-agentic-workflow.md` at line 3, Update the
frontmatter slugs in en/docs/workflows/develop/durable-agentic-workflow.md and
en/docs/workflows/getting-started/build-a-claim-workflow-agent.md to concise
lowercase values, using durable-agentic and build-claim-handling-agent
respectively. Update every markdown link referencing the current filenames so
they point to the renamed slug paths.

Source: Path instructions

description: Build AI agents on the durable workflow runtime in WSO2 Integrator — with durable activities, event channels, human tasks, and agent-to-agent collaboration.
keywords: [wso2 integrator, durable agent, agentic workflow, ai agent, human in the loop, events, durable]
---

import ThemedImage from '@theme/ThemedImage';
import useBaseUrl from '@docusaurus/useBaseUrl';

# Durable Agentic Workflows

A durable agentic workflow flips the authoring model: instead of wiring steps together, you describe the goal in natural language and give the agent **capabilities** — activities, events, human tasks, and peer agents. An AI model plans the path at runtime, and because the agent *is* a durable workflow, every reasoning step, tool call, and wait survives crashes and restarts.

## The agent is the declaration

Creating a Durable Agentic Workflow (Add Artifact → **Durable Agentic Workflow** → name → **Create Agent**) generates one module-level declaration that carries everything:

```ballerina
final workflow:DurableAgent supportAgent = check new ({
systemPrompt: {
role: "Expense claim assistant",
instructions: string `Validate each claim, request missing bills, and escalate
unusual claims to a manager before paying.`
},
model: claimModel,
activities: [
validateClaim,
requestBill,
{activity: payClaim, requiresApproval: true, userRoles: "manager",
retryPolicy: "manager"},
{activity: notifyEmployee, retryPolicy: {maxRetries: 3, retryDelay: 2}}
Comment on lines +30 to +32

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

rg -n -C 3 'retryPolicy|requiresApproval|userRoles' en/docs/workflows

Repository: wso2/docs-integrator

Length of output: 6339


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== target file =="
sed -n '1,80p' en/docs/workflows/develop/durable-agentic-workflow.md

echo
echo "== relevant review doc sections =="
sed -n '30,90p' en/docs/workflows/develop/review-activity-and-error-handling.md
sed -n '112,128p' en/docs/workflows/getting-started/build-a-claim-workflow-agent.md

echo
echo "== all retryPolicy/hasRetry occurrences =="
rg -n 'retryPolicy|hasRetry|autoRetry|Auto Retry|Human Review|requiresApproval|userRoles' en/docs/workflows

Repository: wso2/docs-integrator

Length of output: 10404


Remove the invalid activity retry policy.

retryPolicy: "manager" does not belong on this activity declaration. Keep userRoles: "manager" for the approval gate; keep the automatic retry on notifyEmployee as an object, but remove retryPolicy from payClaim.

Proposed correction
-        {activity: payClaim, requiresApproval: true, userRoles: "manager",
-            retryPolicy: "manager"},
+        {activity: payClaim, requiresApproval: true, userRoles: "manager"},
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{activity: payClaim, requiresApproval: true, userRoles: "manager",
retryPolicy: "manager"},
{activity: notifyEmployee, retryPolicy: {maxRetries: 3, retryDelay: 2}}
{activity: payClaim, requiresApproval: true, userRoles: "manager"},
{activity: notifyEmployee, retryPolicy: {maxRetries: 3, retryDelay: 2}}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@en/docs/workflows/develop/durable-agentic-workflow.md` around lines 30 - 32,
Update the payClaim activity declaration to remove retryPolicy: "manager", while
preserving userRoles: "manager" for the approval gate and the object-based
retryPolicy on notifyEmployee.

],
events: [
{name: "billSubmitted", request: BillSubmission, response: string}
],
humanTasks: [
{name: "approveExpense", roles: "manager",
title: "Approve expense claim",
description: "Review the claim and bills, then approve or reject."}
],
maxIter: 16
});
```

The agent model renders this declaration as a single agent node with its capabilities around it. Use the anchored **+** buttons to add capabilities — human tasks (top left), events (bottom left), activities (middle right), agent tools (bottom right) — and click any capability to edit its entry.

<ThemedImage
alt="Agent model showing the agent node with activity, event, and human task capability circles"
sources={{
light: useBaseUrl('/img/workflows/develop/durable-agentic-workflow/01-agent-model.png'),
dark: useBaseUrl('/img/workflows/develop/durable-agentic-workflow/01-agent-model.png'),
}}
/>

:::info One place to edit
Where the agent is *used* — a `supportAgent.run(...)` call in a service — the agent box is a read-only reference with a **Go to Agent** button. Configuration always lives on the agent's own model.
:::

## Capabilities

### Activities

`@workflow:Activity` functions the agent may invoke. Each call runs durably with the same guarantees and the same **retry policies** as any workflow activity — including **Requires Approval** gates and **Human Review** retries (see [Review activities and error handling](review-activity-and-error-handling.md)). The agent proposes; your policies decide what needs a human.

### Events

Typed channels for data arriving mid-conversation. Declare the request (and optional response) type; the agent waits durably on the channel when its instructions call for it:

```ballerina
events: [
{name: "billSubmitted", request: BillSubmission, response: string}
]
```

### Human tasks

Escalation points the agent can raise on its own judgement — "this claim looks unusual" — decided from the [Control Plane](../icp/managing-workflows.md) inbox by the named roles, exactly like workflow human tasks.

### Agent tools and peers

Reuse `@ai:AgentTool` functions and toolkits, or add **peer agents** — other durable agents the agent can delegate to, synchronously or through a callback channel — to build multi-agent systems.

## Driving the agent

```ballerina
// Start an instance; the input becomes part of the first user turn.
string instanceId = check supportAgent.run(claim.toJsonString());

// Deliver an event turn and read that turn's answer.
string token = check supportAgent.sendEvent(instanceId, "billSubmitted", submission);
string reply = check supportAgent.waitForEventResult(instanceId, token);

// Read the final outcome (AgentBusyError while a human decision is pending).
string|error result = supportAgent.getResult(instanceId);
```

All reads are durable: results live in the workflow history, so a crashed caller can re-issue `waitForResult` and get the same answer.
Comment on lines +91 to +98

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

rg -n '\b(waitForResult|waitForEventResult)\b' en/docs/workflows

Repository: wso2/docs-integrator

Length of output: 480


Use waitForEventResult in the durable-result wording.

The example uses supportAgent.waitForEventResult(...), but the next sentence tells callers to re-issue waitForResult. Use the documented method name consistently.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@en/docs/workflows/develop/durable-agentic-workflow.md` around lines 91 - 98,
Update the durable-result wording after the supportAgent.getResult example to
refer to re-issuing waitForEventResult, keeping the documented method name
consistent with the preceding call.


## Why durable agents are different

| Standalone agent frameworks | Durable agentic workflows |
| --- | --- |
| Crash loses the conversation and in-flight tool calls | Every turn and tool call is recorded; restarts resume mid-plan |
| Human approval needs custom plumbing | Gates, reviews, and tasks are one form field away |
| Waiting for external input holds a process | Waits are suspended with zero resources, for days if needed |
| Retry logic in every tool | Declarative per-activity retry policies |

## Traditional or agentic?

Reach for an agentic workflow when the logic is branchy and judgement-heavy ("request whatever is missing, escalate the odd ones"); keep a [transaction workflow](transaction-workflow.md) when the steps are fixed and auditable. The two share activities, tasks, and the runtime — a claim system can use both side by side.

## Next steps

- [Build a Claim Handling Agent](../getting-started/build-a-claim-workflow-agent.md) — the end-to-end getting started.
- [Integration Control Plane](../icp/managing-workflows.md) — approving the agent's gated steps and reading its progress.
126 changes: 126 additions & 0 deletions en/docs/workflows/develop/human-task-workflow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
---
sidebar_position: 2
title: "Human Task Workflows"
description: Pause WSO2 Integrator durable workflows for role-based human decisions and external data — for hours, days, or months — with zero resources held while waiting.
keywords: [wso2 integrator, durable workflow, human task, approval, human in the loop, data event, task inbox, icp]
---

import ThemedImage from '@theme/ThemedImage';
import useBaseUrl from '@docusaurus/useBaseUrl';

# Human Task Workflows

Real processes wait on people: a manager approves an expense, a reviewer checks documents, an employee submits missing information. A durable workflow can stop at any point, hand a **task** to a role, and resume the moment someone decides — whether that takes a minute or a month. While it waits, it holds no threads, no memory, no connections.

## Await a human task

Add an **Await Human Task** step from the palette's **Workflow → Steps** group. Give the task a name, the **roles** that may decide it, and the **payload** the decider should see:

```ballerina
RequestDecision request = check ctx->awaitHumanTask("checkExpenseRequest", "manager",
payload = {"claimId": claim.claimId, "employee": claim.employee,
"amount": claim.amount, "purpose": claim.purpose},
title = string `Check expense request ${claim.claimId}`,
description = "Review the new claim: request the supporting bills, or reject it.",
timeout = {days: 3});
```

The workflow suspends here. The task appears in the [Integration Control Plane](../icp/managing-workflows.md) task inbox for every user holding the `manager` role, showing your title, description, and payload.

<ThemedImage
alt="Integration Control Plane task inbox showing the checkExpenseRequest task with its payload"
sources={{
light: useBaseUrl('/img/workflows/develop/human-task-workflow/01-task-inbox.png'),
dark: useBaseUrl('/img/workflows/develop/human-task-workflow/01-task-inbox.png'),
}}
/>

## Typed decisions build the task form

The value the human submits is typed — declare a record and the Control Plane renders a matching form. Use an **enum** for the action and it becomes a dropdown:

```ballerina
public enum RequestAction {
REQUEST_BILL,
REJECT
}

public type RequestDecision record {|
RequestAction action;
string comment = "";
|};
```

When the manager submits, the workflow resumes with the decision as a plain value:

```ballerina
if request.action == REJECT {
// notify and finish
} else {
// ask the employee for the supporting bills
}
```

:::tip Design for the form
Whatever you put in the decision record is exactly what the decider fills in. Keep it small: an action enum, a comment, maybe a corrected amount.
:::

## Wait for external data

Sometimes the workflow needs *data*, not a decision — the employee submits the bills, a partner system posts a confirmation. Declare a **data event** as a `future` parameter and wait on it:

```ballerina
@workflow:Workflow
function expenseApprovalWorkflow(workflow:Context ctx, ExpenseClaim claim,
record {|future<BillSubmission> billSubmitted;|} dataEvents) returns ExpenseResult|error {
// ... after the manager requests the bills:
BillSubmission submission = check wait dataEvents.billSubmitted;
// validate the bills and continue
}
```

Anyone with the workflow ID can deliver the data — typically a service resource:

```ballerina
resource function post [string workflowId]/bills(BillSubmission submission) returns json|error {
check workflow:sendData(expenseApprovalWorkflow, workflowId, "billSubmitted", submission);
return {workflowId, status: "BILLS_SUBMITTED"};
Comment on lines +82 to +87

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Require authorization before accepting workflow data.

The text says that anyone with a workflow ID can submit BillSubmission. If this resource is exposed without authentication and instance-level authorization, a caller who obtains an ID can inject data and resume the workflow with untrusted input. Require authorization, or state that authentication is omitted from this example.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@en/docs/workflows/develop/human-task-workflow.md` around lines 82 - 87,
Update the workflow data submission example around the post resource for bills
to require authentication and instance-level authorization before calling
workflow:sendData, or explicitly state that these security checks are omitted
for brevity. Ensure unauthorized callers cannot submit BillSubmission data using
only the workflowId.

}
```

While the workflow waits, the Control Plane's execution graph marks the halt point — a **waiting** data node named after the event — so anyone can see exactly what the process is blocked on.

<ThemedImage
alt="Execution graph showing the workflow halted on a waiting billSubmitted data event"
sources={{
light: useBaseUrl('/img/workflows/develop/human-task-workflow/02-waiting-data-event.png'),
dark: useBaseUrl('/img/workflows/develop/human-task-workflow/02-waiting-data-event.png'),
}}
/>

## A complete two-review flow

Combining tasks and data events gives the classic claim pattern — two reviews with an evidence hand-off between them:

1. **`checkExpenseRequest`** — the manager triages the new claim: request bills, or reject.
2. The employee submits bills via `workflow:sendData` → the workflow validates them.
3. **`reviewBills`** — the manager sees the validation result and approves or rejects the payout.

Every decision happens in the Control Plane inbox; the service exposes no task-completion endpoints.

## Timeouts

`timeout` bounds the wait. When it expires the task fails with a timeout error your workflow can handle — escalate, remind, or fail gracefully:

```ballerina
RequestDecision|error decision = ctx->awaitHumanTask("checkExpenseRequest", "manager",
payload = ..., timeout = {days: 3});
if decision is error {
// escalate to a different role, or end the claim
}
```

## Next steps

- [Review activities and error handling](review-activity-and-error-handling.md) — approvals attached to *activities* rather than free-standing tasks.
- [Integration Control Plane](../icp/managing-workflows.md) — where tasks are decided, and how roles map to users.
109 changes: 109 additions & 0 deletions en/docs/workflows/develop/review-activity-and-error-handling.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
---
sidebar_position: 3
title: "Review Activities & Error Handling"
description: Gate risky workflow steps behind human approval and turn failures into human-reviewed retries in WSO2 Integrator durable workflows.
keywords: [wso2 integrator, durable workflow, review activity, retry, error handling, approval gate, human review]
---

import ThemedImage from '@theme/ThemedImage';
import useBaseUrl from '@docusaurus/useBaseUrl';

# Review Activities & Error Handling

Failures and risky steps are where durable workflows earn their keep. Instead of scattering try/catch blocks and retry loops through your code, you attach a **retry policy** to each activity call — and for the steps that matter most, you put a **human review** in front of the step or behind its failure.

## The three retry policies

Every activity call takes a **Retry Policy**, chosen in the call's form:

| Policy | What happens on failure |
| --- | --- |
| **No Automatic Retry** (default) | The error returns to your workflow logic — handle it with normal Ballerina `check`/`if` handling. |
| **Auto Retry** | The engine re-executes the activity with configurable attempts, delay, and backoff. |
| **Human Review** | A **review task** is created for the roles you name. The reviewer can retry as-is, retry with corrected input, or fail the step. |

<ThemedImage
alt="Call Activity form with the Retry Policy dropdown showing No Automatic Retry, Auto Retry, and Human Review"
sources={{
light: useBaseUrl('/img/workflows/develop/review-activity/01-retry-policy-form.png'),
dark: useBaseUrl('/img/workflows/develop/review-activity/01-retry-policy-form.png'),
}}
/>

### Auto Retry — for transient failures

```ballerina
string _ = check ctx->callActivity(notifyEmployee,
{"claimId": claim.claimId, "message": "Your claim was approved."},
retryPolicy = {maxRetries: 3, retryDelay: 2, retryBackoff: 2.0});
```

A flaky notification service recovers transparently; the workflow never sees the intermediate failures.

### Human Review — when a person should fix it

The roles **are** the policy — name who may decide the retry:

```ballerina
string paymentRef = check ctx->callActivity(makePayment,
{"claimId": claim.claimId, "amount": claim.amount, "currency": claim.currency},
retryPolicy = "manager"); // one role, or ["manager", "finance"]
```

When `makePayment` fails (say, the gateway rejects the currency), a **review task** appears in the [Control Plane](../icp/managing-workflows.md) inbox for managers, showing the failing input and the error. The reviewer chooses:

- **Proceed** — rerun with the same input.
- **Proceed with input** — rerun with corrected input (the form is generated from the activity's parameters, pre-filled with the failing values — fix the currency and retry).
- **Reject** — surface the original error to the workflow, which handles it like any other error.

<ThemedImage
alt="Review task in the Control Plane showing the failed makePayment input with Proceed, Proceed with input, and Reject"
sources={{
light: useBaseUrl('/img/workflows/develop/review-activity/02-review-task.png'),
dark: useBaseUrl('/img/workflows/develop/review-activity/02-review-task.png'),
}}
/>

## Approval gates — review *before* the step runs

Some steps should never run without sign-off, even when nothing has failed. On a durable agent's activity, enable **Requires Approval** with **Reviewer Roles**:

```ballerina
activities: [
validateClaim,
{activity: payClaim, requiresApproval: true, userRoles: "manager"}
]
```

Before every `payClaim` call the agent pauses and a review task shows the *proposed* input. The manager can proceed, edit the input, or reject the call — the same three decisions as a failure review, just before the step instead of after it.

## Error handling in the workflow logic

Retry policies handle the step; your workflow logic handles the outcome. Because activities return ordinary Ballerina values and errors, error handling looks like normal code:

```ballerina
string|error paymentRef = ctx->callActivity(makePayment,
{"claimId": claim.claimId, "amount": claim.amount}, retryPolicy = "manager");
if paymentRef is error {
// the reviewer chose Reject — compensate and finish cleanly
string _ = check ctx->callActivity(notifyEmployee,
{"claimId": claim.claimId, "message": "Payment could not be completed."});
return {claimId: claim.claimId, status: "PAYMENT_FAILED"};
}
```

Because completed activities are never re-executed, compensation logic only ever deals with the step that actually failed — everything before it is already safely recorded.

## Choosing a policy

| Situation | Policy |
| --- | --- |
| Flaky downstream, safe to repeat | Auto Retry |
| Bad input a person could correct | Human Review |
| Risky/irreversible step (payments, deletions) | Approval gate (`requiresApproval`) |
| Business-level failure with a fallback path | No Automatic Retry + workflow logic |

## Next steps

- [Human task workflows](human-task-workflow.md) — free-standing decisions and external data.
- [Durable agentic workflows](durable-agentic-workflow.md) — the same policies applied to an AI agent's activities.
Loading
Loading