diff --git a/en/docs/develop/integration-artifacts/integration-artifacts.md b/en/docs/develop/integration-artifacts/integration-artifacts.md index 07acc26bd6..663b84d1c6 100644 --- a/en/docs/develop/integration-artifacts/integration-artifacts.md +++ b/en/docs/develop/integration-artifacts/integration-artifacts.md @@ -2,12 +2,12 @@ sidebar_position: 1 title: Integration Artifacts description: Choose the right artifact type to expose APIs, react to events, process files, run scheduled jobs, or power AI agents in WSO2 Integrator. -keywords: [wso2 integrator, integration artifacts, http service, event handler, automation, file integration] +keywords: [wso2 integrator, integration artifacts, http service, event handler, automation, file integration, durable workflow] --- # Integration Artifacts -Integration artifacts are the building blocks of every integration. Each type is designed for a specific trigger and communication pattern: receiving HTTP requests, reacting to messages, processing files, running on a schedule, or serving AI agent tools. Choosing the right artifact for the job keeps your integration logic focused and your project easy to navigate. +Integration artifacts are the building blocks of every integration. Each type is designed for a specific trigger and communication pattern: receiving HTTP requests, reacting to messages, processing files, running on a schedule, driving a long-running process, or serving AI agent tools. Choosing the right artifact for the job keeps your integration logic focused and your project easy to navigate. ## Artifact categories @@ -28,6 +28,15 @@ Build AI-powered integrations that use large language models to reason, respond, | AI Chat Agent | An LLM-backed agent accessible via a chat interface or API. Covered in the [AI Integrations](../../genai/overview.md) section. | | MCP Service | Exposes integration capabilities as tools via the Model Context Protocol for use by AI assistants. Covered in the [AI Integrations](../../genai/overview.md) section. | +### Durable workflows + +Model long-running business processes that survive restarts, wait for human decisions, and retry failed steps. + +| Artifact | Description | +|---|---| +| Durable Workflow | A flow of activities that records every completed step and resumes where it left off after a crash or restart. Use for approvals, multi-step transactions, and processes that wait on people or external events. Covered in the [Durable Workflows](../../workflows/overview.md) section. | +| Durable Agentic Workflow | An AI agent that runs on the same durable runtime, so it gets crash safety, human tasks, timers, and retries. Use when the steps are branchy and hard to enumerate up front. Covered in the [Durable Workflows](../../workflows/overview.md) section. | + ### Integration as API Expose your integration logic as a callable endpoint. Clients send a request and receive a response. diff --git a/en/docs/workflows/develop/activities.md b/en/docs/workflows/develop/activities.md new file mode 100644 index 0000000000..308f8ebce4 --- /dev/null +++ b/en/docs/workflows/develop/activities.md @@ -0,0 +1,103 @@ +--- +sidebar_position: 3 +title: "Activities" +description: Activities are the recorded units of work in a WSO2 Integrator durable workflow — exactly-once on replay, retryable on failure, and shared by workflows and durable agents alike. +keywords: [wso2 integrator, durable workflow, activity, call activity, exactly once, replay, idempotency, retry] +--- + +import ThemedImage from '@theme/ThemedImage'; +import useBaseUrl from '@docusaurus/useBaseUrl'; + +# Activities + +An **activity** is a single unit of work that the durable runtime records and its modeled as a function. Anything that touches the outside world — an API call, a database write, a payment, an email — belongs in an activity rather than in the workflow body. That split is what makes a workflow crash-safe: the workflow function can be replayed from the start after a restart, while the work already done inside activities is read back from the record instead of being repeated. + + + +## Why the split matters + +When a workflow resumes after a crash or a restart, the runtime replays the workflow function to rebuild its state. During that replay: + +- A **completed activity is never re-executed.** Its recorded result is handed straight back, so the card is not charged twice and the email is not sent twice. +- A **failed activity can be retried** on its own, without repeating the steps that already succeeded. + +:::warning Keep the workflow body deterministic +Everything **outside** an activity is ordinary code that runs again on replay. No direct API calls, no random values, and no wall-clock reads in the workflow body — put that work in an activity so its result is recorded instead of recomputed. +::: + +## Define an activity + +To create an activity, click **+** on **Workflow Activities** in the left sidebar. + +![The left sidebar with the + button on the Workflow Activities entry](/img/workflows/develop/activities/add-workflow-activity.png) + +**Create Activity** form provides the following fields for defining the activity function: + +| Field | Required | Description | +|---|---|---| +| **Activity Name** | Yes | The name of the activity function. This is the name the workflow calls and the name shown on the activity's node in the execution graph. | +| **Description** | No | Explains what the activity does. | +| **Parameters** | No | Defines the inputs of the activity function. Each parameter has a name and a type. Selecting **+ Add Parameter** adds a new parameter definition row. | +| **Return Type** | No | The type of the value the activity returns, for example `string` or `string\|error`. Leave it empty for an activity that returns nothing. | + +After clicking **Create**, you will be directed to design its body in the same flow diagram used for any other function. + +:::tip Check the prebuilt ones first +REST calls, SOAP calls, and SMTP email already have durable wrappers that ship with the runtime, so there is nothing to write for those. See [Prebuilt activities](prebuilt-activities/index.md). +::: + +## Call an activity from a workflow + +To call an activity from a workflow, click **+** on the workflow diagram and select **Call Activity** from the palette's **Workflow → Steps** group. Then select the activity function you want to call. +Call Activity form provides the following fields for calling an activity function: + +| Field | Required | Description | +|------------------------|--------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **Activity Arguments** | Yes | Form field will appear for each activity function parameter. Provide the relevent arguments for each required parameter | +| **Retry Policy** | Yes | When an activity call failied, how should the workflow handle it. No Automatic Retry, Auto Retry, Human Review. See [Error handling and review activities](review-activity-and-error-handling.md). | +| **Result** | Only if the output of the activity is not `null` | Name of the result variable to capture the activity's output. | +| **Result type** | Only if the output of the activity is not `null` | Type of the resulting data of the activity function. | +| **Check Error** | No | Under **Advanced Configurations**. Adds `check` to the call so a failure automatically propagates out from the workflow. Clear it to handle the error yourself. Defaults to `checked`. See [Error handling in the workflow logic](review-activity-and-error-handling.md#error-handling-in-the-workflow-logic). | + +![Call an activity from a workflow](/img/workflows/develop/activities/activity-call.gif) + +:::tip Idempotent side effects +A *completed* activity never runs twice, but a *failed* attempt may run again once retries are on. Make the side effect idempotent — pass an idempotency key to the payment gateway, upsert instead of insert — so a repeated attempt cannot double-charge or duplicate a record. +::: + +## Activities on a durable agent + +A durable agent uses the same activities. Instead of you wiring the call order, the model chooses which activity to call and when, and each call is recorded exactly as it is in a hand-wired workflow. + +To give an agent an activity, click **+** on the activity icon at the right bottom of the agent node and select the activity. The register form provides the following fields for registering the activity as a durable agent activity: + +| Field | Required | Description | +|---|---|---| +| **Retry Policy** | Yes | Engine retry strategy when the activity fails: no automatic retry (an AI agent may still re-invoke it), automatic backoff retries, or a human review task. See [Error handling and review activities](review-activity-and-error-handling.md). | +| **Reviewer Roles** | Only for **Human Review** | Roles permitted to decide the human review, for example `"manager"` or `["finance", "manager"]`. | +| **Advanced Configurations** | No | Approval settings for the registered activity, collapsed by default. | +| ↳ **Requires Approval** | No | Gate the activity: before the agent runs it, a review activity is created and the agent suspends durably until a reviewer proceeds (optionally editing the arguments) or rejects. | +| ↳ **Reviewer Roles** | Only with **Requires Approval** | Roles permitted to decide the approval review of this activity, for example `"support-lead"` or `["finance", "manager"]`. | + +![Register a workflow activity as a durable agent activity](/img/workflows/develop/activities/register-activity-as-agent-activity.png) + +Registering the activity is what makes it available to the agent. See [Durable agentic workflows](durable-agentic-workflow.md). + +## Watching activities run + +Each activity call appears as an `ACTIVITY` node in the instance's execution graph in the [Integration Control Plane](../icp/managing-workflows.md), so you can see which step an instance is on, which activities have completed, and which one failed. The same graph is available over the [Management API](../reference/management-api.md). + +[//]: # (Add a screenshot of the execution graph with an activity node highlighted.) + +## Next steps + +- [Prebuilt activities](prebuilt-activities/index.md) — durable REST, SOAP, and email calls with no wrapper to write. +- [Durable timers](durable-timers.md) — pause between activities without holding resources. +- [Error handling and review activities](review-activity-and-error-handling.md) — retry policies and approval gates. +- [Build an order processing workflow](../getting-started/build-an-order-processing-workflow.md) — activities wired into a complete flow. diff --git a/en/docs/workflows/develop/create-workflow.md b/en/docs/workflows/develop/create-workflow.md new file mode 100644 index 0000000000..495d7e2c7f --- /dev/null +++ b/en/docs/workflows/develop/create-workflow.md @@ -0,0 +1,54 @@ +--- +sidebar_position: 1 +title: "Create a Workflow" +description: Add a durable workflow artifact in WSO2 Integrator, give it an input type, and design its steps on the workflow diagram. +keywords: [wso2 integrator, durable workflow, create workflow, workflow artifact, workflow input type, workflow context] +--- + +# Create a Workflow + +A **durable workflow** is an artifact in your integration, the same as a service or an automation. You create it once, give it the shape of the data it starts with, and then design its steps on a diagram. + +## Launching the wizard + +1. In the design view, click **+ Add Artifact**. +2. On the **Artifacts** page, under **Durable Workflow**, click **Durable Workflow**. + + ![The Artifacts page with the Durable Workflow card under the Durable Workflow section](/img/workflows/develop/create-workflow/add-artifact.png) + + **Durable Agentic Workflow** beside it produces the same kind of artifact, but you describe the goal and let a model choose the steps instead of wiring them yourself. See [Durable agentic workflows](durable-agentic-workflow.md). + +3. Fill in the **Create New Durable Workflow** form: + + | Field | Required | Description | + |---|---|---| + | **Name** | Yes | The workflow's identifier. It is how the workflow is referenced when it is started, and the name it appears under in workflow management. | + | **Workflow Input Data Type** | No | The type of the data the workflow starts with, usually a record. See [Types](../../develop/integration-artifacts/supporting/types.md). | + + :::tip Design it for the launcher + Whatever you put in this type is what every caller has to supply, including the form the [Integration Control Plane](../icp/start-workflow.md) generates for starting a run by hand. Keep it to the data the process actually needs. + ::: + + ![The Create New Durable Workflow form with Name set to orderWorkflow and Workflow Input Data Type set to OrderInfo](/img/workflows/develop/create-workflow/create-workflow-form.png) + +4. Click **Create**. + +The workflow opens on its own diagram with a single **Start** node, and appears under **Workflows** in the sidebar. + +For a worked example that fills this in end to end, see [Build an order processing workflow](../getting-started/build-an-order-processing-workflow.md). + +## Design the steps + +The workflow diagram is the same flow diagram used everywhere else in WSO2 Integrator, with a group of durable steps added to the node panel: + +| Group | What it holds | +|---|---| +| **Workflow** > **Steps** | [Call Activity](activities.md), [Await Human Task](human-task-workflow.md), [Await Data Event](data-events.md), and [Sleep](durable-timers.md). | +| **Workflow** > **Workflow Functions** | Replay-safe helpers: current time, whether the run is replaying, and the run's own ID and type. | +| **Statement**, **Control**, **Error Handling** | The ordinary building blocks: variables, function calls, `if`, `while`, `foreach`, and error handling. | + +## Next steps + +- [Start a workflow](start-workflow.md) — launch a run from a service, an automation, or the console. +- [Activities](activities.md) — the recorded units of work a workflow calls. +- [Build an order processing workflow](../getting-started/build-an-order-processing-workflow.md) — the whole flow, step by step. diff --git a/en/docs/workflows/develop/data-events.md b/en/docs/workflows/develop/data-events.md new file mode 100644 index 0000000000..cff3759b87 --- /dev/null +++ b/en/docs/workflows/develop/data-events.md @@ -0,0 +1,71 @@ +--- +sidebar_position: 4 +title: "Await Data Events" +description: Pause a WSO2 Integrator durable workflow until an external system or a person delivers data, then resume with that value as a typed, recorded result. +keywords: [wso2 integrator, durable workflow, data event, send data, external data, wait, callback, long running] +--- + +import ThemedImage from '@theme/ThemedImage'; +import useBaseUrl from '@docusaurus/useBaseUrl'; + +# Await Data Events + +Sometimes a workflow needs *data*, not a decision — the employee submits the supporting bills, a partner system posts a shipping confirmation, a scanner returns a document. A **data event** is a named slot the workflow waits on. The instance suspends until something delivers a value into that slot, then resumes with the value as an ordinary typed result. + + + +Like every other durable wait, it costs nothing while it waits and it survives a restart. + +## Pause workflow for data event + +1. On the workflow diagram, click **+** where the workflow should wait. +2. In the node panel, under **Workflow** > **Steps**, click **Await Data Event**. The **Await Data** form opens. +3. Fill in the form: + + | Field | Required | Description | + |---|---|---| + | **Data Receive Variable Name** | Yes | The variable that receives the value once it arrives. | + | **Data Type** | Yes | The type of the value the workflow expects. Pick an existing type, or create one from the list. | + | **Data Name** | Yes | The name used when sending the data into this workflow. | + | **Min Count** | No | Under **Advanced Configurations**. How many of the awaited events must arrive before the workflow continues. Defaults to all of them. | + | **Timeout** | No | Under **Advanced Configurations**. The longest the workflow waits, as a duration record. The wait returns an error when the timeout expires, which your workflow can handle. | + + **Timeout** opens a **Record Configuration** editor: tick the units you want, such as **minutes**, and fill in their values. Switch the field to **Expression** to write the record yourself instead. + +4. Click **Add** to commit the **Data Waits** entry. It collapses to a row showing its type and variable. Use **+ Add Data Waits** to wait on more than one event. +5. Click **Save**. + +![Adding an Await Data Event step and bounding the wait with a timeout](/img/workflows/develop/data-events/await-data-event.gif) + +The diagram gains a wait node, drawn with an arrow arriving from outside the flow, and the workflow now suspends there. + +:::tip Data event or human task? +Use a **data event** when a system or a person is submitting *content* the workflow will process. Use a [human task](human-task-workflow.md) when a person is making a *decision* the Control Plane should render as a form in their inbox. +::: + +## Watching a waiting workflow + +While the workflow waits, the execution graph in the [Integration Control Plane](../icp/managing-workflows.md) marks the halt point as a `DATA` node named after the event, with status `WAITING` — so anyone can see exactly what the process is blocked on rather than guessing that it is stuck. + + + +The same graph is available over the [Management API](../reference/management-api.md). + +## Next steps + +- [Send a data event](send-data-event.md) — the delivery half: fill the event and resume the run. +- [Await human task](human-task-workflow.md) — pause for a person's decision instead of their data. +- [Durable timers](durable-timers.md) — waiting on the clock instead of an event. +- [Activities](activities.md) — the recorded steps that process the data once it arrives. diff --git a/en/docs/workflows/develop/durable-agentic-workflow.md b/en/docs/workflows/develop/durable-agentic-workflow.md new file mode 100644 index 0000000000..0f94505589 --- /dev/null +++ b/en/docs/workflows/develop/durable-agentic-workflow.md @@ -0,0 +1,145 @@ +--- +sidebar_position: 8 +title: "Durable Agentic Workflows" +description: Build AI agents on the durable workflow runtime in WSO2 Integrator — with durable activities, data events, 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, data events, and human tasks. An AI model plans the path at runtime, and because the agent *is* a durable workflow, every reasoning step, activity call, and wait survives crashes and restarts. + + + +## Add durable agent artifact + +1. In the design view, click **+ Add Artifact**. +2. On the **Artifacts** page, under **Durable Workflow**, click **Durable Agentic Workflow**. + + ![The Artifacts page with the Durable Agentic Workflow card under the Durable Workflow section](/img/workflows/develop/durable-agentic-workflow/add-agent-artifact.png) + + **Durable Workflow** beside it produces the same kind of artifact with the steps wired by hand instead of chosen by a model. See [Create a workflow](create-workflow.md). + +3. Set **Name** to the name the agent is referenced by, then click **Create Agent**. The agent opens on its own model and appears under **Workflows** in the sidebar. + +## Configure the agent + +Click the agent node to open the **Configure Agent** form, which holds the agent's role, its instructions, and its reasoning limit. + +| Field | Required | Description | +|------------------------|----------|------------------------------------------------------------------------------------------------------------------------------------------------| +| **Role** | Yes | The agent's primary function, for example `Expense claim assistant`. It appears under the agent's name on the node. | +| **Instructions** | Yes | What the agent should do, in natural language. This is what the model plans from, so name the capabilities it should use and the outcome you expect. | +| **Query** | No | The initial user query. Left empty, the agent waits for the first chat event instead of starting on its own. | +| **Maximum Iterations** | No | The most reasoning iterations the model may take in one turn. | + +**Role** and **Instructions** each take either a prompt or an expression. Keep the field on **Prompt** to write the text directly, and switch it to **Expression** to build the value in code, for example from a configurable. + +![The Configure Agent form with Role set to Expense claim assistant and the claim handling instructions filled in](/img/workflows/develop/durable-agentic-workflow/configure-agent.png) + +## Capabilities + +### Activities + +Durable agent can also invoke activities. Each call runs durably with the same guarantees and the same **retry policies** as any workflow activity, **Human Review** retries included (see [Error handling and review activities](review-activity-and-error-handling.md)). In addition to that regular activity call configuration, you can enable **Requires Approval** on sensitive activities, so a person approves the execution before the agent runs the activity. Those approval requests are listed in the **Review Activities** tab of the [Control Plane](../icp/review-activities.md). The agent proposes; your policies decide what needs a human. + +To register an activity with the agent: + +1. Click **+** on the activity anchor at the bottom right of the agent node. +2. Under **Current Integration**, click the activity you want the agent to have, `payClaim` here. Its register form opens. +3. Choose the **Retry Policy** the engine applies when the agent's call to this activity fails. The policies are the same three a hand-wired activity call takes. See [The three retry policies](review-activity-and-error-handling.md#the-three-retry-policies). +4. Expand **Advanced Configurations** to describe the activity to the model and to gate it: + + | Field | Required | Description | + |---|---|---| + | **Activity Name** | No | The name advertised to the model. Defaults to the function name. | + | **Activity Description** | No | Tells the model what the activity does and when to use it. | + | **Requires Approval** | No | Gates the activity: the agent suspends durably before every call and raises a review activity carrying the arguments it proposes. | + | **Reviewer Roles** | Only with **Requires Approval** | The roles permitted to decide that approval, for example `"Finance"` or `["finance", "manager"]`. | + +5. Click **Save**. + +![Registering payClaim as a durable agent activity, gating it with Requires Approval and setting Reviewer Roles to Finance](/img/workflows/develop/durable-agentic-workflow/register-activity.gif) + +The activity joins the agent node as a capability, drawn with a shield badge while it is gated. Registering it is what makes it available to the model, so an activity the agent never needs is best left off the list. + +:::tip No arguments to wire +Unlike a [Call Activity](activities.md#call-an-activity-from-a-workflow) step in a hand-wired workflow, the register form asks for no activity arguments. The agent maps them at runtime from what it has gathered, which is why **Activity Description** and the parameter names are worth writing clearly. +::: + +### Events + +Durable agents can receive events from external senders. Each event has a name, a request type, and an optional response type. The request type is the payload the sender sends to the agent, and the response type is what the agent returns to the sender. If the response type is left empty, the event is one-way. + +```ballerina +events: [ + {name: "billSubmitted", request: BillSubmission, response: string} +] +``` + +To register a data event: + +1. Click **+** on the event anchor at the bottom left of the agent node. The **Register Data Event** form opens. +2. Fill in the form: + + | Field | Required | Description | + |---|---|---| + | **Event Name** | Yes | The channel name senders use. A channel named `chat` is the one that drives the conversation itself. | + | **Request Type** | Yes | The type of the payload sent to the agent on this channel. Pick a type from the project, a primitive such as `string`, or create one from the list. | + | **Response Type** | No | The type of the agent's reply on this channel. Declaring one makes the channel request-response, read back with `getDataResult` or `waitForDataResult`. Left empty, the channel is one-way. | + | **Cardinality** | No | How the channel consumes its events. `MULTI_EVENT`, the default, re-arms after every turn, so events can arrive repeatedly and from multiple senders. `SINGLE_EVENT` is consumed exactly once per run. | + +3. Click **Save**. + +![Registering the chat data event with a string request type, a string response type, and MULTI_EVENT cardinality](/img/workflows/develop/durable-agentic-workflow/register-data-event.gif) + +### 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. + +## 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 hand-wired [durable workflow](../getting-started/build-an-order-processing-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. diff --git a/en/docs/workflows/develop/durable-timers.md b/en/docs/workflows/develop/durable-timers.md new file mode 100644 index 0000000000..938379f7b1 --- /dev/null +++ b/en/docs/workflows/develop/durable-timers.md @@ -0,0 +1,66 @@ +--- +sidebar_position: 9 +title: "Durable Timers" +description: Pause a WSO2 Integrator durable workflow for hours, days, or months with a durable sleep that survives restarts and holds no threads or memory while it waits. +keywords: [wso2 integrator, durable workflow, timer, sleep, delay, wait, long running, crash recovery] +--- + +import ThemedImage from '@theme/ThemedImage'; +import useBaseUrl from '@docusaurus/useBaseUrl'; + +# Durable Timers + +Long-running processes spend most of their life waiting: a cooling-off period before a refund, a settlement delay before a payout, a reminder three days after a request. A **durable timer** is how a workflow waits. The instance suspends, the runtime remembers when to wake it, and nothing is held open in the meantime. + +## Sleep in a workflow + +Add a **Sleep** step to the flow, or call it on the workflow context in code: + +```ballerina +check ctx->sleep({hours: 24}); +``` + +The duration is a record, so express the wait in the unit that reads best for the process — `{hours: 24}` for a settlement delay, `{days: 3}` for a reminder window. + + + +## What makes it durable + +A durable sleep is not a blocked thread. When the workflow reaches the timer: + +- The instance **suspends**. It consumes no thread and no memory while waiting, so thousands of waiting instances cost nothing to keep around. +- The deadline is **recorded**. Restart the integration, redeploy it, or lose the process to a crash, and the timer still fires at its original time. +- On replay, an **already-elapsed timer does not wait again.** Like a completed activity, it is read back from the record, so a restart never restarts the clock. + +That last point is the reason to reach for the workflow's own sleep rather than a language-level one: + +:::warning +Never use `runtime:sleep()` inside a workflow. It blocks a thread, it is invisible to the runtime, and the wait is lost on restart — the workflow resumes with the delay silently skipped or repeated. Always use the workflow context's durable sleep. +::: + +## Timers versus task timeouts + +A timer and a timeout look similar but answer different questions: + +| | What it does | +| --- | --- | +| **Durable timer** (`ctx->sleep`) | Waits a fixed duration, then continues. Nothing can cut it short. | +| **Human task timeout** (`timeout = {days: 3}`) | Bounds a wait for a *person or an event*. It ends early when the task is answered, and fails with a timeout error if nobody answers in time. | + +Use a timer for a delay you always want, and a timeout when you are waiting on something that may or may not arrive. See [Await human task](human-task-workflow.md) for the timeout form and how to handle its error. + +## Watching timers + +A pending timer appears as a `TIMER` node in the instance's execution graph in the [Integration Control Plane](../icp/managing-workflows.md), so a workflow that looks stalled can be identified as simply waiting, and you can see what it is waiting for and until when. The same graph is available over the [Management API](../reference/management-api.md). + +## Next steps + +- [Activities](activities.md) — the recorded steps a timer sits between. +- [Await human task](human-task-workflow.md) — waiting on people and external events instead of the clock. +- [Build an order processing workflow](../getting-started/build-an-order-processing-workflow.md) — a timer in a complete flow. diff --git a/en/docs/workflows/develop/human-task-workflow.md b/en/docs/workflows/develop/human-task-workflow.md new file mode 100644 index 0000000000..d67fdf2a1e --- /dev/null +++ b/en/docs/workflows/develop/human-task-workflow.md @@ -0,0 +1,99 @@ +--- +sidebar_position: 6 +title: "Await Human Task" +description: Pause a WSO2 Integrator durable workflow until a person with the right role decides, with the decision returned as a typed value. +keywords: [wso2 integrator, durable workflow, human task, await human task, approval, human in the loop, task inbox, icp] +--- + +import ThemedImage from '@theme/ThemedImage'; +import useBaseUrl from '@docusaurus/useBaseUrl'; + +# Await Human Task + +Real processes wait on people: a manager approves an expense, a reviewer checks documents, an HR lead assigns a new joiner to a team. An **Await Human Task** step stops the workflow at exactly that point, hands a task to a role, and resumes the moment someone submits a decision, whether that takes a minute or a month. While it waits it holds no threads, no memory, and no connections. + + + +## Add the step + +The steps below follow one example: an onboarding workflow where an HR lead assigns a new joiner to a team. The task lands in the [Control Plane](../icp/human-tasks.md) inbox of the matching role, as a form rendered with the information needed to answer it. The workflow starts with the employee's details, so its [input type](create-workflow.md) is an `EmployeeDetails` record: + +| Field | Type | +|---|---| +| `id` | `string` | +| `name` | `string` | + +1. On the workflow diagram, click **+** where the workflow should wait. +2. In the node panel, under **Workflow** > **Steps**, click **Await Human Task**. +3. Fill in the form: + + | Field | Required | Description | + |---|---|---| + | **Task Name** | Yes | Identifies the task type, for example `Assign Employee to Team`. | + | **User Roles** | Yes | One or more roles permitted to complete this task, for example `HRManager`. Only users holding a matching role see the task. | + | **Payload** | No | Under **Advanced Configurations**. See [Show the decider what they need](#show-the-decider-what-they-need). | + | **Title** | No | Under **Advanced Configurations**. Short summary shown in the inbox, for example `Assign Employee to Team`. | + | **Description** | No | Under **Advanced Configurations**. Additional context shown alongside the form, for example `Assign the new employee to the appropriate team and select their team lead.` | + | **Timeout** | No | Under **Advanced Configurations**. Maximum time to wait. Omit it to wait indefinitely. | + | **Result** | Yes | Name of the variable that receives the decision. | + | **Completion Type** | Yes | The type of the data returned when the task is completed. See [Type the decision](#type-the-decision). | + +4. Click **Save**. + +![Adding an Await Human Task step, with its payload, title, description, and a new completion type](/img/workflows/develop/human-task-workflow/await-human-task.gif) + +The workflow suspends at this step, and the task appears in the [Integration Control Plane](../icp/human-tasks.md) inbox for every user holding one of the roles you named. + +## Show the decider what they need + +**Payload** is the context the person answering needs, rendered read-only next to the form. It answers "what am I deciding about?", which the decision form itself cannot. + +In the onboarding example that is the employee, so **Payload** is set to the workflow's input, the `EmployeeDetails` record. The new joiner's ID and name then appear alongside the form when the task is completed in the Control Plane. + +:::tip Payload is not the decision +Nothing in the payload comes back to the workflow. It is there to inform the person, while the value they submit is governed by **Completion Type** below. +::: + +## Type the decision + +**Completion Type** is what the workflow gets back, and it is also what the Control Plane renders the form from. A plain approve or reject can be a `boolean`, while anything richer wants a record. + +For the onboarding task, the HR lead answers with a team and a team lead: + +| Field | Type | +|---|---| +| `team` | `string` | +| `lead` | `string` | + +That record produces a two-field form in the inbox, and the workflow resumes with the values as an ordinary typed value. Records created this way are ordinary types, editable later from **Types** in the sidebar. See [Types](../../develop/integration-artifacts/supporting/types.md) for the type editor and the kinds it supports. + +:::tip Design for the form +Whatever you put in the completion type is exactly what the decider fills in. Keep it small: an action, a comment, maybe a corrected value. Use an enum for a fixed set of choices and the Control Plane renders it as a dropdown. +::: + +## Bound the wait + +**Timeout** limits how long the task can sit unanswered. When it expires the wait returns an error, which the workflow can handle: escalate to another role, send a reminder, or end the process. Omit it and the task waits indefinitely. + +## Decision or data? + +Use a human task when a person is making a **decision** the Control Plane should render as a form. When the workflow needs **content** instead, such as an uploaded document or a partner system's confirmation, [await a data event](data-events.md), which suspends the same way but is filled by whoever holds the workflow ID. + +## How these tasks are completed + +A workflow never exposes its own endpoint for finishing a task. Completion happens outside the workflow, in one of two places: + +- **[Complete human tasks](../icp/human-tasks.md)** in the Integration Control Plane, where the task appears in the inbox of everyone holding a matching role, rendered as a form built from the completion type with the payload shown beside it. This is the route for the people actually deciding. +- **[Management API](../reference/management-api.md)**, whose `POST /human-tasks/{taskId}/complete` accepts the same decision as JSON, for building your own portal or automating a decision. + +## Next steps + +- [Complete human tasks](../icp/human-tasks.md) — decide a waiting task from the Control Plane inbox. +- [Await data events](data-events.md) — wait for data delivered by a system or a person instead of a decision. +- [Error handling and review activities](review-activity-and-error-handling.md) — approvals attached to activities rather than free-standing tasks. diff --git a/en/docs/workflows/develop/prebuilt-activities/call-rest-api.md b/en/docs/workflows/develop/prebuilt-activities/call-rest-api.md new file mode 100644 index 0000000000..d4278fca64 --- /dev/null +++ b/en/docs/workflows/develop/prebuilt-activities/call-rest-api.md @@ -0,0 +1,95 @@ +--- +sidebar_position: 2 +title: "Call a REST API" +sidebar_label: "Call REST API" +description: Invoke an HTTP endpoint from a WSO2 Integrator durable workflow as a recorded activity, with the response bound to the type you choose. +keywords: [wso2 integrator, durable workflow, prebuilt activity, call rest api, http client, databinding, workflow activity] +--- + +# Call a REST API + +**Call REST API** invokes an HTTP endpoint through an `http:Client` connection and binds the response to the type you ask for. The call is recorded like any other [activity](../activities.md), so a workflow that restarts mid-flight reads the recorded response back instead of calling the API again. + +Use it whenever a workflow needs to talk to an HTTP service and you would otherwise write an activity function that does nothing but forward the call. + +:::info Prerequisites + +- An `http:Client` connection in your integration, created under **Connections** in the sidebar +::: + +## Fields + +| Field | Required | Description | +|---|---|---| +| **Connection** | Yes | The `http:Client` to call through. Only HTTP connections are offered. | +| **Method** | Yes | `GET`, `POST`, `PUT`, `DELETE`, or `PATCH`. | +| **Path** | No | Resource path appended to the connection's base URL, for example `/users/1`. | +| **Message** | For `POST`, `PUT`, and `PATCH` | The request body. Accepts a record, `json`, `xml`, a string, or bytes. | +| **Headers** | No | Request headers. Under the advanced fields. | +| **Result** | Yes | The variable that receives the response. | +| **Databinding Type** | Yes | The type to bind the response payload to. | + +**Retry Policy** and **Check Error** work the same as for every prebuilt activity. See [Prebuilt activities](index.md#fields-shared-by-all-three). + +## Read a resource + +A `GET` with a **Databinding Type** of `OrderStatus` hands the workflow a typed value rather than raw JSON: + +```ballerina +import ballerina/http; +import ballerina/workflow; +import ballerina/workflow.activity; + +final http:Client ordersApi = check new ("https://api.example.com"); + +type OrderStatus record {| + string id; + string status; +|}; + +@workflow:Workflow +function trackOrderWorkflow(workflow:Context ctx, string orderId) returns error? { + OrderStatus status = check ctx->callActivity(activity:callRestAPI, { + connection: ordersApi, + method: "GET", + path: string `/orders/${orderId}` + }); +} +``` + +The binding is the client's own: whatever `http:Client` can bind a response to, this activity can return. Use `json` when the shape varies and you want to inspect it in the workflow. + +## Send a body + +For `POST`, `PUT`, and `PATCH`, fill in **Message**. Here the request body is a record and the response binds to another: + +```ballerina +type Shipment record {| + string orderId; + string address; +|}; + +type ShipmentCreated record {| + string trackingId; +|}; + +@workflow:Workflow +function shipOrderWorkflow(workflow:Context ctx, Shipment shipment) returns error? { + ShipmentCreated created = check ctx->callActivity(activity:callRestAPI, { + connection: ordersApi, + method: "POST", + path: "/shipments", + message: shipment + }); +} +``` + +:::warning Retries and writes +A `POST` that failed after the server processed it will be sent again when **Auto Retry** is on, which can create a duplicate. Pass an idempotency key if the API supports one, choose **Human Review** so a person decides, or leave retries off for calls that cannot be repeated safely. +::: + +## Next steps + +- [Call SOAP API](call-soap-api.md) — the same idea for SOAP endpoints. +- [Error handling and review activities](../review-activity-and-error-handling.md) — what happens when the call keeps failing. +- [Activities](../activities.md) — write your own activity when the call needs more than a forward. diff --git a/en/docs/workflows/develop/prebuilt-activities/call-soap-api.md b/en/docs/workflows/develop/prebuilt-activities/call-soap-api.md new file mode 100644 index 0000000000..80656b3df2 --- /dev/null +++ b/en/docs/workflows/develop/prebuilt-activities/call-soap-api.md @@ -0,0 +1,72 @@ +--- +sidebar_position: 3 +title: "Call a SOAP API" +sidebar_label: "Call SOAP API" +description: Send a SOAP envelope from a WSO2 Integrator durable workflow as a recorded activity, over a SOAP 1.1 or SOAP 1.2 connection. +keywords: [wso2 integrator, durable workflow, prebuilt activity, call soap api, soap11, soap12, soapaction, workflow activity] +--- + +# Call a SOAP API + +**Call SOAP API** sends a SOAP envelope through a `soap11:Client` or `soap12:Client` connection and returns the response envelope as `xml`. Like every [activity](../activities.md), the call is recorded, so a restart replays the recorded response instead of calling the service again. + +Use it to reach the SOAP services that legacy systems still expose, without writing an activity function to wrap the client. + +:::info Prerequisites + +- A `soap11:Client` or `soap12:Client` connection in your integration, created under **Connections** in the sidebar +::: + +## Fields + +| Field | Required | Description | +|---|---|---| +| **Connection** | Yes | The SOAP client to call through. Both SOAP 1.1 and SOAP 1.2 connections are offered. | +| **Body** | Yes | The SOAP envelope, as `xml`. | +| **Action** | For SOAP 1.1 | The SOAPAction header. Required for SOAP 1.1 endpoints, optional for SOAP 1.2. | +| **Headers** | No | Additional HTTP headers. Under the advanced fields. | +| **Path** | No | Path appended to the connection's base URL. Under the advanced fields. | +| **Result** | Yes | The variable that receives the response envelope. | + +**Retry Policy** and **Check Error** work the same as for every prebuilt activity. See [Prebuilt activities](index.md#fields-shared-by-all-three). + +## Send an envelope + +Build the envelope as `xml` and pass it as **Body**. The response comes back as `xml` for the workflow to read: + +```ballerina +import ballerina/soap.soap11; +import ballerina/workflow; +import ballerina/workflow.activity; + +final soap11:Client billing = check new ("https://billing.example.com/svc?WSDL"); + +@workflow:Workflow +function invoiceWorkflow(workflow:Context ctx, string invoiceId) returns error? { + xml request = xml `${invoiceId}`; + xml response = check ctx->callActivity(activity:callSoapAPI, { + connection: billing, + body: request, + action: "urn:GetInvoice" + }); +} +``` + +## SOAP 1.1 and SOAP 1.2 + +The activity accepts either client, and the only difference is **Action**: + +| Version | Action | +|---|---| +| SOAP 1.1 | Required. The call fails with an error when it is missing. | +| SOAP 1.2 | Optional, because the action can travel in the content type instead. | + +:::warning Multipart responses +The activity returns `xml`. An endpoint that answers with a multipart message fails with an error instead. Write your own [activity](../activities.md) around the SOAP client when you need to read `mime:Entity[]` payloads. +::: + +## Next steps + +- [Call REST API](call-rest-api.md) — the same idea for HTTP endpoints. +- [Error handling and review activities](../review-activity-and-error-handling.md) — what happens when the call keeps failing. +- [Activities](../activities.md) — write your own activity when the response needs shaping first. diff --git a/en/docs/workflows/develop/prebuilt-activities/index.md b/en/docs/workflows/develop/prebuilt-activities/index.md new file mode 100644 index 0000000000..f6714fa7b7 --- /dev/null +++ b/en/docs/workflows/develop/prebuilt-activities/index.md @@ -0,0 +1,60 @@ +--- +sidebar_position: 10 +title: "Prebuilt Activities" +description: Ready-made durable wrappers for the calls every integration makes, so REST, SOAP, and email run as recorded workflow activities without writing one. +keywords: [wso2 integrator, durable workflow, prebuilt activity, builtin activity, rest api, soap, smtp email, connection] +--- + +# Prebuilt Activities + +Every outbound call from a workflow has to run inside an [activity](../activities.md) so the runtime records its result. For your own logic that means writing an activity function. For the calls almost every integration makes, the runtime ships the wrapper for you. + +**Prebuilt activities** are ready-made, durable wrappers around the most common connector calls. You pick an existing connection and fill in the call, and the runtime records the result exactly as it would for an activity you wrote yourself. Nothing to wrap, and nothing to keep in sync when the call changes. + +Three are available today: + +| Prebuilt activity | Calls through | Returns | +|---|---|---| +| **[Call REST API](call-rest-api.md)** | An `http:Client` connection | The response payload, bound to the type you choose | +| **[Call SOAP API](call-soap-api.md)** | A `soap11:Client` or `soap12:Client` connection | The response envelope as `xml` | +| **[Send Email (SMTP)](send-email.md)** | An `email:SmtpClient` connection | Nothing | + +## Add one to a workflow + +1. On the workflow diagram, click **+**. +2. In the node panel, under **Workflow** > **Steps**, click **Call Activity**. +3. In the **Activities** panel, expand **Prebuilt Activities** and click the one you need. +4. Choose the **Connection** to call through, fill in the call's fields, and click **Save**. + +The call appears on the diagram as an ordinary activity node, and it behaves like one: recorded on success, retryable on failure, and visible as an `ACTIVITY` node in the [Integration Control Plane](../../icp/executions.md) execution graph. + +:::info Connections come first +A prebuilt activity calls through a connection that already exists in your integration. Create it under **Connections** in the sidebar before you add the step. In code it is a module-level `final` client variable, which is what lets the runtime hand it to the activity worker. +::: + +## Fields shared by all three + +Each prebuilt activity has its own fields, covered on its page. These two are common to all of them: + +| Field | Description | +|---|---| +| **Retry Policy** | What the engine does when the call fails: **No Automatic Retry**, **Auto Retry** for backoff retries, or **Human Review** to hand the failure to a person. See [Error handling and review activities](../review-activity-and-error-handling.md). | +| **Check Error** | Propagates a failure to the workflow with `check`. Clear it to handle the error yourself. | + +:::tip Idempotent side effects +A completed prebuilt activity never runs twice, but a failed attempt runs again once **Auto Retry** is on. That matters most for calls that change something: a retried `POST` or a retried email can duplicate. Send an idempotency key with the request where the API supports one, or keep retries off for calls that cannot be repeated safely. +::: + +## When to write your own activity instead + +Reach for a custom [activity](../activities.md) when the call is not one of the three above, when you want several calls recorded as a single step, or when the response needs shaping before the workflow sees it. Two other entries in the **Activities** panel cover the middle ground: + +- **Create Activity from a Connection** generates an activity function around an action on a connector you already have, which is the route for connectors other than HTTP, SOAP, and SMTP. +- **+ Create Activity** writes an empty activity function for you to design. + +## Next steps + +- [Call REST API](call-rest-api.md) — invoke an HTTP endpoint and bind the response to a type. +- [Call SOAP API](call-soap-api.md) — send a SOAP envelope and read the response. +- [Send Email (SMTP)](send-email.md) — send a message through an SMTP connection. +- [Activities](../activities.md) — how activities are recorded, retried, and shown in the execution graph. diff --git a/en/docs/workflows/develop/prebuilt-activities/send-email.md b/en/docs/workflows/develop/prebuilt-activities/send-email.md new file mode 100644 index 0000000000..c17f006171 --- /dev/null +++ b/en/docs/workflows/develop/prebuilt-activities/send-email.md @@ -0,0 +1,75 @@ +--- +sidebar_position: 4 +title: "Send an Email" +sidebar_label: "Send Email (SMTP)" +description: Send an email from a WSO2 Integrator durable workflow as a recorded activity, through an SMTP connection. +keywords: [wso2 integrator, durable workflow, prebuilt activity, send email, smtp, notification, workflow activity] +--- + +# Send an Email + +**Send Email (SMTP)** sends a message through an `email:SmtpClient` connection. It is the notification step most long-running processes need: the order shipped, the claim was approved, the document is ready. + +The send is recorded like any other [activity](../activities.md), which is what stops a restart from mailing the same person twice. + +:::info Prerequisites + +- An `email:SmtpClient` connection in your integration, created under **Connections** in the sidebar +::: + +## Fields + +The activity returns nothing, so there is no result variable to name. + +| Field | Required | Description | +|---|---|---| +| **Connection** | Yes | The `email:SmtpClient` to send through. | +| **To** | Yes | Recipient address, or a list of addresses. | +| **Subject** | Yes | Subject line. | +| **Body** | Yes | Plain-text body. | +| **From** | Yes | Sender address. | +| **CC**, **BCC** | No | Further recipients. Under the advanced fields. | +| **Reply To** | No | Where replies should go. | +| **Sender** | No | Envelope sender, when it differs from **From**. | +| **HTML Body** | No | An HTML body sent alongside the plain-text one. | +| **Content Type** | No | MIME content type override, for example `text/plain`. | +| **Email Headers** | No | Additional mail headers. | + +**Retry Policy** and **Check Error** work the same as for every prebuilt activity. See [Prebuilt activities](index.md#fields-shared-by-all-three). + +## Send a notification + +```ballerina +import ballerina/email; +import ballerina/workflow; +import ballerina/workflow.activity; + +final email:SmtpClient smtp = check new ("smtp.example.com", "username", "password"); + +@workflow:Workflow +function notifyWorkflow(workflow:Context ctx, string recipient) returns error? { + check ctx->callActivity(activity:sendEmail, { + connection: smtp, + to: recipient, + subject: "Order shipped", + 'from: "no-reply@example.com", + body: "Your order is on the way." + }); +} +``` + +Set **HTML Body** as well when you want a formatted message. The plain-text **Body** is still sent, so clients that cannot render HTML have something to show. + +:::warning A retried email is a second email +Mail cannot be recalled, and the send is not idempotent: if the message left the server but the activity reported a failure, **Auto Retry** sends it again. For customer-facing mail, prefer **No Automatic Retry**, or **Human Review** so a person decides whether to resend. See [Error handling and review activities](../review-activity-and-error-handling.md). +::: + +:::tip Notification or decision? +Use this activity to tell someone what happened. When the workflow needs an answer back, pause it with a [human task](../human-task-workflow.md) instead and let the person decide from their inbox in the Control Plane. +::: + +## Next steps + +- [Await human task](../human-task-workflow.md) — wait for a person's decision rather than notifying them. +- [Call REST API](call-rest-api.md) — reach a notification service over HTTP instead of SMTP. +- [Activities](../activities.md) — write your own activity when a template or attachment is involved. diff --git a/en/docs/workflows/develop/review-activity-and-error-handling.md b/en/docs/workflows/develop/review-activity-and-error-handling.md new file mode 100644 index 0000000000..f82731735d --- /dev/null +++ b/en/docs/workflows/develop/review-activity-and-error-handling.md @@ -0,0 +1,121 @@ +--- +sidebar_position: 7 +title: "Error Handling & Review Activities" +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, replay, crash safe, state management] +--- + +import ThemedImage from '@theme/ThemedImage'; +import useBaseUrl from '@docusaurus/useBaseUrl'; + +# Error Handling & Review Activities + +Failures and risky steps are where durable workflows earn their keep. Instead of scattering try/catch blocks and retry loops through your flow, 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**, which says what the workflow should do after a failed attempt to execute that activity. + +| Policy | What happens on failure | +|----------------------------------|----------------------------------------------------------------------------------------------------------------------------------| +| **No Automatic Retry** (default) | Error will be returned from the workflow if the first activity execution attempt fails. | +| **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. | + +![The activity call form with the Retry Policy dropdown open on No Automatic Retry, Auto Retry, and Human Review](/img/workflows/develop/review-activity/retry-policy.png) + +## Auto Retry — for transient failures + +Choosing **Auto Retry** adds the backoff fields to the form. Every one of them is optional, and a field left empty falls back to its default. + +| Field | Required | Description | +|---------------------|----------|------------------------------------------------------------------------------------------------------| +| **Max Retries** | No | Maximum retry attempts. Defaults to `3`. | +| **Retry Delay** | No | Initial delay in seconds before the first retry. Defaults to `1.0`. | +| **Retry Backoff** | No | Multiplier applied to the delay after each retry. Defaults to `2.0`. | +| **Max Retry Delay** | No | Cap on the delay between retries, in seconds. Left empty, the delay keeps growing by the multiplier. | + +![The activity call form with Auto Retry chosen, showing Max Retries, Retry Delay, Retry Backoff, and Max Retry Delay](/img/workflows/develop/review-activity/auto-retry.png) + +## Human Review — when a person should fix it + +Choosing **Human Review** hands a failure to a person instead of to the engine. The workflow does not fail along with the activity and the engine does not retry on its own: the run parks at that step, and a review task is raised carrying the failing input and the error it produced. The task takes its name from the activity being called, so there is nothing to name in the form. + +The task is listed in the **Review Activities** tab of the [Control Plane](../icp/review-activities.md) for the roles you name below, matched by exact role name. Until one of them decides it, the run waits there durably and holds no resources, the same as any other durable wait. The decision is what resumes it. + +| Field | Required | Description | +|--------------------|----------|------------------------------------------------------------------------------------------------------------------------------------------------------| +| **Reviewer Roles** | Yes | The role permitted to decide the review, for example `"Finance"`, or a list such as `["finance", "manager"]`. Leave it empty to let any role decide. | + +![The activity call form with Human Review chosen, showing the Reviewer Roles field set to Finance](/img/workflows/develop/review-activity/human-review.png) + +## Approval gates — review *before* the step runs + +Some steps should never run without sign-off, even when nothing has failed. A gate like that is set where a [durable agent](durable-agentic-workflow.md#activities) registers the activity as one of its capabilities: click the activity on the agent's diagram, expand **Advanced Configurations**, and select **Requires Approval**. + +| Field | Required | Description | +|-----------------------|----------|----------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **Requires Approval** | No | Gates the activity. Cleared by default, so an activity runs unattended unless you say otherwise. | +| **Reviewer Roles** | No | The role permitted to decide this activity's approval reviews, for example `"Finance"`, or a list such as `["finance", "manager"]`. Left empty, the agent's own approval roles apply. | + +![The payClaim activity registration form with Requires Approval selected and Reviewer Roles set to Finance](/img/workflows/develop/review-activity/requires-approval.png) + +With the gate on, the agent suspends durably before every call to that activity and raises a review activity showing the arguments it *proposes*. Gates are decided in the **Review Activities** tab of the Control Plane. See [Review activities](../icp/review-activities.md). + +## Error handling in the workflow logic + +Retry policies handle the step; your workflow logic handles the outcome. Clearing **Check Error** on an activity call hands the failure back to the workflow as a value instead of propagating it out of the workflow, and from there you branch on it like on any other value. + + + +To handle an activity failure inside the workflow, as in the diagram above: + +1. On the workflow diagram, click the activity call whose failure you want to handle, `sendConfirmationEmail` here. +2. Expand **Advanced Configurations** and clear **Check Error**. A **Result** field appears. +3. Name the **Result** variable, for example `emailResult`, and click **Save**. The variable holds the error the activity failed with, or nil when it succeeded, and the node now shows the variable under the activity name. + + ![The activity call's Advanced Configurations with Check Error cleared and the Result variable named emailResult](/img/workflows/develop/review-activity/check-error-result.png) + +4. Click **+** below the activity and, in the node panel under **Control**, click **If**. +5. In **Condition**, write the error check, `emailResult is error`. Selecting the variable under **Variables** in the expression helper saves typing it. Click **Save**. +6. Click **+** on the branch taken when the condition holds and add the steps that deal with the failure, here a **Call Activity** step calling `notifyFailedEmail`. Click **Save**. + +![Clearing Check Error on an activity call, naming its result variable, and branching on emailResult is error to call notifyFailedEmail](/img/workflows/develop/review-activity/handle-error-in-logic.gif) + +Both branches rejoin the flow after the **If**, so `startShipment` runs whether or not the email failed. Leave the else branch empty when there is nothing to do on success. + +## 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 (**Requires Approval**) | +| Business-level failure with a fallback path | No Automatic Retry + workflow logic | + +## Crash recovery + +Everything above rests on one guarantee: the engine writes down the outcome of every step as that step completes. When a run resumes after a crash, a restart, or a redeploy, the workflow function replays, and each step that already finished is read back from the record instead of being run again. + +| Step | What is read back on replay | +|-------------------|-------------------------------------------------------------------------------------------------------------------| +| An activity call | The value the activity returned, so the card is not charged and the email is not sent a second time. | +| A data event wait | The value that was delivered, so the run does not wait for it again. | +| A human task wait | The decision the person submitted. | +| A durable timer | The original deadline, so an elapsed wait does not restart its clock. | +| Current time | The instant the run first reached that step. The workflow always works with the time at which it first got there. | + +This is why a durable workflow needs no state management of its own. You write no checkpoint rows, no status columns, and no resume logic, and you do not reload progress when a run picks back up: the variables in the workflow body are rebuilt from the record, so the code after a wait sees exactly what the code before it left behind. The recorded events for a run are listed on the **History** tab in the [Control Plane](../icp/executions.md#history). + +What this asks of you in return is a deterministic workflow body, since that is the part which runs again on every replay. See [Activities](activities.md#why-the-split-matters). + +## Next steps + +- [Await human task](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. diff --git a/en/docs/workflows/develop/send-data-event.md b/en/docs/workflows/develop/send-data-event.md new file mode 100644 index 0000000000..32943abb7a --- /dev/null +++ b/en/docs/workflows/develop/send-data-event.md @@ -0,0 +1,52 @@ +--- +sidebar_position: 5 +title: "Send a Data Event" +description: Deliver a value into a running WSO2 Integrator durable workflow with the Send Data Event step, so a run waiting on a data event resumes. +keywords: [wso2 integrator, durable workflow, send data event, senddata, resume workflow, workflow id, callback] +--- + +import ThemedImage from '@theme/ThemedImage'; +import useBaseUrl from '@docusaurus/useBaseUrl'; + +# Send a Data Event + +A run parked on an [await data event](data-events.md) stays there until something delivers the value it is waiting for or timeout is reached. That delivery is a **Send Data Event** step, and it usually lives in the entry point a partner system or a person calls back on: the payment gateway's webhook, the resource where an employee uploads the missing document, a scheduled automation that posts the day's file. + + + +The delivery does not create a run. It resumes one, so it needs the workflow ID that [starting the run](start-workflow.md) returned. + +## Send one from an integration + +1. In the trigger artifact flow design, click **+**. +2. In the node panel, under **Workflow**, click **Send Data Event**. +3. Fill in the form: + + | Field | Required | Description | + |---|---|---| + | **Workflow Name** | Yes | The workflow to deliver into. The dropdown lists every workflow in the project. | + | **Target Workflow Id** | Yes | Which run to resume, so this is the ID that [starting the run](start-workflow.md) returned. | + | **Data Name** | Yes | The event to fill. The dropdown lists the data events declared by the workflow chosen above, so the two cannot drift apart. | + | **Data** | Yes | The value to deliver. It has to match the type the event declares. | + +4. Click **Save**. + +![Adding a Send Data Event step and choosing the workflow and its data event](/img/workflows/develop/send-data-event/add-send-data-event.gif) + +## Anyone with the ID can deliver + +There is nothing special about the integration that started the run. Any integration that can reach the runtime and holds the workflow ID can fill the event, which is what makes a callback from a third party work: hand out the ID when the run starts, and the delivery can arrive from anywhere, minutes or weeks later. + +That also means the ID is worth guarding. A run whose ID was lost keeps waiting, and no code can resume it. + +## Next steps + +- [Await data events](data-events.md) — the waiting half: declare the event and pause the workflow on it. +- [Start a workflow](start-workflow.md) — where the workflow ID comes from. +- [Await human task](human-task-workflow.md) — when a person is deciding rather than submitting content. diff --git a/en/docs/workflows/develop/start-workflow.md b/en/docs/workflows/develop/start-workflow.md new file mode 100644 index 0000000000..54a8cce9bd --- /dev/null +++ b/en/docs/workflows/develop/start-workflow.md @@ -0,0 +1,64 @@ +--- +sidebar_position: 2 +title: "Start a Workflow" +description: Launch a durable workflow run from a service or an automation with the Run Workflow step, keep its ID, and read the result when it finishes. +keywords: [wso2 integrator, durable workflow, start workflow, run workflow, workflow id, workflow result, entry point] +--- + +import ThemedImage from '@theme/ThemedImage'; +import useBaseUrl from '@docusaurus/useBaseUrl'; + +# Start a Workflow + +A workflow does not start itself. Something has to launch it. It can be any type of trigger: a service resource that receives a request, an automation that runs on a schedule, a file handler on file receive or an operator starting one by hand. Whatever the trigger, starting a run returns a **workflow ID**, and that ID is the handle to the run from then on. + + + +## Start one from an integration + +1. In the trigger artifact flow design, click **+**. +2. In the node panel, under **Workflow**, click **Run Workflow**. + + ![The node panel with Run Workflow and Send Data Event under the Workflow group](/img/workflows/develop/start-workflow/run-workflow-node.png) + +3. Fill in the form: + + | Field | Required | Description | + |---|---|---| + | **Input** | Yes | The data the workflow starts with. It must match the workflow's [input type](create-workflow.md). Pick the request payload from **Inputs** in the value helper. | + | **Workflow ID Variable Name** | Yes | The variable that receives the ID of the started run. | + +4. Click **Save**. + +![Adding a Run Workflow step and following its link to the workflow it starts](/img/workflows/develop/start-workflow/add-run-workflow.gif) + +The step is drawn with an arrow across to the workflow it starts, so an entry point shows at a glance which process it kicks off. Click that marker to open the workflow itself. + +## Starting returns immediately + +**Run Workflow** starts the run and returns its ID straight away. It does not wait for the workflow to finish, and that is the point: a durable workflow may sit on a [human task](human-task-workflow.md) or a [data event](data-events.md) for days, and no caller should be held open for that. + +So hand the ID back to whoever will need it: + +- Return it in the response, so the caller can ask about the run later. +- Store it against your own record, such as the order row, so the process can be traced from your data. +- Include it in the callback URL you give a partner system, so the value they post [comes back to the right run](data-events.md). + +## Other ways to start a run + +| Route | Use it for | +|---|----------------------------------------------------------------------------------------| +| [Integration Control Plane](../icp/start-workflow.md) | Starting a run by hand from a generated form, for testing, onboarding, and operations. | +| [Management API](../reference/management-api.md) | `POST /workflows` for custom portals and automation of your own. | + +## Next steps + +- [Await data events](data-events.md) — deliver data into a run using the ID you kept. +- [Workflow executions](../icp/executions.md) — follow a started run through its timeline and execution graph. +- [Build an order processing workflow](../getting-started/build-an-order-processing-workflow.md) — a service resource that starts a workflow, end to end. diff --git a/en/docs/workflows/getting-started/build-a-claim-workflow-agent.md b/en/docs/workflows/getting-started/build-a-claim-workflow-agent.md new file mode 100644 index 0000000000..ef7efcfc0f --- /dev/null +++ b/en/docs/workflows/getting-started/build-a-claim-workflow-agent.md @@ -0,0 +1,220 @@ +--- +sidebar_position: 2 +title: "Build a Claim Handling Agent" +description: Build your first durable agentic workflow in WSO2 Integrator — an AI agent that validates expense claims and pays them only after a manager approves. +keywords: [wso2 integrator, durable workflow, agentic workflow, durable agent, claim workflow, human in the loop, approval] +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import ThemedImage from '@theme/ThemedImage'; +import useBaseUrl from '@docusaurus/useBaseUrl'; + +# Build a Claim Handling Agent + +**Time:** 15 minutes | **What you'll build:** A durable AI agent that receives expense claims, validates them, and pays them out — but only after a manager approves the payment from the Integration Control Plane. If the process crashes while waiting, it resumes exactly where it left off. + +:::info Prerequisites + +- [WSO2 Integrator installed](../../get-started/setup/local-setup.md) +- Signed in to WSO2 Integrator Copilot (provides the default AI model — no API key needed) + +::: + +## Step 1: Create the integration + +1. Open WSO2 Integrator. +2. Select **Create** in the **Create New Integration** card. +3. Set **Integration Name** to `ClaimHandler`. +4. Select **Create Integration**. + + + +## Step 2: Add a Durable Agentic Workflow + +1. In the design view, click **+ Add Artifact**. +2. Under **Durable Workflow**, select **Durable Agentic Workflow** and click **Next**. +3. Set **Name** to `claimAgent`. +4. Click **Create Agent**. + +![Create Durable Agent](/img/workflows/getting-started/build-a-claim-workflow-agent/create-agent.png) + +## Step 3: Describe the agent + +1. Click the agent node and give it its role and instructions: + +- **Role:** `Expense claim assistant` +- **Instructions:** + + ```text + Process expense claims end to end. Validate each claim with validateClaim first and + reject invalid claims with a clear reason. When a claim is valid, pay it with payClaim + using the claimed amount. Finish with a one-line summary of the outcome. + ``` +2. Click **Save**. + +![Agent node form with the Role and Instructions fields filled in](/img/workflows/getting-started/build-a-claim-workflow-agent/agent-role-and-instructions.png) + +## Step 4: Give the agent activities + +Activities are the units of work the agent can call. Each one runs durably — completed work is never lost or repeated, even across restarts. + +First add the claim validator: + + + + +1. Click **+** on the **bottom right** of the agent node and Click **+ Create Activity**. +2. Set the **Activity Name** to `validateClaim`. +3. Click **+ Add Parameter**. +4. For the **Type** field, select **+ Create New Type**. +5. In the **Create from scratch** tab, enter `ExpenseClaim` as the **Name**. +6. Click **+** next to **Fields** and add each field: + + | Field | Type | + |---|---| + | `claimId` | `string` | + | `amount` | `decimal` | + | `purpose` | `string` | + +7. Click **Save** to create the `ExpenseClaim` type. The modal closes and `ExpenseClaim` is auto-injected as the parameter type. +8. Name the parameter `claim` and click **Add**. +9. Fill the return type as `boolean` and click **Save**. +10. Select the newly created `validateClaim` activity to add it to the agent. + +![Add `validateClaim` activity`](/img/workflows/getting-started/build-a-claim-workflow-agent/create-expense-claim-type.png) + +Now give the activity its body. The activity is a function, so its flow returns the validation result: + +1. In the left sidebar, expand **Workflow Activities** and select `validateClaim`. +2. In the node panel on the right, under **Control**, select **Return**. +3. Click the **Expression** field to open the value helper, then select **Inputs** > `claim` > `amount`. +4. With the cursor after the inserted value, type `> 0d` to require a positive amount. +5. Click **Save**. and select `claimAgent` under **Workflows** to return to the agent diagram. + +![Add `validateClaim` activity`](/img/workflows/getting-started/build-a-claim-workflow-agent/validate-claim-body.gif) + + + + +```ballerina +@workflow:Activity +function validateClaim(ExpenseClaim claim) returns boolean { + return claim.amount > 0d; +} +``` + + + + +Then add the payment activity — this is the risky step, so gate it behind a manager: + +1. Add another activity named `payClaim`. +2. In the activity form, enable **Requires Approval** and set **Reviewer Roles** to `manager`. + +```ballerina +@workflow:Activity +function payClaim(string claimId, decimal amount) returns string { + return string `PAY-${claimId}`; +} +``` + + + +Behind the scenes the designer maintains a single declaration — the agent *is* the workflow: + +```ballerina +final workflow:DurableAgent claimAgent = check new ({ + systemPrompt: { + role: "Expense claim assistant", + instructions: string `Process expense claims end to end. ...` + }, + model: claimModel, + activities: [ + validateClaim, + {activity: payClaim, requiresApproval: true, userRoles: "manager"} + ] +}); +``` + +## Step 5: Expose the agent over HTTP + +Add an HTTP service so employees can submit claims. Each `run` starts a durable agent instance; the returned `instanceId` is the claim's reference. + +```ballerina +service /claims on new http:Listener(9090) { + + resource function post .(ExpenseClaim claim) returns json|error { + string instanceId = check claimAgent.run(claim.toJsonString()); + return {claimId: claim.claimId, instanceId, status: "PROCESSING"}; + } + + resource function get [string instanceId]() returns json|error { + string|error result = claimAgent.getResult(instanceId); + if result is workflow:AgentBusyError { + return {instanceId, status: "PENDING_APPROVAL"}; + } + if result is error { + return result; + } + return {instanceId, status: "COMPLETED", summary: result}; + } +} +``` + +## Step 6: Run it + +1. Select **Run** in the designer to start the integration. +2. Submit a claim: + +```bash +curl -X POST localhost:9090/claims -H 'Content-Type: application/json' \ + -d '{"claimId":"EXP-1","employee":"nimal","amount":180.50,"purpose":"Team lunch"}' +``` + +The agent validates the claim, decides to pay it, and **pauses** — the gated `payClaim` created an approval review for the `manager` role. The workflow now waits durably; you can even restart the integration and nothing is lost. + +## Step 7: Approve the payment + +1. Open the **Integration Control Plane** and sign in as a user with the `manager` role. +2. Open the **Task Inbox** — the `payClaim` approval shows the claim ID and amount the agent proposed. +3. Select **Proceed**. + + + +The agent resumes, completes the payment, and records its summary: + +```bash +curl localhost:9090/claims/ +# {"instanceId":"...","status":"COMPLETED","summary":"Claim EXP-1 validated and paid (PAY-EXP-1)."} +``` + +## What you built + +- A **durable AI agent** whose reasoning, activity calls, and waits all survive restarts. +- A **gated activity** — the agent can propose a payment, but only a manager can release it. +- A **zero-cost wait** — the claim can sit in the inbox for days without holding any resources. + +## Next steps + +- [Await human task](../develop/human-task-workflow.md) — ask people structured questions, not just approvals. +- [Error handling and review activities](../develop/review-activity-and-error-handling.md) — let a human fix a failed step's input and retry it. +- [Durable agentic workflows](../develop/durable-agentic-workflow.md) — events, multi-turn conversations, and agent-to-agent collaboration. diff --git a/en/docs/workflows/getting-started/build-an-order-processing-workflow.md b/en/docs/workflows/getting-started/build-an-order-processing-workflow.md new file mode 100644 index 0000000000..8afaa6e56a --- /dev/null +++ b/en/docs/workflows/getting-started/build-an-order-processing-workflow.md @@ -0,0 +1,440 @@ +--- +sidebar_position: 1 +title: "Build an Order Processing Workflow" +description: Build a crash-safe order processing workflow in WSO2 Integrator that reserves inventory, waits for a payment confirmation, and then confirms or cancels the order. +keywords: [wso2 integrator, durable workflow, order processing, activity, data event, wait, crash recovery] +--- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import ThemedImage from '@theme/ThemedImage'; +import useBaseUrl from '@docusaurus/useBaseUrl'; + +# Build an Order Processing Workflow + +**Time:** 20 minutes | **What you'll build:** A durable workflow that reserves inventory for an order, suspends until a payment confirmation arrives, and then either emails the customer or cancels the order. The every activity output and data events are recorded, so a restart replays the record instead of reserving twice, and the wait for payment costs nothing while it lasts, whether that is seconds or days. + +The finished flow has three steps: + +1. **Reserve inventory** as a recorded activity. +2. **Wait for a payment data event** delivered from outside the workflow. +3. **Send the confirmation email** when payment succeeded, or **cancel the order** when it did not. + + + +:::info Prerequisites + +- [WSO2 Integrator installed](../../get-started/setup/local-setup.md) + +::: + +## Step 1: Create the integration + +1. Open WSO2 Integrator. +2. Click **Create** in the **Create New Integration** card. +3. Set **Integration Name** to `OrderProcessor`. +4. Click **Create Integration**. + +## Step 2: Add durable workflow artifact + + + + +1. In the design view, click **+ Add Artifact**. +2. On the **Artifacts** page, under **Durable Workflow**, click **Durable Workflow**. The **Create New Durable Workflow** form opens. +3. Set **Name** to `orderWorkflow`. +4. Click the **Workflow Input Data Type** field. Let's create a new type for the order information that the workflow needs. +5. Click **+ Create New Type**. On the **Create from scratch** tab, with **Kind** set to **Record**. +6. Change **Name** from `MyType` to `OrderInfo`. +7. Add each field with the **+** next to **Fields**, then set its name and type: + + | Field | Type | + |---|---| + | `id` | `string` | + | `customerId` | `string` | + | `customerEmail` | `string` | + | `total` | `int` | + +8. Click **Save**. The record is added to your project and appears under **Types** in the sidebar. +9. Click **Create**. The workflow is generated and its diagram opens with a single **Start** node, ready for the first step. + +![Creating the orderWorkflow durable workflow and its OrderInfo input type](/img/workflows/getting-started/build-an-order-processing-workflow/create-workflow.gif) + + +```ballerina +type OrderInfo record {| + string id; + string customerId; + string customerEmail; + int total; +|}; +``` + +```ballerina +@workflow:Workflow +function orderWorkflow(workflow:Context ctx, OrderInfo orderInfo) returns json|error { +} +``` + + + +:::tip Reuse an existing type +**+ Create New Type** is only one way to fill **Workflow Input Data Type**. Pick any type already in the project straight from the list, or click **Open Type Browser** to search the wider set. Either way the result is an ordinary record you can edit later from **Types** in the sidebar. See [Types](../../develop/integration-artifacts/supporting/types.md). +::: + +## Step 3: Reserve the inventory + +Anything that touches the outside world belongs in an **activity** rather than in the workflow body. The runtime records each activity result, so a completed activity is never executed twice on replay. + +The first step of the order process reserves stock. You create the activity and the call that uses it in one pass, straight from the workflow diagram. + + + + +1. On the workflow diagram, click **+**. +2. In the node panel, under **Workflow** > **Steps**, click **Call Activity**. The **Activities** panel opens, listing everything this workflow can call. +3. Under **Current Integration**, click **+ Create Activity**. +4. In the **Workflow Activity** form, set **Activity Name** to `reserveInventory`. +5. Under **Parameters**, click **+ Add Parameter**, set **Type** to `OrderInfo` and **Name** to `orderInfo`, then click **Add**. +6. Leave **Return Type** empty. This activity holds stock and returns nothing. Click **Save**. +7. `reserveInventory` now appears under **Current Integration**, and under **Workflow Activities** in the sidebar. Click it to add the call. +8. Fill in the call form: + + | Field | Value | + |---|---| + | **Order Info** | The order to reserve. Switch to **Expression** and set it to the workflow's input parameter | + | **Retry Policy** | **No Automatic Retry** for now. | + +9. Click **Save**. The `reserveInventory` node appears on the diagram. +10. Give the activity something to do. Click the open icon on the node to open its own diagram, To make it simple let's mock the implementation to a log line. +11. Click **+**, then **Log Info** under **Logging**. Set **Msg** to `Inventory reserved` and click **Save**. + +![Creating the reserveInventory activity and calling it from the workflow](/img/workflows/getting-started/build-an-order-processing-workflow/add-activity.gif) + + +```ballerina +import ballerina/log; +import ballerina/workflow; + +@workflow:Activity +function reserveInventory(OrderInfo orderInfo) returns error? { + log:printInfo("Inventory reserved"); +} +``` + +```ballerina +@workflow:Workflow +function orderWorkflow(workflow:Context ctx, OrderInfo orderInfo) returns json|error { + anydata inventoryResult = check ctx->callActivity(reserveInventory, {orderInfo: orderInfo}); +} +``` + + + +:::tip Where activities come from +**+ Create Activity** writes a new activity function in your integration. **Create Activity from a Connection** wraps an action on a connector you already have, and **[Prebuilt Activities](../develop/prebuilt-activities/index.md)** holds the ones that ship with the runtime, for REST calls, SOAP calls, and email. See [Activities](../develop/activities.md). +::: + +## Step 4: Wait for the payment confirmation + +Payment is confirmed by something outside the workflow, such as a payment gateway callback or an operator action. Model that as a **data event**: a named slot the workflow waits on, and that anyone holding the workflow ID can fill. + + + + +1. Log a line first, so the run says why it is sitting still. Click **+** below the activity call, scroll to the bottom of the node panel, and click **Show More Functions**. +2. In the **Functions** panel, under **Imported Functions** > **log**, click **printInfo**. Set **Msg** to `Waiting for payment` and click **Save**. +3. Now add the wait. Click **+** below the log step. +4. In the node panel, under **Workflow** > **Steps**, click **Await Data Event**. The **Await Data** form opens. +5. Under **Data Waits**, fill in the entry: + + | Field | Value | Description | + |---|---|-----------------------------------------------------------------------------------------------------------------------------------------------------| + | **Data Receive Variable Name** | `payment` | The variable that receives the value once it arrives. | + | **Data Type** | `boolean` | The type of the value the workflow expects. To make this article simple lets go with `boolean`. If the payment is receved, the value will be `true` | + | **Data Name** | `payment` | The name used when sending the data into this workflow. | + +6. Click **Add** then click **Save**. + +The diagram gains a **Wait for payment** node, drawn with an incoming arrow from outside the flow, because that is where the value comes from. +The workflow now suspends at this line, and only at this line. It holds no thread, no memory, and no connection while it waits, and it survives a restart of the runtime. + +![Adding the payment data event to the workflow](/img/workflows/getting-started/build-an-order-processing-workflow/await-data-event.gif) + + +```ballerina +# Data record for workflow function +type OrderWorkflowData record {| + future payment; +|}; +``` + +```ballerina +@workflow:Workflow +function orderWorkflow(workflow:Context ctx, OrderInfo orderInfo, OrderWorkflowData data) returns json|error { + anydata inventoryResult = check ctx->callActivity(reserveInventory, {orderInfo: orderInfo}); + log:printInfo("Waiting for payment"); + boolean payment = check wait data.payment; +} +``` + + + +:::tip Data event or human task? +Use a **data event** when a system or a person submits *content* the workflow processes, as here. Use a [human task](../develop/human-task-workflow.md) when a person makes a *decision* that the Integration Control Plane should render as a form in their inbox. +::: + +## Step 5: Branch on the payment result + +The value that arrived decides what happens next, so split the flow in two. + + + + +1. Click **+** below the wait. +2. In the node panel, under **Control**, click **If**. +3. **Condition** is prefilled with `true`. Replace it with the value the wait produced: click the field and pick `payment` under **Variables** in the value helper. +4. Click **Save**. + +The diagram splits into a `payment` path and an **Else** path, each with its own **+**. + +![Branching the workflow on the payment result](/img/workflows/getting-started/build-an-order-processing-workflow/branch-on-payment.gif) + + +```ballerina +if payment { +} else { +} +``` + + + +:::tip More than two paths +**+ Add Else If Block** adds another condition to the same node, and **Remove Else Block** drops the else path when a branch is all you need. +::: + +## Step 6: Send the confirmation email + +The `payment` path tells the customer the order is confirmed. Create that activity and call it in one pass, the same way as `reserveInventory`. + + + + +1. Click **+** on the `payment` path, then under **Workflow** > **Steps**, click **Call Activity**. +2. The **Activities** panel already lists `reserveInventory`, so click the **+** on the **Current Integration** header to add another activity. +3. Set **Activity Name** to `sendEmail`. Click **+ Add Parameter**, set **Type** to `OrderInfo` and **Name** to `orderInfo`, click **Add**, then click **Save**. +4. Click `sendEmail` in the **Activities** panel, set **Order Info** to the workflow's input, and click **Save**. +5. Click the open icon on the `sendEmail` node to open its diagram, To make it simple let's mock the implementation to a log line. +6. Click **+**, then click **Log Info** under **Logging**. +7. Leave **Msg** on **Text** and type `Email sent to `. Click on the text box open the field's value helper, then click **Inputs** > `orderInfo` > `customerEmail`. It lands in the text as an expression. +8. Click **Save**. + +![Creating the sendEmail activity, calling it, and logging the customer address](/img/workflows/getting-started/build-an-order-processing-workflow/send-email.gif) + + +```ballerina +@workflow:Activity +function sendEmail(OrderInfo orderInfo) { + log:printInfo(string `Email sent to ${orderInfo.customerEmail}`); +} +``` + + + +:::tip Text or Expression? +Keep **Msg** on **Text** whenever the message is words with values dropped into it. Text takes what you type as the message and turns each inserted value into an interpolation, which is what produces the string template above. **Expression** treats the whole field as a single Ballerina expression, so bare words like `Email sent to` are a syntax error there. The same toggle appears on any field that accepts either, such as **Order Info** on a call. +::: + +## Step 7: Cancel the order + +The **Else** path releases the hold instead. + + + + +1. Click **+** on the **Else** path, then under **Workflow** > **Steps**, click **Call Activity**. +2. Click the **+** on the **Current Integration** header. +3. Set **Activity Name** to `cancelOrder`, add an `orderInfo` parameter of type `OrderInfo` the same way, and click **Save**. +4. Click `cancelOrder` in the **Activities** panel, set **Order Info** to the workflow's input, and click **Save**. + +Both branches now end in an activity, and the workflow is complete. + +![Creating the cancelOrder activity and calling it on the else path](/img/workflows/getting-started/build-an-order-processing-workflow/cancel-order.gif) + + +```ballerina +@workflow:Activity +function cancelOrder(OrderInfo orderInfo) { +} +``` + +```ballerina +@workflow:Workflow +function orderWorkflow(workflow:Context ctx, OrderInfo orderInfo, OrderWorkflowData data) returns json|error { + anydata inventoryResult = check ctx->callActivity(reserveInventory, {orderInfo: orderInfo}); + log:printInfo("Waiting for payment"); + boolean payment = check wait data.payment; + if payment { + anydata result = check ctx->callActivity(sendEmail, {orderInfo: orderInfo}); + } else { + anydata result = check ctx->callActivity(cancelOrder, {orderInfo: orderInfo}); + } +} +``` + + + +## Step 8: Start the workflow from a service + +A workflow does not start itself. It is launched from an entry point such as a service or an automation, so give the order process an HTTP resource that starts a run for each order that arrives. + + + + +1. Click **+ Add Artifact**, then under **Integration as API**, click **HTTP Service**. +2. On the **Create HTTP Service** form, keep **Service Contract** on **Design From Scratch**, put **Service Base Path** as `/'order`, and click **Create**. +3. The service opens with no resources. Click **Add Resource**. +4. Set **HTTP Method** to **POST** and **Resource Path** to `.`. +5. Click **+ Define Payload**, open the **Browse Existing Types** tab, click `OrderInfo` under the current integration, and click **Save**. +6. Click **Save** to create the resource. Its own diagram opens. +7. Click **+**, then under **Workflow**, click **Run Workflow**. +8. Select the `orderWorkflow` under **Current Integration**. +9. Set **Input** to the request payload and leave **Workflow ID Variable Name** as `workflowId`. Click **Save**. +10. Click **+** below the node, click **Return** under **Control**, set **Expression** to `workflowId`, and click **Save**. + +The resource now starts a run for every order it receives and answers with that run's workflow ID. + +![Adding an HTTP service with a POST order resource that starts the workflow](/img/workflows/getting-started/build-an-order-processing-workflow/start-workflow.gif) + + + ```ballerina + import ballerina/http; + import ballerina/workflow; + + listener http:Listener httpDefaultListener = http:getDefaultListener(); + + service /'order' on httpDefaultListener { + + resource function post .(OrderInfo payload) returns json|error { + string workflowId = check workflow:run(orderWorkflow, payload); + return workflowId; + } + } + ``` + + + +:::info Hold on to the workflow ID +**Run Workflow** starts an instance and returns immediately with its ID. It does not wait for the order to finish, which is the point: the run may sit on the payment event for days. Return the ID to the caller, because it is what ties a later payment confirmation to this instance. +::: + +:::tip Why `'order` and not `order` +`order` is a Ballerina keyword, so the resource path is written as the quoted identifier `'order`. The HTTP path is unaffected: the resource still answers on `/order`. +::: + +## Step 9: Deliver the payment confirmation + +The run is now waiting on the `payment` data event, and it will wait forever until something fills it. Add a second resource that does. + + + + +1. On the service, click **+ Resource**. +2. Set **HTTP Method** to **POST**. +3. The path needs a segment that carries the workflow ID, so click **+ Path Param** and fill in the **Path Parameter** form: + + | Field | Value | Description | + |---|---|---| + | **Name** | `orderId` | The variable the segment binds to, and the name you reference it by inside the resource. | + | **Type** | `string` | Under **Advanced Configurations**. Defaults to `string`, which is what a workflow ID is. | + +4. Click **Save**. The segment lands in **Resource Path** as `[string orderId]`. Complete the path so it reads `[string orderId]/payment`. +5. Click **Save** to create the resource. Its diagram opens. +6. Click **+**, then under **Workflow**, click **Send Data Event**. +7. Fill in the **Send Data** form: + + | Field | Value | Description | + |---|---|--------------------------------------------------------------------------------| + | **Workflow Name** | `orderWorkflow` | The workflow to send the data to. The dropdown lists all the available workflows. | + | **Target Workflow Id** | `orderId` | The instance to resume, taken from the request path. | + | **Data Name** | `payment` | The event to fill. The dropdown lists the events the chosen workflow declares. | + | **Data** | `true` | The value to deliver. | + +8. Click **Save**. + +The node reads **Send to payment** and is drawn with a dashed arrow across to `orderWorkflow`, because it hands a value to a run rather than calling something. + +![Adding a payment resource that sends the data event into the running workflow](/img/workflows/getting-started/build-an-order-processing-workflow/send-data-event.gif) + + +```ballerina +resource function post [string orderId]/payment() returns json|error { + check workflow:sendData(orderWorkflow, orderId, "payment", true); +} +``` + + + +:::tip Take the value from the request +Anyone holding the workflow ID can deliver the value, so in a real integration this resource is what the payment gateway's callback hits. See [Await data events](../develop/data-events.md). +::: + +## Step 10: Run it + +A durable workflow keeps its record in a workflow engine, and by default the runtime expects a local Temporal server (`mode` defaults to `LOCAL`). To keep this walkthrough self-contained, switch to the in-memory engine, which runs inside the integration and needs nothing external. + +1. In the sidebar, click **Configurations**. +2. On the **Configurable Variables** page, under **Imported libraries**, click **ballerina/workflow**. +3. In the box under `mode`, enter `"IN_MEMORY"`. + + ![Setting the workflow mode to IN_MEMORY in Configurable Variables](/img/workflows/getting-started/build-an-order-processing-workflow/set-in-memory-mode.gif) + :::warning `IN_MEMORY` does not survive a restart + The in-memory engine keeps the record in the integration's own memory, so stopping the integration loses every run that was in flight. It is meant for trying a workflow out, not for the crash-safety this guide is about. To see a suspended order survive a restart, set `mode` back to `"LOCAL"` and start a Temporal server with `temporal server start-dev` before running. + ::: +4. Click **Run** to start the integration. +5. Post an order and keep the returned workflow ID: eg: `019ffed4-c12e-7e24-a438-8bdaae2b5a29` + + ```bash + curl -X POST http://localhost:9090/order \ + -H 'Content-Type: application/json' \ + -d '{"id": "ORD-1", "customerId": "CUS-9", "customerEmail": "ann@example.com", "total": 4500}' + ``` + + The workflow reserves the inventory and then suspends on the `payment` event. +```bash + Compiling source (UP-TO-DATE) + dulminakodagoda/orderprocessor:0.1.0 + + Running executable + + time=2026-08-14T11:23:04.009+05:30 level=INFO module=dulminakodagoda/orderprocessor message="Inventory reserved" + time=2026-08-14T11:23:04.028+05:30 level=INFO module=dulminakodagoda/orderprocessor message="Waiting for payment" + ``` +6. Confirm the payment with the workflow ID from the previous response: + + ```bash + curl -X POST http://localhost:9090/order/019ffed4-c12e-7e24-a438-8bdaae2b5a29/payment \ + -H 'Content-Type: application/json' -d 'true' + ``` + + The workflow resumes and sends the confirmation email. Post `false` instead and it cancels the order. + + ```bash + time=2026-08-14T11:34:13.224+05:30 level=INFO module=dulminakodagoda/orderprocessor message="Email sent to ann@example.com" + ``` + +## Watch it run + +Once the integration is [connected to the Integration Control Plane](../icp/connect-runtime.md), every instance is visible there, running or completed. The [execution graph](../icp/executions.md) shows the reservation as a completed activity and the pending payment as a `DATA` node with status `WAITING`, so anyone can see exactly what an order is blocked on instead of guessing that it is stuck. + +## Next steps + +- [Activities](../develop/activities.md) — retry policies, activity inputs, and how results are recorded. +- [Await data events](../develop/data-events.md) — several events, timeouts, and delivering data from other systems. +- [Await human task](../develop/human-task-workflow.md) — pause the order for a person's decision instead of a system's data. +- [Durable timers](../develop/durable-timers.md) — add a payment deadline that survives restarts. diff --git a/en/docs/workflows/icp/connect-runtime.md b/en/docs/workflows/icp/connect-runtime.md new file mode 100644 index 0000000000..66fdc1e3ba --- /dev/null +++ b/en/docs/workflows/icp/connect-runtime.md @@ -0,0 +1,199 @@ +--- +title: "Connect a Workflow Runtime" +description: Register an integration that runs durable workflows with the WSO2 Integration Control Plane so its executions, human tasks, and reviews appear in the console. +keywords: [wso2 integrator, integration control plane, icp, workflow runtime, enable workflow management, config.toml, task queue] +sidebar_label: "Getting Started" +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +# Connect a Workflow Runtime + +A durable workflow runs inside your integration. The Integration Control Plane (ICP) can list its executions, hand out its human tasks, and control running instances only after the runtime tells ICP where to reach it. This page walks through the whole setup: creating the integration in ICP, generating a secret with workflow management enabled, applying the configuration to your integration, and verifying that the workflows show up. + +:::info Prerequisites + +- An ICP server that is running and reachable on port `9445` ([Install ICP](../../manage/icp/install-icp.md)) +- A project and at least one environment in ICP ([Manage projects](../../manage/icp/manage-projects.md)) +- An integration that defines at least one durable workflow ([Build an order processing workflow](../getting-started/build-an-order-processing-workflow.md)) +::: + +## 1. Create a workflow integration + +The **Workflow** integration type tells ICP that this integration hosts workflows. It gives the integration its own **Workflows** page and lists workflow definitions instead of service endpoints on the overview. + +1. Go to **Projects** > *your project*. +2. Click **+ Create Integration**. +3. Fill in the form: + + | Field | Value | + | --- | --- | + | **Display Name** | A readable name, for example `Order Processing` | + | **Name** | The URL-safe handle derived from the display name. Note this value. It becomes the workflow task queue. | + | **Technology** | **WSO2 Integrator** | + | **Integration Type** | **Workflow** | + +4. Click **Create**. + +:::note +The **Workflow** integration type is offered only for **WSO2 Integrator**. The workflow engine and its management API are not available on **WSO2 Integrator: MI**. +::: + +## 2. Generate a secret with workflow management enabled + +1. Open the integration and go to **Runtimes**. +2. Find the environment card, for example **dev**, and click **Add Runtime**. +3. Click **Generate Secret**. +4. Check that **Enable Workflow Management** is on. For an integration created with the **Workflow** type the toggle is not shown, because workflow management is always enabled. For any other integration type, switch it on. +5. Copy the generated `Config.toml` snippet. + +:::warning +The secret is shown once. Copy it before you close the dialog. The same secret authenticates the runtime bridge and protects the workflow management API, so you need it in two places in `Config.toml`. +::: + +:::tip +You can also generate a secret from **Runtimes** in the organization sidebar. That dialog is organization-scoped, so `project` and `integration` come through as placeholders that you fill in yourself. Keep `taskQueue` identical to the `integration` value. +::: + +## 3. Apply the configuration + +Three files in your integration change: `Config.toml`, `Ballerina.toml`, and `main.bal`. + +### Config.toml + +Paste the copied snippet into `Config.toml`, then replace `runtime` with a unique name for this instance: + +```toml +[wso2.icp.runtime.bridge] +environment = "dev" +project = "order-processing" +integration = "order-api" +runtime = "order-api-node-1" +secret = "" +enableWorkflowManagement = true +# workflowManagementApiPort = 8234 +# serverUrl = "https://:9445" +# runtimeHostUrl = "http://" + +[ballerina.workflow] +# mode = "LOCAL" +taskQueue = "order-api" + +[ballerina.workflow.management] +enableManagementApi = true +enableApiKey = true +apiKeyValue = "" +apiKeyHeader = "X-API-Key" +enableBasicAuth = false +# port = 8234 +``` + +The three blocks do different jobs: + +- `[wso2.icp.runtime.bridge]` registers the runtime with ICP and reports where the workflow management API can be reached. +- `[ballerina.workflow]` configures the workflow engine, including the task queue this integration polls. +- `[ballerina.workflow.management]` turns on the REST management API that ICP calls, and protects it with an API key. + +### Workflow configuration reference + +| Key | Block | Default | Description | +| --- | --- | --- | --- | +| `enableWorkflowManagement` | bridge | `false` | Report the workflow management API's URL to ICP in the heartbeat. Without it, ICP has no address to call and every workflow view returns "No running workflow runtime with a callback URL for this environment". | +| `workflowManagementApiPort` | bridge | `8234` | The port ICP is told to call. Keep it equal to `port` in `[ballerina.workflow.management]`. | +| `runtimeHostUrl` | bridge | `http://localhost` | The host ICP is told to call. Set it when ICP cannot reach the runtime at `localhost`, such as in containers or Kubernetes. If this value already includes a port, that port is used and `workflowManagementApiPort` is ignored. | +| `taskQueue` | workflow | none | The queue this integration polls. It must match the integration handle in the bridge block. ICP uses it to map an execution back to the integration that owns it. | +| `mode` | workflow | `LOCAL` | How the runtime connects to the workflow engine. Add `url`, `namespace`, and the authentication keys your deployment needs to the same block. | +| `enableManagementApi` | management | `false` | Expose the workflow management REST API. | +| `enableApiKey`, `apiKeyValue`, `apiKeyHeader` | management | `false`, none, `x-api-key` | Protect the management API with an API key. Set `apiKeyValue` to the secret ICP generated. ICP sends that exact value in the `X-API-Key` header. | +| `port` | management | `8234` | The port the management API listens on. | + +:::warning +Set `apiKeyValue` to the secret from step 2, unchanged. ICP reconstructs the key from the secret the runtime registered with, so a different value makes every workflow request fail with an authentication error. If you revoke and regenerate the secret, update both `secret` and `apiKeyValue`. +::: + +### Ballerina.toml + +Enable remote management so ICP can manage the runtime: + +```toml +[build-options] +remoteManagement = true +``` + +### main.bal + +Add both imports. Workflow management is a separate module, and its `[ballerina.workflow.management]` configuration only takes effect when the module is part of the build: + +```ballerina +import ballerina/workflow.management as _; + +import wso2/icp.runtime.bridge as _; +``` + +Both are blank imports (`as _`). They register their modules, which activate at startup. + +## 4. Start the runtime and verify + + + + +Run the integration from its directory: + +```bash +bal run +``` + + + + +Click **Run**. Because ICP is already running externally, a popup reports that ICP is not running. Click **Run Anyway**. + + + + +Then check the console: + +1. Under **Runtimes**, the runtime appears with status **RUNNING**. +2. A **Workflows** item appears in the integration's sidebar. +3. On the integration overview, the environment card lists your workflow definitions under **Workflow Definitions**. Selecting one shows its running instances, with **View Workflows** and **Start New Workflow** actions. + +If all three are true, the setup is complete. Continue with [Start a workflow](start-workflow.md). + +## How ICP reaches the runtime + +Requests from the console are relayed by ICP rather than sent to the runtime by your browser: + +```text +Browser -> https:///icp/workflow/{integrationId}/{environmentId}/ + -> /workflow/ +``` + +Along the way ICP does three things: + +- **Authorizes the caller** against the workflow permissions for that integration, and rejects the request with `403` if the caller lacks them. +- **Replaces the caller's ICP token** with the runtime's API key, and adds `x-user-id` and `x-user-roles` headers so the runtime can filter tasks by role. +- **Picks the target runtime**, which is the running runtime that reported a workflow callback URL for that integration and environment. + +Two consequences are worth knowing: + +- Requests time out after 30 seconds by default. Tune `workflowProxyTimeout` on the ICP server if your runtime needs longer. +- A plain `http` callback URL sends the API key unencrypted, and ICP logs a warning when it does. Use an `https` callback URL in production. ICP validates the runtime's certificate by default. To accept a self-signed certificate in a development or in-cluster deployment, set `workflowProxyAllowInsecureTLS = true` on the ICP server. + +## Troubleshooting + +| Symptom | Cause | Fix | +| --- | --- | --- | +| The **Workflows** page reports that no running workflow runtime has a callback URL | The runtime is offline, or it registered without `enableWorkflowManagement = true` | Confirm the runtime is **RUNNING**, add the key to `Config.toml`, and restart the runtime | +| Every workflow request fails with an authentication error | `apiKeyValue` does not match the secret the runtime registered with | Set `apiKeyValue` to the same secret as `secret`, then restart | +| Lists load, but requests time out or cannot connect | ICP cannot reach the address the runtime reported | Set `runtimeHostUrl` to a host ICP can resolve, and confirm `workflowManagementApiPort` matches the management API `port` | +| Executions appear at project level but the integration has no **Workflows** item | The integration is not typed as **Workflow** | Create the integration with the **Workflow** integration type, or use the project-level page | +| Tasks and reviews are missing for a user who can open the page | The user holds no ICP role matching the task's eligible roles | Create roles named exactly as the workflow declares them, and map them to the user's group | +| The runtime does not appear at all | The bridge cannot reach ICP, or the secret is wrong | See [Connect an integration to ICP](../../manage/icp/connect-runtime.md#troubleshooting) | + +## What's next + +- [Start a workflow](start-workflow.md) — launch a new execution from the console +- [Workflow executions](executions.md) — inspect the timeline, execution graph, and history of a run +- [Manage workflows with the Integration Control Plane](managing-workflows.md) — the permissions and roles that control each view +- [Management API](../reference/management-api.md) — the REST API the console calls diff --git a/en/docs/workflows/icp/executions.md b/en/docs/workflows/icp/executions.md new file mode 100644 index 0000000000..40ebe9c19c --- /dev/null +++ b/en/docs/workflows/icp/executions.md @@ -0,0 +1,133 @@ +--- +title: "Workflow Executions" +description: Browse durable workflow executions in the WSO2 Integration Control Plane and inspect each run through its timeline, execution graph, and event history. +keywords: [wso2 integrator, integration control plane, icp, workflow execution, timeline, execution graph, workflow history, suspend, terminate] +sidebar_label: "Workflow Executions" +--- + +# Workflow Executions + +The **Workflow Executions** tab is the operational view of your durable workflows. It lists every run in the selected environment, and opening one shows what the run did, how long each step took, where it is now, and what it is waiting for. From the same view you can suspend, resume, cancel, or terminate a run. + +:::info Prerequisites + +- A runtime registered with workflow management enabled ([Connect a workflow runtime](connect-runtime.md)) +- The `workflow_mgt:view_workflows` permission to browse, and `workflow_mgt:manage_workflows` to control a run +::: + +## Browse executions + +Open **Workflows** at project or integration level, choose an environment, and go to the **Workflow Executions** tab. + +| Column | Description | +| --- | --- | +| **Workflow ID** | The identifier of the run, either the one supplied when it started or one generated by the runtime. | +| **Name** | The workflow type that is running. | +| **Integration** | The integration that owns the run. Shown at project level only. | +| **Status** | Running, Completed, Failed, Terminated, Canceled, or Timed out. | +| **Started** | When the run began. | +| **Actions** | Opens the execution detail panel. | + +Executions are listed newest first, 50 at a time. When more match, the list reports that it is showing the first 50 and asks you to narrow the filters. + +### Filters + +| Filter | Use it to | +| --- | --- | +| Search | Find a run by workflow ID. | +| **Status** | Show only running, completed, failed, terminated, canceled, or timed out runs. | +| **Workflow name** | Show one workflow type. The list spans every workflow the project's integrations advertise. | +| **Integration** | Narrow a project-level list to one integration. | +| Time range | Restrict by start time, either a preset such as **Past 1 hour** or **Past 24 hours**, or a **Custom** range with explicit bounds. | + +**Clear** resets every filter at once. The refresh icon reloads the list without changing them. + +:::tip +Workflow IDs shown on the **My Tasks** and **Review Activities** tabs are links. Clicking one brings you here with the list already filtered to that run. +::: + +## Inspect one execution + +Click the view icon on a row. A panel opens with the workflow ID and status in the header, and three tabs: **Timeline**, **Execution Graph**, and **History**. + +### Timeline + +The **Timeline** tab answers "what happened, and how long did it take". + +At the top, two panels show the data the run started with and its current execution info: + +- **Start input** is the input the workflow was started with, decoded and pretty-printed. +- **Execution info** is the run's metadata, including its type, status, run ID, and close time. + +Below them, the run is drawn as a Gantt chart with one row per step, in execution order. The first row is the workflow itself, so its bar spans the whole run. + +| Row | What it represents | +| --- | --- | +| Workflow | The root execution, from start to its terminal event. | +| Activity | One activity invocation, from when it was scheduled to when it closed. | +| Human task | A task awaiting a person. | +| Review activity | A review gate, shown as `review-` followed by the reviewed activity's name, so it is distinct from the activity's own row. | +| Timer | A durable timer, from started to fired or canceled. | +| Signal | A point in time rather than a duration, drawn as a single marker. | + +Colour carries status. Completed steps are green, failures red, cancellations and timeouts amber, and running steps blue. Successful activities and human tasks use their own hues so they stand out among the completed steps. Hovering a bar shows the step's full name, status, and duration. + +While anything is still running, its bar is animated and grows against a live clock, and the header reads `Running for` with the elapsed time. Once the run closes, the header reports the total duration instead. The axis along the bottom marks elapsed time from the start of the run. + +### Execution graph + +The **Execution Graph** tab answers "what depends on what, and where is the run now". + +The graph is drawn top to bottom from a **Start** marker, one layer per dependency level. Steps that can run in parallel sit side by side on the same row, and arrows show the order the runtime followed. Each node card carries the step name, its kind, its duration, and a status dot. + +| Node kind | Meaning | +| --- | --- | +| **Activity** | A recorded unit of work. | +| **Human task** | A task assigned to a role. | +| **Review activity** | An approval gate before an activity runs, or a rerun decision after one fails. | +| **Timer** | A durable timer. | +| **Data** | A data event the workflow waits on. | + +Node status uses the same vocabulary throughout: `RUNNING`, `WAITING`, `COMPLETED`, `FAILED`, `TIMED_OUT`, and `CANCELED`. + +:::tip Find where a run is halted +A **Data** node with status `WAITING` is exactly where the workflow is blocked, waiting for a [data event](../develop/data-events.md) to arrive. A pending **Human task** or **Review activity** node means the run is waiting on a person. +::: + +Click a node to open a side panel with that step's detail: + +- **Status and duration** for the step. +- **Input**, the arguments the step was called with. +- **Result**, the value it returned. A step that completed without a return value says so, rather than showing an empty box. +- **Error**, the failure message, when the step failed. + +Click the node again, or the close icon on the panel, to dismiss it. + +### History + +The **History** tab lists the raw recorded events for the run, in order. This is the underlying record that the timeline and the graph are built from. Use it when you need the exact event sequence for auditing or for debugging a replay. + +## Control a running execution + +When a run is still active, the detail panel shows lifecycle actions above the tabs. They require `workflow_mgt:manage_workflows` and are hidden from anyone else. + +| Action | Effect | +| --- | --- | +| **Suspend** | Pauses the run. It holds its exact position and resumes from there. | +| **Resume** | Continues a suspended run. | +| **Cancel** | Requests a graceful stop that the workflow can react to and clean up after. | +| **Terminate** | Stops the run immediately, with no cleanup. You are asked to confirm, and can record a reason. | + +Terminating cannot be undone. Prefer **Cancel** whenever the workflow has cleanup logic worth running. + +:::note +A suspended run may continue to be listed as **Running**, because suspension is delivered as a signal to the execution rather than as a separate engine status. **Resume** appears once the runtime reports the run as suspended. +::: + +## What's next + +- [Start a workflow](start-workflow.md) — launch a new execution from the console +- [Complete human tasks](human-tasks.md) — decide the tasks a halted run is waiting on +- [Review activities](review-activities.md) — approve, correct, or reject a gated or failed activity +- [Activities](../develop/activities.md) — how the steps on the timeline are recorded and retried +- [Management API](../reference/management-api.md) — read the same history and execution graph over REST diff --git a/en/docs/workflows/icp/human-tasks.md b/en/docs/workflows/icp/human-tasks.md new file mode 100644 index 0000000000..948d221fde --- /dev/null +++ b/en/docs/workflows/icp/human-tasks.md @@ -0,0 +1,126 @@ +--- +title: "Complete Human Tasks" +description: Find, decide, and fail the human tasks that durable workflows are waiting on, from the WSO2 Integration Control Plane task inbox. +keywords: [wso2 integrator, integration control plane, icp, human task, task inbox, complete task, eligible roles, approval] +sidebar_label: "Complete Human Tasks" +--- + +# Complete Human Tasks + +When a workflow reaches a decision only a person can make, it suspends and creates a **human task**. The task appears in the **My Tasks** tab of the Integration Control Plane for everyone whose roles allow deciding it, and the workflow resumes the moment one of them submits. Nothing is held open while it waits, so a task can sit for minutes or for months. + +For how workflows create these tasks, see [Await human task](../develop/human-task-workflow.md). + +:::info Prerequisites + +- A runtime registered with workflow management enabled ([Connect a workflow runtime](connect-runtime.md)) +- The `workflow_mgt:view_human_tasks` permission to see tasks, and `workflow_mgt:manage_human_tasks` to act on them +- An ICP role whose name matches a role the task was assigned to +::: + +## Find your tasks + +Open **Workflows**, choose an environment, and stay on the **My Tasks** tab. Its badge counts the tasks waiting on your roles and refreshes every 30 seconds. + +| Column | Description | +| --- | --- | +| **Task** | The task's title, or its name when the workflow set no title. | +| **Workflow Name** | The workflow type that created the task. | +| **Integration** | The integration that owns it. Shown at project level only. | +| **Workflow ID** | The waiting run. Click it to open that run in [Workflow executions](executions.md). | +| **Status** | Pending, Completed, Failed, or Terminated. | +| **Started** | When the task was created. | +| **Open** | Opens the task. | + +The list opens filtered to **Pending**. Change the **Status** filter to review tasks that have already been decided. + +A task marked **Read-only** is one you can see but not decide, because you hold no role that permits completing it. + +### Task statuses + +| Status | Meaning | +| --- | --- | +| **Pending** | Waiting for someone to decide it. | +| **Completed** | Someone submitted a result and the workflow resumed. | +| **Failed** | Failed explicitly through the fail action, or timed out before anyone acted. | +| **Canceled** | Retired because the parent workflow closed. | +| **Terminated** | An administrator terminated the underlying task. | + +## Decide a task + +Click the open icon. The dialog shows what you need in order to decide: + +- **Description**, the context the workflow supplied. +- **Task Detail**, with the creation time, the **Eligible Roles** that may decide it, and every field of the task's payload, one row per value. + +From there you have two actions, both of which require `workflow_mgt:manage_human_tasks`. + +### Complete + +**Complete** opens the decision form and **Submit Completion** sends it. The workflow resumes with your answer as a plain typed value. + +The form comes from the decision type the workflow declared: + +```ballerina +public enum RequestAction { + REQUEST_BILL, + REJECT +} + +public type RequestDecision record {| + RequestAction action; + string comment = ""; +|}; +``` + +That record renders as a dropdown of `REQUEST_BILL` and `REJECT` for the action, and a text box for the comment. Booleans become Yes/No toggles, numbers are validated as numbers, and a nested record becomes an indented group of fields. Required fields carry a red asterisk. + +When a task declares no decision type, the dialog falls back to a **Result (JSON)** box where you enter the payload by hand. + +:::tip +Keep decision records small. Whatever you put in them is exactly what the decider fills in, so an action enum plus a comment usually reads better than a long form. +::: + +### Fail + +**Fail** ends the task without a decision. A **Reason** is required. The workflow sees the task as failed and handles it with its own error handling, for example by escalating to another role or by ending the process. + +Use **Fail** when the task cannot be decided at all. To answer with a rejection that the workflow expects, complete the task with the rejecting value instead, so the workflow follows its normal rejection path. + +## Who can decide a task + +A workflow names the roles that may decide each task: + +```ballerina +RequestDecision request = check ctx->awaitHumanTask("checkExpenseRequest", "manager", + payload = {"claimId": claim.claimId, "amount": claim.amount}, + title = string `Check expense request ${claim.claimId}`, + timeout = {days: 3}); +``` + +When you open the tab, ICP sends your ICP role names to the runtime, which returns only the tasks whose eligible roles include one of them. The comparison is an exact, case-sensitive match on the role name, so the task above is visible only to users holding an ICP role named exactly `manager`. + +To make that work, create the roles your workflows name, grant them the workflow permissions, and map them to the groups whose members should decide those tasks. See [Access control](../../manage/icp/access-control.md). + +:::note +Permissions and roles do different jobs. `workflow_mgt:manage_human_tasks` decides whether you may act on tasks at all, and the role name decides which tasks you see. Holding the permission without a matching role shows you nothing to decide. +::: + +## Tasks that time out + +A workflow can bound how long it waits: + +```ballerina +RequestDecision|error decision = ctx->awaitHumanTask("checkExpenseRequest", "manager", + payload = ..., timeout = {days: 3}); +``` + +If nobody decides in time, the task moves to **Failed** and the workflow receives a timeout error to handle. Watch the badge and the **Started** column to catch tasks that are approaching their deadline. + +## What's next + +- [Await human task](../develop/human-task-workflow.md) — how a workflow creates tasks and types their decisions +- [Await data events](../develop/data-events.md) — wait for data from a system or a person instead of a decision +- [Review activities](review-activities.md) — decisions attached to an activity rather than free-standing +- [Workflow executions](executions.md) — see where the waiting run is halted +- [Management API](../reference/management-api.md) — complete tasks programmatically diff --git a/en/docs/workflows/icp/managing-workflows.md b/en/docs/workflows/icp/managing-workflows.md new file mode 100644 index 0000000000..c79828ac7a --- /dev/null +++ b/en/docs/workflows/icp/managing-workflows.md @@ -0,0 +1,111 @@ +--- +title: "Manage Workflows with the Integration Control Plane" +description: Monitor durable workflow executions, decide human tasks and reviews, and control running instances from the WSO2 Integration Control Plane. +keywords: [wso2 integrator, integration control plane, icp, workflow monitoring, human task, review activity, workflow permissions] +--- + +# Manage Workflows with the Integration Control Plane + +The **Integration Control Plane (ICP)** is where running workflows meet their humans. Operations teams watch executions and intervene, and business users decide the tasks and reviews that workflows are waiting on. Everything is gated by role-based access, so each person sees only the work that belongs to them. + +This page explains where workflow management lives in the console, how the console maps to your projects and integrations, and which permissions control each view. The pages linked at the end cover each task in detail. + +:::info Prerequisites + +- An ICP server that is running and reachable ([Install ICP](../../manage/icp/install-icp.md)) +- An integration with durable workflows whose runtime is registered with workflow management enabled ([Connect a workflow runtime](connect-runtime.md)) +::: + +## Where workflow management lives + +Workflow management appears as a **Workflows** item in the ICP sidebar at two levels. + +| Level | Path | What it covers | +| --- | --- | --- | +| Project | **Projects** > *project* > **Workflows** | Every workflow execution, task, and review in the project, across all its integrations. | +| Integration | **Projects** > *project* > **Integrations** > *integration* > **Workflows** | Only the executions, tasks, and reviews owned by that one integration. | + +The integration-level item appears only for integrations created with the **Workflow** integration type. The project-level item is always available, because a project's workflow data is shared across its integrations rather than tied to any single one. + +Pick the environment you want to work in from the **Environment** selector at the top right of the page. Every list on the page reads from the runtime registered for that environment. + +## The three views + +The **Workflows** page has three tabs. Each one is a separate task, and each is gated by its own permission. + +| Tab | What you do there | Details | +| --- | --- | --- | +| **My Tasks** | Complete or fail the human tasks that your roles allow you to decide. | [Complete human tasks](human-tasks.md) | +| **Review Activities** | Approve, correct, or reject an activity before it runs or after it fails. | [Review activities](review-activities.md) | +| **Workflow Executions** | Browse executions, inspect the timeline and execution graph, start new workflows, and suspend, resume, cancel, or terminate a run. | [Workflow executions](executions.md) and [Start a workflow](start-workflow.md) | + +**My Tasks** and **Review Activities** carry a badge with the amount of work waiting for you. Both counts refresh every 30 seconds. The review badge shows `50+` when there are more pending reviews than one page can report. + +## How the console maps to your integrations + +Every runtime in a project registers with the same workflow namespace, and each integration identifies itself with a task queue that matches its ICP integration handle. Two things follow from this. + +- **One runtime answers for the whole project.** The project-level view does not call every integration. It reads through one running runtime and narrows the result by task queue. +- **Every row knows its owner.** A task, review, or execution carries the task queue it came from, so the console shows the owning integration in an **Integration** column and sends any action you take back to the right runtime. + +At project level you can narrow any list to a single integration with the **Integration** filter. At integration level the filter is not shown, because the view is already scoped to one task queue. + +:::note +Workflow management is enabled per runtime, not per integration type. If you enable it on a runtime whose integration is not typed as **Workflow**, its executions still appear in the project-level view, but the integration does not get its own **Workflows** item in the sidebar. +::: + +## Permissions + +Workflow management adds four permissions in the **Workflow-Management** domain. Assign them through roles and groups, the same way as every other ICP permission. See [Access control](../../manage/icp/access-control.md) for the model. + +| Permission | Allows | +| --- | --- | +| `workflow_mgt:view_human_tasks` | View human tasks. | +| `workflow_mgt:manage_human_tasks` | Complete and fail human tasks. | +| `workflow_mgt:view_workflows` | View workflow executions and review activities. | +| `workflow_mgt:manage_workflows` | Start, suspend, resume, cancel, and terminate executions, and decide review activities. | + +Each tab is gated on these permissions: + +- **My Tasks** requires `view_human_tasks` or `manage_human_tasks`. +- **Review Activities** and **Workflow Executions** require `view_workflows` or `manage_workflows`. + +If you hold none of them, the page reports that you do not have permission to view workflows. + +### Default role grants + +| Role | Human tasks | Workflow executions | +| --- | --- | --- | +| Super Admin, Admin, Project Admin | View and manage | View and manage | +| Developer | View and manage | View only | +| Viewer | View only | No access | + +:::warning Project-scope access +The project-level **Workflows** page checks permissions granted at project level or above. A user whose workflow permissions were granted only on individual integrations does not pass that check and must use the integration-level page instead. +::: + +## How roles decide who sees a task + +Permissions decide who can open the workflow views. **Roles** decide which tasks and reviews appear inside them. + +When you open a task list, ICP forwards your ICP role names to the runtime, and the runtime returns only the tasks whose eligible roles include one of them. Matching is an exact, case-sensitive comparison of role names, so a task declared for `manager` in the workflow code is visible only to users holding an ICP role named exactly `manager`. + +```ballerina +RequestDecision request = check ctx->awaitHumanTask("checkExpenseRequest", "manager", + payload = {"claimId": claim.claimId, "amount": claim.amount}); +``` + +Create the ICP roles your workflows name (`manager`, `finance`, `support-lead`, and so on), grant them the workflow permissions above, and map them to the groups whose members should decide those tasks. + +:::tip +Super admins are also given a synthetic `admin` role when calls reach the runtime. That role matches only tasks and reviews that explicitly declare `admin`, so it is not a way to see everything. +::: + +## What's next + +- [Connect a workflow runtime](connect-runtime.md) — register a runtime so its workflows appear in the console +- [Start a workflow](start-workflow.md) — launch a new execution from the console +- [Workflow executions](executions.md) — read the timeline, execution graph, and history of a run +- [Complete human tasks](human-tasks.md) — decide the tasks waiting on your roles +- [Review activities](review-activities.md) — approve, correct, or reject a gated or failed activity +- [Management API](../reference/management-api.md) — the REST API behind every view on this page diff --git a/en/docs/workflows/icp/review-activities.md b/en/docs/workflows/icp/review-activities.md new file mode 100644 index 0000000000..f1564da1c8 --- /dev/null +++ b/en/docs/workflows/icp/review-activities.md @@ -0,0 +1,96 @@ +--- +title: "Review Activities" +description: Approve, correct, or reject a gated or failed workflow activity from the WSO2 Integration Control Plane. +keywords: [wso2 integrator, integration control plane, icp, review activity, approval gate, retry review, proceed with input, reject] +sidebar_label: "Review Activities" +--- + +# Review Activities + +A review activity is a decision attached to an activity rather than a free-standing task. A workflow raises one in two situations: before a sensitive step runs, so a person can approve it, and after a step fails, so a person can decide whether to try again. Both land on the **Review Activities** tab, where you can see the arguments the step would run with, correct them, and let it proceed, or reject it and hand the outcome back to the workflow. + +For how workflows declare these gates, see [Error handling and review activities](../develop/review-activity-and-error-handling.md). + +:::info Prerequisites + +- A runtime registered with workflow management enabled ([Connect a workflow runtime](connect-runtime.md)) +- The `workflow_mgt:view_workflows` permission to browse reviews, and `workflow_mgt:manage_workflows` to decide them +::: + +## The two kinds of review + +| Trigger | Raised | What you are deciding | +| --- | --- | --- | +| Approval gate | Before the activity runs | Whether this call should be made at all, and with which arguments. | +| Rerun decision | After the activity failed | Whether to run it again, with the original or corrected arguments, or to let the failure reach the workflow. | + +The difference matters when you read the detail: an approval gate shows a proposed call, while a rerun decision also shows the error that the previous attempt produced. + +## Browse reviews + +Open **Workflows**, choose an environment, and go to the **Review Activities** tab. The tab badge counts the reviews waiting for a decision, and refreshes every 30 seconds. It reads `50+` when more are pending than a single page reports. + +| Column | Description | +| --- | --- | +| **Activity Name** | The activity being reviewed. | +| **Workflow Name** | The workflow type that raised the review. | +| **Integration** | The integration that owns it. Shown at project level only. | +| **Workflow ID** | The run that is waiting. Click it to open that run in [Workflow executions](executions.md). | +| **Status** | Pending, Completed, Canceled, or Terminated. A review that nobody decided before it timed out reports Failed. | +| **Started** | When the review was created. | +| **View** | Opens the review. | + +The list opens filtered to **Pending**, because that is the work waiting on you. Change the **Status** filter to look back at decided reviews. You can also filter by workflow name, by integration at project level, by a time range, and search by workflow ID. **Clear** resets everything back to Pending. + +:::note +Rejecting a review completes it. There is no separate rejected status, so a rejected review appears under **Completed** with its decision recorded. +::: + +## Decide a review + +Click the view icon to open the review. + +The dialog shows: + +- **Description**, the context the workflow supplied for the reviewer. +- **Activity Detail**, with the task name, workflow name, parent workflow ID, and creation time. For a rerun decision it also shows the **Error** from the failed attempt. +- **The arguments**, rendered as a form generated from the activity's parameters and pre-filled with the values the activity would run with. Where no form can be derived, the arguments are shown as JSON instead. + +You then have two decisions. + +### Proceed + +**Proceed** runs the activity with the values currently in the form. The fields are editable, so correcting the input and clicking **Proceed** is how you fix a bad argument before the step runs or after it failed. + +Leave the form untouched to run the step exactly as proposed. Field-level messages appear under any value that does not match the expected type, and a constraint the runtime rejects is reported in a banner at the top of the dialog, with your edits preserved. + +### Reject + +**Reject** skips the call. Enter an optional **Feedback** note, which is relayed to the workflow as the rejection reason, then click **Submit Rejection**. + +What rejection means to the run depends on the trigger. An approval gate that you reject does not make the call, and the workflow continues along its rejection path. A rerun decision that you reject surfaces the original failure to the workflow, which then handles it with its own error handling. + +:::warning +Only a pending review can be decided. Once a review is completed, canceled, or terminated, the dialog is read-only and the decision buttons are gone. The buttons are also hidden from users without `workflow_mgt:manage_workflows`. +::: + +## Who sees which review + +A review that declares roles is listed only for users holding one of them, matched by exact role name, in the same way as [human tasks](human-tasks.md#who-can-decide-a-task). + +Reviews raised by a failing activity are created without role restrictions, so they are visible to any user who can open the tab. To restrict them, set `reviewActivityAccessRole` in the runtime's `[ballerina.workflow.management]` configuration to the role a caller must hold: + +```toml +[ballerina.workflow.management] +enableManagementApi = true +reviewActivityAccessRole = "support-lead" +``` + +Reviews that declare their own roles always require a matching role, whatever this setting is. + +## What's next + +- [Error handling and review activities](../develop/review-activity-and-error-handling.md) — how a workflow declares approval gates and retry reviews +- [Complete human tasks](human-tasks.md) — free-standing decisions that a workflow waits on +- [Workflow executions](executions.md) — see the review in the run's timeline and execution graph +- [Management API](../reference/management-api.md) — decide reviews programmatically diff --git a/en/docs/workflows/icp/start-workflow.md b/en/docs/workflows/icp/start-workflow.md new file mode 100644 index 0000000000..40f195188a --- /dev/null +++ b/en/docs/workflows/icp/start-workflow.md @@ -0,0 +1,106 @@ +--- +title: "Start a Workflow" +description: Launch a new durable workflow execution from the WSO2 Integration Control Plane, using the input form generated from the workflow's input type. +keywords: [wso2 integrator, integration control plane, icp, start workflow, workflow input schema, workflow id, workflow timeout] +sidebar_label: "Start a Workflow" +--- + +# Start a Workflow + +Workflows usually start from your own integration logic, but during testing, onboarding, and day-to-day operations it is useful to launch one by hand. The Integration Control Plane can start any workflow the runtime advertises and builds the input form for you from the workflow's input type, so you do not have to hand-write JSON. + +:::info Prerequisites + +- A runtime registered with workflow management enabled ([Connect a workflow runtime](connect-runtime.md)) +- The `workflow_mgt:manage_workflows` permission on the project or integration +::: + +## Where to start a workflow + +Two places in the console open the same dialog. + +| From | Steps | +| --- | --- | +| The workflow list | Open **Workflows**, go to the **Workflow Executions** tab, and click **Start New Workflow**. | +| The integration overview | Open the integration, select a definition under **Workflow Definitions** on the environment card, and click **Start New Workflow**. The workflow is preselected. | + +**Start New Workflow** is visible only to users who hold `workflow_mgt:manage_workflows`. + +## Fill in the dialog + +| Field | Required | Description | +| --- | --- | --- | +| **Workflow name** | Yes | The workflow to run. At project level the list spans every integration in the project, and each option is labelled with the integration that hosts it. | +| Input fields | Depends on the workflow | Generated from the workflow's input type. Required fields carry a red asterisk. | +| **Workflow ID** | No | Your own identifier for the execution. Leave it empty to let the runtime generate one. A meaningful ID, such as an order number, makes the execution easy to find later. | +| **Timeout (seconds)** | No | Bounds the whole execution. The run times out with status **Timed out** if it has not finished when the timeout expires. | + +Click **Start** to launch the execution. + +:::note +The workflow you pick also decides where it runs. Each definition is reported by the runtime that hosts it, so choosing a workflow name selects that integration's runtime. There is no separate integration picker. +::: + +## How the input form is built + +Each workflow publishes a JSON schema for its input type, and the console renders a field per property. + +```ballerina +public type OrderInput record {| + string orderId; + decimal amount; + boolean expedited = false; + ShippingAddress address; +|}; + +@workflow:Workflow +function orderWorkflow(workflow:Context ctx, OrderInput input) returns OrderResult|error { +} +``` + +The record above produces a form with a text box for `orderId`, a numeric box for `amount`, a Yes/No toggle for `expedited`, and an indented group of fields for `address`. + +| Type in the workflow | Control in the console | +| --- | --- | +| `string` | Text box | +| Enum or union of string constants | Dropdown of the allowed values | +| `int`, `decimal`, `float` | Text box validated as a number, and rejected if it is not an integer where one is required | +| `boolean` | Yes/No toggle | +| Nested record | An indented group of that record's own fields | +| Open record, `map`, or array | Multi-line box where you enter JSON | + +An optional nested group that you leave completely empty is omitted from the input rather than reported as missing, so you only have to fill in the parts the run actually needs. + +If the workflow declares no input, or its schema cannot be turned into fields, the dialog shows the raw schema under **Click to see Input Schema**, or reports that no input schema is defined. + +:::tip +Design the input type for the people who launch the workflow. Enums become dropdowns, descriptions become helper text under the field, and a `title` on a property becomes the field label. +::: + +## After the workflow starts + +A confirmation dialog reports the workflow ID the execution was given, with three actions: + +- **Copy Workflow ID** puts the ID on your clipboard, which is useful for correlating logs. +- **View Running Workflow** opens the **Workflow Executions** tab filtered to that ID, so you can watch the run in the [timeline and execution graph](executions.md). +- **Close** returns to the list. + +Immediately after starting, the new execution appears in the workflow list with status **Running**. + +## When starting fails + +Errors are shown inside the dialog so that the values you typed are preserved. + +| Message | Cause | Fix | +| --- | --- | --- | +| A field-level message such as "Amount must be a number" | The value does not match the type the workflow declared | Correct the field and submit again | +| A red banner at the top of the dialog | The runtime rejected the input, usually because of a schema constraint the form does not pre-check, such as a pattern or a minimum | Adjust the input to satisfy the constraint | +| "Could not load workflow definitions from *integration*" | One integration in the project did not return its definitions | The workflows it hosts are missing from the dropdown. Check that its runtime is running with workflow management enabled. | +| "No running workflow runtime with a callback URL for this environment" | No runtime in that environment reported a workflow management URL | See [Troubleshooting](connect-runtime.md#troubleshooting) | + +## What's next + +- [Workflow executions](executions.md) — follow the run through its timeline and execution graph +- [Complete human tasks](human-tasks.md) — decide the tasks a running workflow is waiting on +- [Activities](../develop/activities.md) — the recorded units of work a workflow executes +- [Management API](../reference/management-api.md) — start workflows programmatically with `POST /workflows` diff --git a/en/docs/workflows/overview.md b/en/docs/workflows/overview.md new file mode 100644 index 0000000000..95d0e88d59 --- /dev/null +++ b/en/docs/workflows/overview.md @@ -0,0 +1,63 @@ +--- +title: Durable Workflows Overview +description: Build long-running, crash-safe business processes with WSO2 Integrator using durable workflows, human tasks, events, and durable AI agents. +keywords: [wso2 integrator, durable workflow, workflow, human task, agentic workflow, durable agent, temporal, long running, crash recovery] +sidebar_label: Overview +slug: /workflows/overview +--- + +# Durable Workflows + +Most integrations start simple and end up long-lived: an order needs a manager's approval, a claim waits days for supporting documents, a payment needs a retry after a gateway hiccup. A normal program loses everything when the process restarts — a **durable workflow does not**. + +WSO2 Integrator lets you design workflows that: + +- **Survive crashes and restarts** — every completed step is recorded, and the workflow resumes exactly where it left off. A finished step is never re-executed. +- **Wait for as long as it takes** — pause for hours, days, or months for a human decision or an external event, consuming no threads or memory while suspended. +- **Recover from failures** — retry failed steps automatically, or hand the failure to a human who can fix the input and retry. +- **Keep humans in the loop** — assign role-based tasks that people decide from the [Integration Control Plane](icp/managing-workflows.md) task inbox. + +## Two ways to build, one durable runtime + +| Durable Workflow | Durable Agentic Workflow | +| --- |----------------------------------------------------------------------| +| You wire the steps together in a visual flow | You describe the goal in natural language; an AI model decides the steps | +| Explicit, predictable path | Adapts to each request at runtime | +| Best for known, fixed business logic | Best for branchy, hard-to-enumerate logic | + +Both run on the same durable runtime, so an AI agent gets crash-safety, human tasks, timers, and retries for free. + +## Getting started + +- **[Build an Order Processing Workflow](getting-started/build-an-order-processing-workflow.md):** Your first durable workflow — it reserves inventory, waits for a payment confirmation, and then confirms or cancels the order. +- **[Build a Claim Handling Agent](getting-started/build-a-claim-workflow-agent.md):** A durable agentic workflow — an agent that validates expense claims and pays them out only after a manager approves. + +## Develop workflows + +- **[Create a workflow](develop/create-workflow.md):** Add the artifact, give it an input type, and design its steps on the diagram. +- **[Start a workflow](develop/start-workflow.md):** Launch a run from a service or an automation, and keep the ID that identifies it. +- **[Activities](develop/activities.md):** The recorded units of work in a workflow — exactly-once on replay and retryable on failure. +- **[Durable timers](develop/durable-timers.md):** Pause for hours, days, or months with a wait that survives restarts and holds no resources. +- **[Await data events](develop/data-events.md):** Wait until an external system or a person delivers the data the workflow needs, then resume with it. +- **[Send a data event](develop/send-data-event.md):** Deliver a value into a waiting run, using the workflow ID it was started with. +- **[Await human task](develop/human-task-workflow.md):** Pause for role-based human decisions and external data, for as long as it takes. +- **[Error handling and review activities](develop/review-activity-and-error-handling.md):** Approval gates before risky steps and human-reviewed retries after failures. +- **[Durable agentic workflows](develop/durable-agentic-workflow.md):** AI agents with durable activities, events, human tasks, and agent-to-agent collaboration. +- **[Prebuilt activities](develop/prebuilt-activities/index.md):** Durable REST, SOAP, and email calls with no wrapper to write. + +## Manage running workflows + +- **[Integration Control Plane](icp/managing-workflows.md):** Where workflow management lives in the console, and the permissions and roles that control each view. +- **[Connect a workflow runtime](icp/connect-runtime.md):** Register an integration so its workflows, tasks, and reviews appear in the console. +- **[Start a workflow](icp/start-workflow.md):** Launch an execution from a form generated out of the workflow's input type. +- **[Workflow executions](icp/executions.md):** Follow a run through its timeline, execution graph, and history, and suspend, resume, or terminate it. +- **[Complete human tasks](icp/human-tasks.md):** Decide the tasks a workflow is waiting on, from the task inbox. +- **[Review activities](icp/review-activities.md):** Approve, correct, or reject an activity before it runs or after it fails. + +## Tutorials + +- **[Tutorials](tutorials/overview.md):** Complete, step-by-step examples for each workflow feature. + +## Reference + +- **[Management API](reference/management-api.md):** The REST API behind the Control Plane — list instances, read execution graphs, and complete tasks programmatically. diff --git a/en/docs/workflows/reference/management-api.md b/en/docs/workflows/reference/management-api.md new file mode 100644 index 0000000000..d6167b5484 --- /dev/null +++ b/en/docs/workflows/reference/management-api.md @@ -0,0 +1,95 @@ +--- +sidebar_position: 1 +title: "Management API" +description: REST API reference for managing WSO2 Integrator durable workflows — instances, execution graphs, human tasks, and review activities. +keywords: [wso2 integrator, durable workflow, management api, rest, human task api, review activity api] +--- + +# Management API + +Every integration with durable workflows can expose a **Management API** — the same REST surface the [Integration Control Plane](../icp/managing-workflows.md) uses. Enable it to build custom portals, automations, or operational tooling. + +## Enable and configure + +```toml +[ballerina.workflow.management] +enableManagementApi = true +port = 8234 # default +enableApiKey = true # optional API-key protection +apiKeyValue = "" +apiKeyHeader = "x-api-key" +``` + +Base URL: `http://:8234/workflow` + +### Caller identity headers + +| Header | Purpose | +| --- | --- | +| `x-user-id` | Recorded in audit fields (`completedBy`, `decidedBy`). | +| `x-user-roles` | Comma-separated roles; tasks and reviews are filtered and authorized against them. | + +## Workflow instances + +| Method & path | Description | +| --- | --- | +| `GET /workflows` | List instances. Filters: `status` (`RUNNING`, `SUSPENDED`, `COMPLETED`, `FAILED`, …), `workflowType`, `workflowId` prefix, time bounds, pagination (`limit`, `pageToken`). | +| `GET /workflows/{workflowId}` | Instance detail: type, status, result, and activity invocations. | +| `GET /workflows/{workflowId}/history` | Full recorded event history. | +| `GET /workflows/{workflowId}/execution-graph` | Nodes and edges of the execution so far. Node types: `ACTIVITY`, `TIMER`, `DATA`, `HUMAN_TASK`, `REVIEW_ACTIVITY`. A `DATA` node with status `WAITING` marks a data event the workflow is currently blocked on. | +| `GET /workflows/{workflowId}/activity-tree` | The same execution as a tree of typed nodes with inputs, outputs, and attempts. | +| `POST /workflows/{workflowId}/suspend` | Pause the instance. | +| `POST /workflows/{workflowId}/resume` | Resume a suspended instance. | +| `POST /workflows/{workflowId}/terminate` | Stop immediately (no cleanup). | +| `POST /workflows/{workflowId}/cancel` | Request graceful cancellation. | +| `POST /workflows` | Start a workflow by type: `{"workflowType": "...", "input": {…}}`. | +| `GET /definitions` | The workflow types this integration registers, for launcher UIs. | + +Run-scoped variants exist for detail, control, history, activity-tree, and execution-graph: +append `/{runId}` (for example `GET /workflows/{workflowId}/{runId}/execution-graph`). + +### Example: find where an instance is halted + +```bash +curl -s http://localhost:8234/workflow/workflows//execution-graph \ + -H 'x-user-roles: manager' | jq '.nodes[] | select(.status=="WAITING" or .status=="RUNNING")' +``` + +## Human tasks + +| Method & path | Description | +| --- | --- | +| `GET /human-tasks` | List tasks; filters: `status` (`PENDING`, `COMPLETED`, …), `parentWorkflowId`, `taskName`, time bounds, pagination. Visibility is filtered by `x-user-roles`. | +| `GET /human-tasks/pending-count` | Pending-task count for the caller's roles (inbox badges). | +| `GET /human-tasks/{taskId}` | Task detail: title, description, payload, roles, and the decision form's JSON schema. | +| `POST /human-tasks/{taskId}/complete` | Complete with `{"result": {…}}` matching the task's decision type. | +| `POST /human-tasks/{taskId}/fail` | Fail the task with a reason. | + +```bash +curl -s -X POST http://localhost:8234/workflow/human-tasks//complete \ + -H 'Content-Type: application/json' -H 'x-user-id: alice' -H 'x-user-roles: manager' \ + -d '{"result": {"action": "REQUEST_BILL", "comment": "Please attach the receipts"}}' +``` + +## Review activities + +Approval gates (before a gated step runs) and retry reviews (after a step fails) share one surface: + +| Method & path | Description | +| --- | --- | +| `GET /review-activities` | List reviews; same filters and role-based visibility as human tasks. | +| `GET /review-activities/{taskId}` | Review detail: the activity, its (proposed or failing) input, the error for failure reviews, and the input form's JSON schema. | +| `POST /review-activities/{taskId}/proceed` | Run/rerun with the original input. | +| `POST /review-activities/{taskId}/proceed-with-input` | Run/rerun with corrected input: `{"input": {…}}`. | +| `POST /review-activities/{taskId}/reject` | Skip the gated call, or surface the failure to the workflow. | + +```bash +curl -s -X POST http://localhost:8234/workflow/review-activities//proceed-with-input \ + -H 'Content-Type: application/json' -H 'x-user-id: alice' -H 'x-user-roles: manager' \ + -d '{"input": {"claimId": "EXP-1", "amount": 180.50, "currency": "EUR"}}' +``` + +:::info Role-based visibility +A task or review that declares roles is only listed for — and decidable by — callers whose `x-user-roles` include one of them. Reviews created without roles can optionally be restricted with the `reviewActivityAccessRole` configuration. +::: + diff --git a/en/docs/workflows/tutorials/overview.md b/en/docs/workflows/tutorials/overview.md new file mode 100644 index 0000000000..9a1751306f --- /dev/null +++ b/en/docs/workflows/tutorials/overview.md @@ -0,0 +1,19 @@ +--- +sidebar_position: 1 +title: "Workflow Tutorials" +description: Complete, step-by-step durable workflow tutorials for WSO2 Integrator. +keywords: [wso2 integrator, durable workflow, tutorials] +sidebar_label: Overview +--- + +# Workflow Tutorials + +Complete, real examples — each tutorial builds a working integration step by step with screenshots and full code. + +- **[Build a Claim Handling Agent](../getting-started/build-a-claim-workflow-agent.md)** — the getting-started tutorial: a durable agent with a gated payment approved from the Control Plane. + +More tutorials are on the way, covering each feature in depth: + +- *Expense approval with two human reviews* — a traditional control-flow workflow with a data-event hand-off between reviews. +- *Payment retries with human review* — Auto Retry for transient failures and reviewer-corrected retries for bad input. +- *A multi-agent travel desk* — durable agents collaborating through synchronous and callback peers. diff --git a/en/sidebars.ts b/en/sidebars.ts index 68fab8aae8..4d8c5038e2 100644 --- a/en/sidebars.ts +++ b/en/sidebars.ts @@ -2351,6 +2351,77 @@ const sidebars: SidebarsConfig = { ], }, + // ───────────────────────────────────────────── + // DURABLE WORKFLOWS + // "How do I build long-running, crash-safe processes?" + // ───────────────────────────────────────────── + { + type: 'category', + label: 'Durable Workflows', + collapsed: true, + link: { type: 'doc', id: 'workflows/overview' }, + items: [ + // Getting Started + { + type: 'category', + label: 'Getting Started', + items: [ + 'workflows/getting-started/build-an-order-processing-workflow', + 'workflows/getting-started/build-a-claim-workflow-agent', + ], + }, + // Workflow Features + { + type: 'category', + label: 'Develop Workflows', + items: [ + 'workflows/develop/create-workflow', + 'workflows/develop/start-workflow', + 'workflows/develop/activities', + 'workflows/develop/data-events', + 'workflows/develop/send-data-event', + 'workflows/develop/human-task-workflow', + 'workflows/develop/review-activity-and-error-handling', + 'workflows/develop/durable-agentic-workflow', + 'workflows/develop/durable-timers', + { + type: 'category', + label: 'Prebuilt Activities', + link: { type: 'doc', id: 'workflows/develop/prebuilt-activities/index' }, + items: [ + 'workflows/develop/prebuilt-activities/call-rest-api', + 'workflows/develop/prebuilt-activities/call-soap-api', + 'workflows/develop/prebuilt-activities/send-email', + ], + }, + ], + }, + // Integration Control Plane + { + type: 'category', + label: 'Integration Control Plane', + link: { type: 'doc', id: 'workflows/icp/managing-workflows' }, + items: [ + 'workflows/icp/connect-runtime', + 'workflows/icp/start-workflow', + 'workflows/icp/executions', + 'workflows/icp/human-tasks', + 'workflows/icp/review-activities', + ], + }, + // Tutorials + 'workflows/tutorials/overview', + // API Reference + { + type: 'category', + label: 'API Reference', + items: [ + 'workflows/reference/management-api', + ], + }, + ], + }, + // ───────────────────────────────────────────── // TUTORIALS // "Show me a complete, real example" diff --git a/en/src/pages/index.tsx b/en/src/pages/index.tsx index a72fe13b65..86bee6eb16 100644 --- a/en/src/pages/index.tsx +++ b/en/src/pages/index.tsx @@ -50,6 +50,16 @@ function IconGenAI(): ReactNode { ); } +function IconWorkflows(): ReactNode { + return ( + + + + + + ); +} + function IconTutorials(): ReactNode { return ( @@ -144,6 +154,15 @@ const sections: SectionCard[] = [ iconBgDark: 'rgba(168, 85, 247, 0.15)', iconColor: '#A855F7', }, + { + title: 'Durable Workflows', + description: 'Build long-running, crash-safe processes with human tasks, timers, and retries.', + link: '/workflows/overview', + icon: , + iconBg: '#FFF1F2', + iconBgDark: 'rgba(225, 29, 72, 0.15)', + iconColor: '#E11D48', + }, { title: 'Guides', description: 'End-to-end tutorials and integration patterns.', diff --git a/en/static/img/workflows/develop/activities/activity-call.gif b/en/static/img/workflows/develop/activities/activity-call.gif new file mode 100644 index 0000000000..55c39136a5 Binary files /dev/null and b/en/static/img/workflows/develop/activities/activity-call.gif differ diff --git a/en/static/img/workflows/develop/activities/add-workflow-activity.png b/en/static/img/workflows/develop/activities/add-workflow-activity.png new file mode 100644 index 0000000000..cb9124ac7d Binary files /dev/null and b/en/static/img/workflows/develop/activities/add-workflow-activity.png differ diff --git a/en/static/img/workflows/develop/activities/register-activity-as-agent-activity.png b/en/static/img/workflows/develop/activities/register-activity-as-agent-activity.png new file mode 100644 index 0000000000..9f798d23da Binary files /dev/null and b/en/static/img/workflows/develop/activities/register-activity-as-agent-activity.png differ diff --git a/en/static/img/workflows/develop/activities/workflow-activities-dark.png b/en/static/img/workflows/develop/activities/workflow-activities-dark.png new file mode 100644 index 0000000000..2b4826f01b Binary files /dev/null and b/en/static/img/workflows/develop/activities/workflow-activities-dark.png differ diff --git a/en/static/img/workflows/develop/activities/workflow-activities-light.png b/en/static/img/workflows/develop/activities/workflow-activities-light.png new file mode 100644 index 0000000000..de1d58dfbb Binary files /dev/null and b/en/static/img/workflows/develop/activities/workflow-activities-light.png differ diff --git a/en/static/img/workflows/develop/activities/workflow-activity-form.png b/en/static/img/workflows/develop/activities/workflow-activity-form.png new file mode 100644 index 0000000000..3a5e7a6ef4 Binary files /dev/null and b/en/static/img/workflows/develop/activities/workflow-activity-form.png differ diff --git a/en/static/img/workflows/develop/create-workflow/add-artifact.png b/en/static/img/workflows/develop/create-workflow/add-artifact.png new file mode 100644 index 0000000000..3bda9fbb36 Binary files /dev/null and b/en/static/img/workflows/develop/create-workflow/add-artifact.png differ diff --git a/en/static/img/workflows/develop/create-workflow/create-workflow-form.png b/en/static/img/workflows/develop/create-workflow/create-workflow-form.png new file mode 100644 index 0000000000..8594af0c46 Binary files /dev/null and b/en/static/img/workflows/develop/create-workflow/create-workflow-form.png differ diff --git a/en/static/img/workflows/develop/data-events/await-data-event-dark.png b/en/static/img/workflows/develop/data-events/await-data-event-dark.png new file mode 100644 index 0000000000..7d343132d9 Binary files /dev/null and b/en/static/img/workflows/develop/data-events/await-data-event-dark.png differ diff --git a/en/static/img/workflows/develop/data-events/await-data-event-light.png b/en/static/img/workflows/develop/data-events/await-data-event-light.png new file mode 100644 index 0000000000..86df755d21 Binary files /dev/null and b/en/static/img/workflows/develop/data-events/await-data-event-light.png differ diff --git a/en/static/img/workflows/develop/data-events/await-data-event.gif b/en/static/img/workflows/develop/data-events/await-data-event.gif new file mode 100644 index 0000000000..14b6c61e70 Binary files /dev/null and b/en/static/img/workflows/develop/data-events/await-data-event.gif differ diff --git a/en/static/img/workflows/develop/durable-agentic-workflow/add-agent-artifact.png b/en/static/img/workflows/develop/durable-agentic-workflow/add-agent-artifact.png new file mode 100644 index 0000000000..f66247294b Binary files /dev/null and b/en/static/img/workflows/develop/durable-agentic-workflow/add-agent-artifact.png differ diff --git a/en/static/img/workflows/develop/durable-agentic-workflow/agent-model-dark.png b/en/static/img/workflows/develop/durable-agentic-workflow/agent-model-dark.png new file mode 100644 index 0000000000..db19f0c0bc Binary files /dev/null and b/en/static/img/workflows/develop/durable-agentic-workflow/agent-model-dark.png differ diff --git a/en/static/img/workflows/develop/durable-agentic-workflow/agent-model-light.png b/en/static/img/workflows/develop/durable-agentic-workflow/agent-model-light.png new file mode 100644 index 0000000000..eebcf33984 Binary files /dev/null and b/en/static/img/workflows/develop/durable-agentic-workflow/agent-model-light.png differ diff --git a/en/static/img/workflows/develop/durable-agentic-workflow/configure-agent.png b/en/static/img/workflows/develop/durable-agentic-workflow/configure-agent.png new file mode 100644 index 0000000000..5c2de04192 Binary files /dev/null and b/en/static/img/workflows/develop/durable-agentic-workflow/configure-agent.png differ diff --git a/en/static/img/workflows/develop/durable-agentic-workflow/register-activity.gif b/en/static/img/workflows/develop/durable-agentic-workflow/register-activity.gif new file mode 100644 index 0000000000..9797023141 Binary files /dev/null and b/en/static/img/workflows/develop/durable-agentic-workflow/register-activity.gif differ diff --git a/en/static/img/workflows/develop/durable-agentic-workflow/register-data-event.gif b/en/static/img/workflows/develop/durable-agentic-workflow/register-data-event.gif new file mode 100644 index 0000000000..e2c6f9483c Binary files /dev/null and b/en/static/img/workflows/develop/durable-agentic-workflow/register-data-event.gif differ diff --git a/en/static/img/workflows/develop/human-task-workflow/await-human-task-dark.png b/en/static/img/workflows/develop/human-task-workflow/await-human-task-dark.png new file mode 100644 index 0000000000..7f40aa829a Binary files /dev/null and b/en/static/img/workflows/develop/human-task-workflow/await-human-task-dark.png differ diff --git a/en/static/img/workflows/develop/human-task-workflow/await-human-task-light.png b/en/static/img/workflows/develop/human-task-workflow/await-human-task-light.png new file mode 100644 index 0000000000..0a8f16d5e2 Binary files /dev/null and b/en/static/img/workflows/develop/human-task-workflow/await-human-task-light.png differ diff --git a/en/static/img/workflows/develop/human-task-workflow/await-human-task.gif b/en/static/img/workflows/develop/human-task-workflow/await-human-task.gif new file mode 100644 index 0000000000..357d3eedd9 Binary files /dev/null and b/en/static/img/workflows/develop/human-task-workflow/await-human-task.gif differ diff --git a/en/static/img/workflows/develop/review-activity/auto-retry.png b/en/static/img/workflows/develop/review-activity/auto-retry.png new file mode 100644 index 0000000000..d67c4f8c00 Binary files /dev/null and b/en/static/img/workflows/develop/review-activity/auto-retry.png differ diff --git a/en/static/img/workflows/develop/review-activity/check-error-result.png b/en/static/img/workflows/develop/review-activity/check-error-result.png new file mode 100644 index 0000000000..2050c9650d Binary files /dev/null and b/en/static/img/workflows/develop/review-activity/check-error-result.png differ diff --git a/en/static/img/workflows/develop/review-activity/error-handling-dark.png b/en/static/img/workflows/develop/review-activity/error-handling-dark.png new file mode 100644 index 0000000000..30cae7b7d5 Binary files /dev/null and b/en/static/img/workflows/develop/review-activity/error-handling-dark.png differ diff --git a/en/static/img/workflows/develop/review-activity/error-handling-light.png b/en/static/img/workflows/develop/review-activity/error-handling-light.png new file mode 100644 index 0000000000..91a69f78af Binary files /dev/null and b/en/static/img/workflows/develop/review-activity/error-handling-light.png differ diff --git a/en/static/img/workflows/develop/review-activity/handle-error-in-logic.gif b/en/static/img/workflows/develop/review-activity/handle-error-in-logic.gif new file mode 100644 index 0000000000..b4f9c0f55f Binary files /dev/null and b/en/static/img/workflows/develop/review-activity/handle-error-in-logic.gif differ diff --git a/en/static/img/workflows/develop/review-activity/human-review.png b/en/static/img/workflows/develop/review-activity/human-review.png new file mode 100644 index 0000000000..ade0cce5fa Binary files /dev/null and b/en/static/img/workflows/develop/review-activity/human-review.png differ diff --git a/en/static/img/workflows/develop/review-activity/requires-approval.png b/en/static/img/workflows/develop/review-activity/requires-approval.png new file mode 100644 index 0000000000..0d02ec160c Binary files /dev/null and b/en/static/img/workflows/develop/review-activity/requires-approval.png differ diff --git a/en/static/img/workflows/develop/review-activity/retry-policy.png b/en/static/img/workflows/develop/review-activity/retry-policy.png new file mode 100644 index 0000000000..c73ca923e0 Binary files /dev/null and b/en/static/img/workflows/develop/review-activity/retry-policy.png differ diff --git a/en/static/img/workflows/develop/send-data-event/add-send-data-event.gif b/en/static/img/workflows/develop/send-data-event/add-send-data-event.gif new file mode 100644 index 0000000000..47e8750d67 Binary files /dev/null and b/en/static/img/workflows/develop/send-data-event/add-send-data-event.gif differ diff --git a/en/static/img/workflows/develop/send-data-event/send-data-event-dark.png b/en/static/img/workflows/develop/send-data-event/send-data-event-dark.png new file mode 100644 index 0000000000..d7b5a76403 Binary files /dev/null and b/en/static/img/workflows/develop/send-data-event/send-data-event-dark.png differ diff --git a/en/static/img/workflows/develop/send-data-event/send-data-event-light.png b/en/static/img/workflows/develop/send-data-event/send-data-event-light.png new file mode 100644 index 0000000000..02e7674ed9 Binary files /dev/null and b/en/static/img/workflows/develop/send-data-event/send-data-event-light.png differ diff --git a/en/static/img/workflows/develop/start-workflow/add-run-workflow.gif b/en/static/img/workflows/develop/start-workflow/add-run-workflow.gif new file mode 100644 index 0000000000..e0de943177 Binary files /dev/null and b/en/static/img/workflows/develop/start-workflow/add-run-workflow.gif differ diff --git a/en/static/img/workflows/develop/start-workflow/run-workflow-dark.png b/en/static/img/workflows/develop/start-workflow/run-workflow-dark.png new file mode 100644 index 0000000000..47cc8590b5 Binary files /dev/null and b/en/static/img/workflows/develop/start-workflow/run-workflow-dark.png differ diff --git a/en/static/img/workflows/develop/start-workflow/run-workflow-light.png b/en/static/img/workflows/develop/start-workflow/run-workflow-light.png new file mode 100644 index 0000000000..001c0a6ccf Binary files /dev/null and b/en/static/img/workflows/develop/start-workflow/run-workflow-light.png differ diff --git a/en/static/img/workflows/develop/start-workflow/run-workflow-node.png b/en/static/img/workflows/develop/start-workflow/run-workflow-node.png new file mode 100644 index 0000000000..07a304411b Binary files /dev/null and b/en/static/img/workflows/develop/start-workflow/run-workflow-node.png differ diff --git a/en/static/img/workflows/getting-started/build-a-claim-workflow-agent/agent-role-and-instructions.png b/en/static/img/workflows/getting-started/build-a-claim-workflow-agent/agent-role-and-instructions.png new file mode 100644 index 0000000000..38e10b0598 Binary files /dev/null and b/en/static/img/workflows/getting-started/build-a-claim-workflow-agent/agent-role-and-instructions.png differ diff --git a/en/static/img/workflows/getting-started/build-a-claim-workflow-agent/create-agent.png b/en/static/img/workflows/getting-started/build-a-claim-workflow-agent/create-agent.png new file mode 100644 index 0000000000..efe0163a6a Binary files /dev/null and b/en/static/img/workflows/getting-started/build-a-claim-workflow-agent/create-agent.png differ diff --git a/en/static/img/workflows/getting-started/build-a-claim-workflow-agent/create-expense-claim-type.png b/en/static/img/workflows/getting-started/build-a-claim-workflow-agent/create-expense-claim-type.png new file mode 100644 index 0000000000..bff3552fd9 Binary files /dev/null and b/en/static/img/workflows/getting-started/build-a-claim-workflow-agent/create-expense-claim-type.png differ diff --git a/en/static/img/workflows/getting-started/build-a-claim-workflow-agent/validate-claim-body.gif b/en/static/img/workflows/getting-started/build-a-claim-workflow-agent/validate-claim-body.gif new file mode 100644 index 0000000000..179a355388 Binary files /dev/null and b/en/static/img/workflows/getting-started/build-a-claim-workflow-agent/validate-claim-body.gif differ diff --git a/en/static/img/workflows/getting-started/build-an-order-processing-workflow/add-activity.gif b/en/static/img/workflows/getting-started/build-an-order-processing-workflow/add-activity.gif new file mode 100644 index 0000000000..731d7d445d Binary files /dev/null and b/en/static/img/workflows/getting-started/build-an-order-processing-workflow/add-activity.gif differ diff --git a/en/static/img/workflows/getting-started/build-an-order-processing-workflow/await-data-event.gif b/en/static/img/workflows/getting-started/build-an-order-processing-workflow/await-data-event.gif new file mode 100644 index 0000000000..fa7fadc066 Binary files /dev/null and b/en/static/img/workflows/getting-started/build-an-order-processing-workflow/await-data-event.gif differ diff --git a/en/static/img/workflows/getting-started/build-an-order-processing-workflow/branch-on-payment.gif b/en/static/img/workflows/getting-started/build-an-order-processing-workflow/branch-on-payment.gif new file mode 100644 index 0000000000..20642f4208 Binary files /dev/null and b/en/static/img/workflows/getting-started/build-an-order-processing-workflow/branch-on-payment.gif differ diff --git a/en/static/img/workflows/getting-started/build-an-order-processing-workflow/cancel-order.gif b/en/static/img/workflows/getting-started/build-an-order-processing-workflow/cancel-order.gif new file mode 100644 index 0000000000..041afef059 Binary files /dev/null and b/en/static/img/workflows/getting-started/build-an-order-processing-workflow/cancel-order.gif differ diff --git a/en/static/img/workflows/getting-started/build-an-order-processing-workflow/completed-workflow-dark.png b/en/static/img/workflows/getting-started/build-an-order-processing-workflow/completed-workflow-dark.png new file mode 100644 index 0000000000..3ff5edf5fd Binary files /dev/null and b/en/static/img/workflows/getting-started/build-an-order-processing-workflow/completed-workflow-dark.png differ diff --git a/en/static/img/workflows/getting-started/build-an-order-processing-workflow/completed-workflow-light.png b/en/static/img/workflows/getting-started/build-an-order-processing-workflow/completed-workflow-light.png new file mode 100644 index 0000000000..a93eafd437 Binary files /dev/null and b/en/static/img/workflows/getting-started/build-an-order-processing-workflow/completed-workflow-light.png differ diff --git a/en/static/img/workflows/getting-started/build-an-order-processing-workflow/create-workflow.gif b/en/static/img/workflows/getting-started/build-an-order-processing-workflow/create-workflow.gif new file mode 100644 index 0000000000..9ae6aff5cd Binary files /dev/null and b/en/static/img/workflows/getting-started/build-an-order-processing-workflow/create-workflow.gif differ diff --git a/en/static/img/workflows/getting-started/build-an-order-processing-workflow/send-data-event.gif b/en/static/img/workflows/getting-started/build-an-order-processing-workflow/send-data-event.gif new file mode 100644 index 0000000000..e1d51bed75 Binary files /dev/null and b/en/static/img/workflows/getting-started/build-an-order-processing-workflow/send-data-event.gif differ diff --git a/en/static/img/workflows/getting-started/build-an-order-processing-workflow/send-email.gif b/en/static/img/workflows/getting-started/build-an-order-processing-workflow/send-email.gif new file mode 100644 index 0000000000..ad2a05c04a Binary files /dev/null and b/en/static/img/workflows/getting-started/build-an-order-processing-workflow/send-email.gif differ diff --git a/en/static/img/workflows/getting-started/build-an-order-processing-workflow/set-in-memory-mode.gif b/en/static/img/workflows/getting-started/build-an-order-processing-workflow/set-in-memory-mode.gif new file mode 100644 index 0000000000..974570ba0b Binary files /dev/null and b/en/static/img/workflows/getting-started/build-an-order-processing-workflow/set-in-memory-mode.gif differ diff --git a/en/static/img/workflows/getting-started/build-an-order-processing-workflow/start-workflow.gif b/en/static/img/workflows/getting-started/build-an-order-processing-workflow/start-workflow.gif new file mode 100644 index 0000000000..684fc1a2d3 Binary files /dev/null and b/en/static/img/workflows/getting-started/build-an-order-processing-workflow/start-workflow.gif differ