> ## Documentation Index
> Fetch the complete documentation index at: https://docs.agentfront.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Workflows

> Long-running multi-step workflows with state, retries, and resumability

Workflows are **DAG-based multi-step pipelines** that compose [Jobs](/frontmcp/servers/jobs) with dependencies, conditional execution, and parallel step processing. They provide orchestration for complex operations that span multiple jobs.

<Info>
  Workflows execute jobs in dependency order with automatic cycle detection, parallel batching, and per-step error handling.
</Info>

<Tip>
  **Nx users:** Scaffold with `nx g @frontmcp/nx:workflow my-workflow --project my-app`. See [Workflow Generator](/frontmcp/nx-plugin/generators/workflow).
</Tip>

## Why Workflows?

Workflows handle orchestration that individual jobs cannot:

| Aspect | Job | Workflow | Skill |
| - | - | - | - |
| **Purpose** | Single unit of work | Multi-step pipeline | AI instruction guide |
| **Composition** | Standalone | Chains multiple jobs | References tools |
| **Dependencies** | None | DAG with `dependsOn` | None |
| **Parallelism** | Single execution | Bounded concurrent steps | None |
| **Triggers** | On-demand | Manual, webhook, or event | On-demand |

Workflows are ideal for:

* **Multi-step data pipelines** — extract, transform, load sequences
* **Approval chains** — sequential steps with conditional logic
* **Parallel processing** — fan-out/fan-in patterns with bounded concurrency
* **Event-driven automation** — webhook-triggered multi-job flows

***

## Creating Workflows

Workflows are defined declaratively using the `@Workflow` decorator with a `steps` array:

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
import { Workflow } from '@frontmcp/sdk';

@Workflow({
  name: 'greet-and-analyze',
  description: 'Greet a user then analyze the greeting',
  trigger: 'manual',
  steps: [
    {
      id: 'greet',
      jobName: 'greet',
      input: { name: 'World', formal: false },
    },
    {
      id: 'analyze',
      jobName: 'analyze-text',
      dependsOn: ['greet'],
      input: (steps) => ({
        text: steps.get('greet').outputs.message,
        language: 'en',
      }),
    },
  ],
})
class GreetAndAnalyzeWorkflow {}
```

### Function Style

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
import { workflow } from '@frontmcp/sdk';

const GreetAndAnalyzeWorkflow = workflow({
  name: 'greet-and-analyze',
  description: 'Greet a user then analyze the greeting',
  steps: [
    {
      id: 'greet',
      jobName: 'greet',
      input: { name: 'World' },
    },
    {
      id: 'analyze',
      jobName: 'analyze-text',
      dependsOn: ['greet'],
      input: (steps) => ({
        text: steps.get('greet').outputs.message,
      }),
    },
  ],
});
```

***

## Registering Workflows

Add workflows to your app via the `workflows` array:

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
import { App } from '@frontmcp/sdk';

@App({
  id: 'text-processing',
  name: 'Text Processing',
  jobs: [GreetJob, AnalyzeTextJob],
  workflows: [GreetAndAnalyzeWorkflow],
})
class TextProcessingApp {}
```

<Note>
  Jobs referenced by workflow steps must be registered in the same app or available in the scope's job registry.
</Note>

***

## Workflow Steps

Each step defines a job to execute and how it connects to other steps:

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
interface WorkflowStep {
  id: string;           // Unique step identifier
  jobName: string;      // Reference to a registered job
  dependsOn?: string[]; // Step IDs that must complete first
  input?: Record<string, unknown> | ((steps) => Record<string, unknown>);
  condition?: (steps) => boolean;
  continueOnError?: boolean;
  timeout?: number;     // Per-step timeout override (ms)
  retry?: JobRetryConfig; // Per-step retry override
}
```

