# Workflow applications

A **workflow application** is an application — with its own [ID](https://docs.diagrid.io/concepts/identities) — that contains workflow code. It is an ordinary Catalyst application that additionally runs durable orchestrations, so it inherits the same identity, policies, and observability as any other workload while gaining durable execution.

Catalyst Workflows are **long-running, stateful orchestrations** with durable state that **recover automatically** from failures.
When a process restarts, an activity fails, or a tool call times out, the workflow **resumes from the exact point of failure** instead of starting over — no partial work is repeated and no in-flight state is lost.

They are built on the open-source [Dapr Workflow API](https://docs.dapr.io/developing-applications/building-blocks/workflow/workflow-overview/), using [the same programming model](#programming-model) — your existing Dapr workflow code runs on Catalyst unchanged.
This API offers durable state, automatic retries policies, timers, external events handling, and activity fan-out/fan-in. Author workflows in **Python, JavaScript, .NET, Java, or Go** with the same orchestrator semantics across all SDKs.

With Catalyst Workflows you can:

- **Coordinate parallel work** and aggregate results without managing concurrency limits.
- **Schedule durable timers** that survive process restarts — for polling loops or delayed steps.
- **Pause for human approvals or external events** for seconds, days, or weeks.
- **Resume business processes from the exact point of failure** across crashes, deploys, and region failovers.
- **Run [AI agents](https://docs.diagrid.io/concepts/ai-agents)** whose reasoning steps, tool calls, and decisions all persist and replay.

## What Catalyst adds over Dapr Workflows

Dapr Workflow gives you the authoring model and the runtime contract. Catalyst runs that contract as a managed service and adds the operational tooling around it:

- **Managed workflow runtime** — no backing store to provision or scale. Workflow history is persisted in Catalyst's managed store.
- **Workflow operations dashboard** — a unified view of every execution across a project: status, app, and workflow name. Drill into any execution to inspect its execution graph, step-level history, inputs, and outputs. See [Operate workflows](https://docs.diagrid.io/operate/project-operations/workflows).
- **Execution control from the console and CLI** — pause, resume, rerun, terminate, raise event, and purge instances from the UI or the [`diagrid workflow`](https://docs.diagrid.io/references/catalyst/cli-reference/workflow) CLI.
- **Workflow Composer** — an AI-powered service that turns BPMN-style diagrams into runnable Dapr Workflow projects in your language. See [Workflow Composer](https://docs.diagrid.io/develop/workflows/workflow-composer).
- **Dapr Dev Dashboard** — inspect workflow executions, inputs, outputs, and history during local development. See [Dapr Dev Dashboard](https://docs.diagrid.io/develop/local-development/dev-dashboard).
- **Verifiable execution history** — optionally sign every recorded step so a workflow's history is tamper-evident and independently verifiable. See [Tamper-evident attestation](#tamper-evident-attestation).

## Programming model

A workflow is **orchestration code** that schedules work and waits on results. The orchestration itself is deterministic and side-effect free — all side effects happen in activities. The runtime replays the orchestration on restart, using the recorded history to reconstruct in-memory state.

- **Workflow** — the top-level orchestration function. It runs to completion (or remains durably suspended) across process restarts. Inputs and outputs are serialized to the backing store at every yield point.
- **Activity** — a unit of work that performs a side effect: a database write, an HTTP call, an LLM invocation. Activities run at-least-once and are retried by the runtime according to the workflow's retry policy.
- **Child workflow** — a workflow invoked from another workflow. Use child workflows to compose larger orchestrations from reusable sub-processes, isolate retry boundaries, or fan out work across application instances.
- **Durable timer** — a sleep that survives process restarts. Use timers for scheduled steps, polling loops, and approval timeouts ranging from seconds to days.
- **External event** — a named signal a workflow waits on. The workflow remains durably suspended until the event is raised — by another service, by the CLI, or from the console.

```mermaid
flowchart LR
  START((Start)):::startNode
  ACT1(Activity 1)
  ACT2(Activity 2)
  subgraph CHILD[Child workflow]
    CSTART((Start)):::startNode
    CACT1(Activity A)
    CACT2(Activity B)
    CEND((End)):::endNode
    CSTART-->CACT1
    CACT1-->CACT2
    CACT2-->CEND
  end
  ACT3(Activity 3)
  END((End)):::endNode
  START-->ACT1
  ACT1-->ACT2
  ACT2-->CSTART
  CEND-->ACT3
  ACT3-->END

  classDef startNode stroke:#22613f,stroke-width:3px
  classDef endNode stroke:#8b1a1a,stroke-width:3px
```

The same primitives are available across the [.NET, Go, Java, JavaScript, and Python SDKs](https://docs.diagrid.io/develop/workflows).

## Lifecycle states

Every workflow execution moves through a small set of well-defined states. Three are **terminal** — once entered, the instance never transitions back. The runtime guarantees state transitions are atomic and durable: a state you observe is a state the runtime has already persisted.

```mermaid
flowchart LR
  START((Start)):::startNode
  subgraph ACTIVE[Active states]
    direction TB
    RUN(&nbsp;RUNNING&nbsp;&nbsp;)
    SUSP(SUSPENDED&nbsp;)
    RUN--"pause"-->SUSP
    SUSP--"resume"-->RUN
  end
  subgraph TERMINAL[Terminal states]
    direction TB
    COMP((COMPLETED&nbsp;)):::endNode
    FAIL((&nbsp;&nbsp;FAILED&nbsp;&nbsp;)):::endNode
    TERM((TERMINATED)):::endNode
  end

  START-->RUN
  RUN--"orchestrator returns"-->COMP
  RUN--"unhandled error"-->FAIL
  RUN--"terminate"-->TERM
  SUSP--"terminate"-->TERM

  classDef startNode stroke:#22613f,stroke-width:3px
  classDef endNode stroke:#8b1a1a,stroke-width:3px
```

- **RUNNING** — the instance is actively executing, awaiting an activity, a timer, or an external event. Most long-running workflows spend almost all their wall-clock time in this state while durably suspended on a timer or event.
- **SUSPENDED** — the instance has been paused by an operator. The runtime stops dispatching new work for it; in-flight activities still run to completion and their results are recorded, but no new activities are scheduled until it is resumed.
- **COMPLETED** — the orchestrator function returned a value. The output is durably stored and available to callers.
- **FAILED** — the orchestrator raised an unhandled exception. Activity-level failures that are caught and handled in orchestrator code do *not* surface as `FAILED` — only an exception that propagates out of the workflow itself.
- **TERMINATED** — an operator explicitly ended the instance before completion. The orchestrator was not given a chance to clean up; any in-flight activity may still complete but its result is discarded.

After reaching a terminal state, the instance's history is retained until it is purged. See [Manage workflow instances](https://docs.diagrid.io/operate/project-operations/workflows) for runtime operations against each state.

## Tamper-evident attestation

A workflow's history is the durable record of everything that ran — every activity call with its input and output, every timer, and every external event. On a project created with **workflow history signing** enabled, Catalyst cryptographically signs that history as it is written, so the record cannot be altered after the fact without detection. The result is a provable, tamper-evident audit trail of exactly what a workflow did — the property InfoSec and compliance teams need for regulated processes.

Signing is a project-level choice made **at creation time** and cannot be changed later. Enable it with `diagrid project create --enable-workflow-history-signing`, or with the **Workflow history signing** toggle when creating the project in the Catalyst console — see [Enable verifiable execution](https://docs.diagrid.io/operate/platform-operations/projects#enable-verifiable-execution), including how to [configure the workflow state store](https://docs.diagrid.io/operate/platform-operations/projects#configure-the-workflow-state-store) that holds the signed histories.

History is signed in **blocks**: each block covers a contiguous range of events and is signed with the [SPIFFE identity](https://docs.diagrid.io/concepts/security#identity-and-mtls) of the workflow application that produced it. Blocks are chained, so the signature over one block depends on the block before it — changing any recorded event breaks the chain from that point on. Whenever an instance is read, Catalyst re-verifies the history against the signatures and reports a verdict, preserving the identity that signed each block so you can see *which workload* produced a given stretch of history, not just that it is intact. Every read reports one of four statuses:

| Status | Meaning |
| --- | --- |
| **`verified`** | The signature is valid and its certificate chains to the trusted certificate authority. The history is intact. |
| **`tampered`** | Verification failed — the stored history no longer matches its signature. Treat this as a security incident. |
| **`unsigned`** | No signature covers the event or instance: signing is not enabled on the project, or the newest events of a running instance have not been sealed into a signature block yet. |
| **`unverified`** | The check could not run — for example, the trust anchor was unavailable. The reason is reported alongside the status. This is *not* evidence of tampering; re-check once the condition clears. |

To read the verdict Catalyst reports for an instance — and export signed proof an auditor can verify independently of Catalyst — see [Verify workflow execution history](https://docs.diagrid.io/operate/project-operations/verify-workflow-history). The same verdicts are shown visually in the console; see [Verify execution history in the console](https://docs.diagrid.io/operate/project-operations/workflows#verify-execution-history).

## Workflow vs saga

There are two common ways to coordinate multi-step work: sagas and workflows. They offer different properties when it comes to failure recovery and visibility.

A **workflow** centralizes the orchestration in code: branching, retries, timeouts, and compensation all live in one function you can read top-to-bottom.

A **saga** pushes that logic out to the participating services making ot harder to reason about it.

You can still implement the **Saga pattern** *inside* a workflow: model each step as an activity and add a compensating activity that runs on failure. The workflow gives you the durable state and central timeline a hand-rolled saga lacks. See [Compensation in workflow patterns](https://docs.diagrid.io/develop/workflows/patterns).

| Approach | State | Failure recovery | Visibility | Best for |
| --- | --- | --- | --- | --- |
| **Saga** (event-driven choreography) | Spread across services and topics | Each service handles its own retries and compensations | Distributed traces. Must instrument all services | Loosely coupled services that already communicate by events |
| **Workflow** (orchestration) | Persisted by the runtime | Replays from the last checkpoint after any restart | Execution graph, step history, inputs and outputs | Multi-step business processes, agents |

## Next steps

For development concepts, tools, and language specific guides see [Develop workflows](https://docs.diagrid.io/develop/workflows).

For inspecting running workflows in the console, see [Operate workflows](https://docs.diagrid.io/operate/project-operations/workflows).

**Dapr & AI University — See it in action:** [Dapr Workflow: Use durable execution to build reliable distributed applications](https://www.diagrid.io/university/dapr-workflow?utm_source=docs&utm_medium=cta&utm_campaign=dapr-workflow)

Build reliable distributed applications with durable execution: task chaining, fan-out/fan-in, and error handling.

Intermediate · 50 min · .NET, Java, Python
