---
name: workflows
description: "Structured executable workflows. Named compositions of MCP tool calls with validated references, dependency ordering, failure handling, execution history, and schedule integration."
tools:
  - name: workflow-create
    publicAllowlist: false
    adminAllowlist: false
  - name: workflow-list
    publicAllowlist: false
    adminAllowlist: false
  - name: workflow-get
    publicAllowlist: false
    adminAllowlist: false
  - name: workflow-update
    publicAllowlist: false
    adminAllowlist: false
  - name: workflow-delete
    publicAllowlist: false
    adminAllowlist: false
  - name: workflow-validate
    publicAllowlist: false
    adminAllowlist: false
  - name: workflow-execute
    publicAllowlist: false
    adminAllowlist: false
  - name: workflow-runs
    publicAllowlist: false
    adminAllowlist: false
metadata: {"platform":{"always":false,"embed":[],"pluginKey":"workflows"}}
mcp:
  command: node
  args:
    - ${PLATFORM_ROOT}/lib/mcp-spawn-tee/dist/index.js
    - ${PLATFORM_ROOT}/plugins/workflows/mcp/dist/index.js
  env:
    MCP_SPAWN_TEE_NAME: workflows
    LOG_DIR: ${LOG_DIR}
    PLATFORM_ROOT: ${PLATFORM_ROOT}
    ACCOUNT_ID: ${ACCOUNT_ID}
    SESSION_ID: ${SESSION_ID}
    AGENT_SLUG: ${AGENT_SLUG}
mcp-manifest: auto
---

# Workflows

> **Loading note:** `platform.always:false` in the frontmatter above refers to **prose embedding** — this file's contents are not auto-injected into every agent system prompt. It does **not** refer to MCP server loading. The workflows MCP server is always loaded in admin sessions (registered in `getMcpServers()` in `claude-agent.ts` alongside `tasks` / `scheduling` / `email`). The same binary is designed to be spawned ad-hoc by the platform's scheduled-task dispatcher for `workflow-execute` calls via `.mcp.json`; that dispatcher is currently unwired (see `maxy-code-prd.md` §Scheduled tasks for the canonical destination surface).

Workflows are persistent, named compositions of MCP tool calls that the user creates and the engine executes. Each step is a direct invocation of a plugin tool — the workflow runner is an MCP-tool orchestrator, never an LLM caller. Plugins that need LLM reasoning own their own SDK call inside a tool. Steps are validated at creation time — a workflow with unmet dependencies cannot be activated. The user manages them conversationally.

## Creating Workflows

When the user describes a recurring process, conditional action, or reusable sequence, create a workflow. Confirm the name, description, and steps with the user before committing.

### Step shape

Every step is `type: "tool"`:

- **plugin** — the plugin that provides the tool (e.g. `memory`, `scheduling`, `telegram`)
- **tool** — the tool name within that plugin (e.g. `contact-create`, `message`)
- **params** — parameter object with `{{outputKey.field}}` templates for values from prior steps
- **timeout** — execution timeout in seconds (default: 30, max: 300)
- **label** — human-readable description of what this step does
- **dependsOn** — step positions (as string indexes) that must complete first
- **onFailure** — `abort` (default), `skip`, or `retry`
- **retryLimit** — max retries when onFailure is `retry` (max 3)
- **outputKey** — name under which this step's output is stored for use in subsequent step templates

### Capability validation

At creation time, every step's referenced plugin directory and built MCP server are checked. If any capability is missing, the workflow's status is set to `draft` with specific error messages listing which plugins or tools are unavailable. Only `active` workflows can be executed.

When the missing capability is later installed, use `workflow-validate` to re-check and promote the workflow to `active`.

### Status model

| Status | Meaning |
|--------|---------|
| `active` | All capabilities validated, ready to execute |
| `draft` | Missing capabilities — cannot execute until resolved |
| `paused` | Manually deactivated — retained but not suggested or executable |

## Discovery

Active workflows are discoverable via `workflow-list` and via `memory-search` (the vector index on the Workflow node surfaces relevant workflows alongside other graph knowledge). When the user's request matches an existing workflow, offer to execute it.

## Execution

Use `workflow-execute` to run a workflow. The engine:
1. Loads the workflow and its steps
2. Resolves step dependencies into execution order (topological sort)
3. For each step: resolves `{{template.variables}}` from context and prior step outputs, spawns the target plugin's MCP server, and calls the specified tool
4. Records every step's outcome as a StepResult node
5. Creates a WorkflowRun node with the overall status

### Template variables

Steps receive data from prior steps via named output bindings. If step 1 has `outputKey: "newContact"` and returns `{"contactId": "abc-123"}`, step 2 can reference `{{newContact.contactId}}` in its params.

The execution context can also be seeded with runtime variables via the `context` parameter on `workflow-execute`. These are available alongside step outputs.

Unresolved template variables cause the step to fail with a specific error naming the missing variable.

### Failure handling

Each step declares an `onFailure` policy:
- **abort** (default) — stop the workflow, mark the run as `failed`
- **skip** — mark this step as `skipped`, continue to the next step
- **retry** — retry up to `retryLimit` times before applying abort behaviour

### Run status

| Status | Meaning |
|--------|---------|
| `completed` | Every step succeeded |
| `partial` | Finished, but one or more steps were skipped |
| `failed` | A step failed with abort policy, execution stopped |

### Step status values

| Status | Meaning |
|---|---|
| `completed` | Step ran normally and produced output |
| `skipped` | Step failed but `onFailure: "skip"` kept execution going |
| `failed` | Step failed and `onFailure: "abort"` stopped execution |

(Historic `degraded` step results from before task 127 remain in the graph; the runner no longer produces them.)

## Execution History

Use `workflow-runs` to query execution history. Returns run status, timing, trigger type, and per-step outcome counts. The admin can ask "show me recent workflow runs" and get structured execution history.

## Schedule Integration

To trigger a workflow on a schedule, create an Event with the scheduling plugin using action dispatch:

```
action: { plugin: "workflows", tool: "workflow-execute", args: { workflowId: "..." } }
```

Scheduled workflows are dispatched by `check-due-events`, which is currently unwired pending migration to Desktop scheduled tasks (see `maxy-code-prd.md` §Scheduled tasks). When the dispatcher fires, the WorkflowRun captures `trigger: "schedule"`.

## Managing Workflows

- **Pause**: Set status to `paused` via `workflow-update`
- **Resume**: Set status back to `active` (only if capabilities are still valid)
- **Edit steps**: Provide the complete new step list via `workflow-update` — replaces all existing steps and re-validates capabilities
- **Re-validate**: Use `workflow-validate` after installing new plugins to check if draft workflows can now be activated
- **Delete**: Remove permanently via `workflow-delete` — cascades to steps, runs, and step results. Confirm with the user first.

## Tools

- `workflow-create` — Create a workflow with name, description, and tool steps
- `workflow-list` — List workflows with status and step counts
- `workflow-get` — Fetch a workflow with full step details
- `workflow-update` — Update name, description, steps (full replacement), or status
- `workflow-delete` — Permanently remove a workflow and all history
- `workflow-validate` — Re-check step capabilities and update status
- `workflow-execute` — Execute a workflow with optional runtime context
- `workflow-runs` — Query execution history with optional filters