| Field | Type | Default | Description |
| - | - | - | - |
| `id` | `string` | — | **Required.** Unique step identifier |
| `jobName` | `string` | — | **Required.** Name of the job to execute |
| `dependsOn` | `string[]` | `[]` | Step IDs that must complete before this step runs |
| `input` | `object \| function` | Workflow input | Static input or dynamic callback from previous step outputs |
| `condition` | `function` | — | Skip step if returns `false` |
| `continueOnError` | `boolean` | `false` | Continue workflow if this step fails |
| `timeout` | `number` | Job default | Per-step timeout override in ms |
| `retry` | `JobRetryConfig` | Job default | Per-step retry override |

***

## Step Dependencies

Steps declare dependencies using `dependsOn`. The engine validates the DAG before execution:

* **Duplicate ID detection** — no two steps can share an ID
* **Missing reference detection** — `dependsOn` must reference existing step IDs
* **Cycle detection** — DFS-based cycle detection prevents infinite loops

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
steps: [
  { id: 'fetch', jobName: 'fetch-data' },
  { id: 'validate', jobName: 'validate-data', dependsOn: ['fetch'] },
  { id: 'transform', jobName: 'transform-data', dependsOn: ['validate'] },
  { id: 'load', jobName: 'load-data', dependsOn: ['transform'] },
]
```

***

## Dynamic Input

Steps can compute their input dynamically based on previous step outputs:

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
steps: [
  {
    id: 'fetch-users',
    jobName: 'fetch-users',
    input: { limit: 100 },
  },
  {
    id: 'enrich',
    jobName: 'enrich-users',
    dependsOn: ['fetch-users'],
    input: (steps) => ({
      users: steps.get('fetch-users').outputs.users,
      source: 'crm',
    }),
  },
]
```

The `steps` context provides a `get(stepId)` method that returns `WorkflowStepResult`:

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
interface WorkflowStepResult {
  outputs: Record<string, unknown>;
  state: 'completed' | 'failed' | 'skipped';
}
```

***

## Conditional Steps

Steps can be conditionally skipped based on previous step results:

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
steps: [
  {
    id: 'check-eligibility',
    jobName: 'check-eligibility',
  },
  {
    id: 'send-notification',
    jobName: 'send-notification',
    dependsOn: ['check-eligibility'],
    condition: (steps) =>
      steps.get('check-eligibility').outputs.eligible === true,
    input: (steps) => ({
      userId: steps.get('check-eligibility').outputs.userId,
    }),
  },
]
```

When a condition returns `false`, the step is marked as `skipped` and downstream steps that depend on it will still execute (skipped steps are treated as completed for dependency resolution).

***

## Parallel Execution

Steps with no mutual dependencies execute in parallel, bounded by `maxConcurrency`:

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
@Workflow({
  name: 'parallel-analysis',
  maxConcurrency: 3, // Run up to 3 steps concurrently
  steps: [
    { id: 'fetch', jobName: 'fetch-data' },
    // These three run in parallel after 'fetch' completes
    { id: 'analyze-sentiment', jobName: 'sentiment', dependsOn: ['fetch'] },
    { id: 'analyze-entities', jobName: 'entities', dependsOn: ['fetch'] },
    { id: 'analyze-topics', jobName: 'topics', dependsOn: ['fetch'] },
    // This runs after all three analyses complete
    {
      id: 'aggregate',
      jobName: 'aggregate-results',
      dependsOn: ['analyze-sentiment', 'analyze-entities', 'analyze-topics'],
    },
  ],
})
```

***

## Configuration

| Field | Type | Default | Description |
| - | - | - | - |
| `name` | `string` | — | **Required.** Unique workflow identifier |
| `description` | `string` | — | Human-readable description |
| `steps` | `WorkflowStep[]` | — | **Required.** Ordered step definitions (min 1) |
| `trigger` | `'manual' \| 'webhook' \| 'event'` | `'manual'` | How the workflow is triggered |
| `webhook` | `WorkflowWebhookConfig` | — | Webhook configuration (when trigger is `'webhook'`) |
| `timeout` | `number` | `600000` | Maximum total execution time in ms (10 min) |
| `maxConcurrency` | `number` | `5` | Maximum parallel step concurrency |
| `id` | `string` | `name` | Stable identifier for tracking |
| `tags` | `string[]` | — | Categorization tags |
| `labels` | `Record<string, string>` | — | Fine-grained key-value labels |
| `hideFromDiscovery` | `boolean` | `false` | Hide from `list_workflows` |
| `permissions` | `JobPermission[]` | — | RBAC permission rules (same shape and semantics as [job permissions](/frontmcp/servers/jobs#permissions)) |
| `inputSchema` | `ZodShape` | — | Workflow-level input schema |
| `outputSchema` | `ZodShape` | — | Workflow-level output schema |

### Webhook Configuration

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
interface WorkflowWebhookConfig {
  path?: string;          // Custom path (default: /workflows/webhook/{name})
  secret?: string;        // Webhook secret for validation
  methods?: ('GET' | 'POST')[]; // Allowed HTTP methods (default: ['POST'])
}
```

***

## Triggers

| Trigger | Description |
| - | - |
| `manual` | Triggered via `execute_workflow` tool or DirectClient |
| `webhook` | Triggered by HTTP webhook with optional secret validation |
| `event` | Triggered by internal events |

***

## Execution Flow

```mermaid theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
graph TD
    START["Start Workflow"] --> VALIDATE["Validate DAG"]
    VALIDATE --> FIND["Find Ready Steps"]
    FIND --> COND{"Any ready?"}
    COND -->|Yes| EVAL["Evaluate Conditions"]
    EVAL --> INPUT["Compute Inputs"]
    INPUT --> EXEC["Execute Batch (max N)"]
    EXEC --> RECORD["Record Results"]
    RECORD --> CHECK{"All done?"}
    CHECK -->|No| FIND
    CHECK -->|Yes| RESULT["Return Result"]
    COND -->|No| SKIP["Propagate Skips"]
    SKIP --> CHECK
```

***

## Error Handling

When a step fails:

1. **Default behavior** — the step is marked as `failed`, and downstream dependents are `skipped`
2. **With `continueOnError: true`** — the step is marked as `failed` but treated as completed for dependency resolution, allowing downstream steps to proceed

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
steps: [
  {
    id: 'optional-enrichment',
    jobName: 'enrich-data',
    continueOnError: true, // Workflow continues even if this fails
  },
  {
    id: 'save',
    jobName: 'save-results',
    dependsOn: ['optional-enrichment'],
    // This will run even if enrichment failed
  },
]
```

***

## MCP Tools

When workflows are enabled, the following MCP tools are automatically registered:

| Tool | Description |
| - | - |
| `list_workflows` | List registered workflows with optional filtering |
| `execute_workflow` | Execute a workflow (inline or background) |
| `get_workflow_status` | Get execution status with per-step results |
| `register_workflow` | Register a dynamic workflow at runtime (`hideFromDiscovery: true`) |
| `remove_workflow` | Remove a dynamic workflow (`hideFromDiscovery: true`) |

Hyphen aliases (`list-workflows`, `execute-workflow`, …) still resolve with a deprecation log line for one release — agents and code that hardcoded the old form keep working.

***

## Best Practices

**Do:**

* Keep steps focused on a single job each
* Use `dependsOn` to make data flow explicit
* Set `continueOnError: true` for non-critical steps
* Use dynamic input functions to pass data between steps
* Set appropriate `maxConcurrency` based on resource constraints

**Don't:**

* Create deeply nested dependency chains when parallel execution is possible
* Skip DAG validation by referencing non-existent step IDs
* Set `maxConcurrency` too high for resource-intensive jobs
* Use workflows for single-step operations (use jobs directly)

## Next Steps

<CardGroup cols={2}>
  <Card title="Jobs" icon="briefcase" href="/frontmcp/servers/jobs">
    Define the jobs that workflows orchestrate
  </Card>

  <Card title="@Workflow" icon="at" href="/frontmcp/sdk-reference/decorators/workflow">
    Decorator reference
  </Card>

  <Card title="WorkflowRegistry" icon="database" href="/frontmcp/sdk-reference/registries/workflow-registry">
    Registry API reference
  </Card>

  <Card title="DirectClient" icon="plug" href="/frontmcp/sdk-reference/core/direct-client">
    Programmatic workflow execution
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.