# Interactor Workflows

> Build state-machine based automation with human-in-the-loop support through Interactor. Use when implementing approval flows, multi-step processes, automated pipelines, or any workflow requiring user input at specific stages.

- Skill: `diegosouzapw/interactor-workflows` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add diegosouzapw/interactor-workflows`
- Raw SKILL.md: https://api.skillmd.com/api/skills/diegosouzapw/interactor-workflows/raw
- Safety review: pending (external: skill-scanner PASS, skillspector CAUTION)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Productivity
- Author: diegosouzapw (https://skillmd.com/u/diegosouzapw)
- Updated: 2026-09-08
- Page: https://skillmd.com/skills/diegosouzapw/interactor-workflows

---


# Interactor Workflows Skill

Build state-machine based automations with human-in-the-loop support for multi-step business processes.

## When to Use

- **Approval Flows**: Multi-level approval processes (expense reports, purchase orders)
- **Onboarding Workflows**: Step-by-step user or employee onboarding
- **Order Processing**: Order fulfillment with status tracking
- **Support Escalation**: Ticket routing with human handoffs
- **Document Processing**: Review and approval pipelines
- **Any Multi-Step Process**: Processes requiring conditional logic and user input

## Prerequisites

- Interactor authentication configured (see `interactor-auth` skill)
- Understanding of state machines and workflow concepts
- Webhook endpoint for workflow notifications (recommended)

## Overview

Workflows consist of:

| Component | Description |
|-----------|-------------|
| **States** | Steps in your process (action, halting, terminal) |
| **Transitions** | Rules for moving between states |
| **Instances** | Running executions of a workflow |
| **Threads** | Parallel execution paths within an instance |

---

## Instructions

### Step 1: Create a Workflow Definition

```bash
curl -X POST https://core.interactor.com/api/v1/workflows \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "approval_workflow",
    "initial_state": "request",
    "ai_guidance": "This workflow handles approval requests. Route based on amount thresholds.",
    "states": {
      "request": {
        "type": "action",
        "logic": {
          "type": "script",
          "code": "return { request_id: input.id, amount: input.amount, status: \"pending\", submitted_at: new Date().toISOString() }"
        },
        "transitions": [
          { "target": "await_approval" }
        ]
      },
      "await_approval": {
        "type": "halting",
        "presentation": {
          "type": "form",
          "title": "Approval Required",
          "description": "Please review and approve or reject this request.",
          "fields": [
            { "name": "approved", "type": "boolean", "label": "Approve this request?" },
            { "name": "comment", "type": "string", "label": "Comment (optional)", "multiline": true }
          ]
        },
        "transitions": [
          { "target": "approved", "condition": { "field": "approved", "equals": true } },
          { "target": "rejected" }
        ]
      },
      "approved": {
        "type": "terminal",
        "on_enter": {
          "type": "http",
          "method": "POST",
          "url": "https://yourapp.com/api/webhooks/approval-complete",
          "body": { "request_id": "${workflow_data.request_id}", "status": "approved" }
        }
      },
      "rejected": {
        "type": "terminal",
        "on_enter": {
          "type": "http",
          "method": "POST",
          "url": "https://yourapp.com/api/webhooks/approval-complete",
          "body": { "request_id": "${workflow_data.request_id}", "status": "rejected" }
        }
      }
    }
  }'
```

**Response:**
```json
{
  "data": {
    "name": "approval_workflow",
    "version_id": "v_abc123",
    "status": "draft",
    "created_at": "2026-01-20T12:00:00Z"
  }
}
```

### State Types

| Type | Description | Behavior |
|------|-------------|----------|
| `action` | Executes logic automatically | Runs logic, then transitions immediately |
| `halting` | Pauses for external input | Waits for `resume` call with user input |
| `terminal` | End state | Workflow completes, no further transitions |

> **Note**: The `on_enter` property shown in terminal states (for triggering HTTP callbacks on completion) is an optional enhancement. Verify availability with your Interactor version.

### Step 2: Validate Without Saving

Test a workflow definition before creating it:

```bash
curl -X POST https://core.interactor.com/api/v1/workflows/validate \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "my_workflow",
    "initial_state": "start",
    "states": {
      "start": {
        "type": "action",
        "logic": { "type": "script", "code": "return { message: \"Hello\" }" },
        "transitions": [{ "target": "end" }]
      },
      "end": {
        "type": "terminal"
      }
    }
  }'
```

**Response (success):**
```json
{
  "data": {
    "valid": true
  }
}
```

**Response (error):**
```json
{
  "data": {
    "valid": false,
    "errors": [
      {
        "path": "states.start.transitions[0].target",
        "message": "Target state 'nonexistent' does not exist"
      }
    ]
  }
}
```

### Step 3: List Workflows

```bash
curl https://core.interactor.com/api/v1/workflows \
  -H "Authorization: Bearer <token>"
```

**Response:**
```json
{
  "data": {
    "workflows": [
      {
        "name": "approval_workflow",
        "latest_version_id": "v_abc123",
        "published_version_id": "v_abc123",
        "created_at": "2026-01-20T12:00:00Z"
      },
      {
        "name": "onboarding_workflow",
        "latest_version_id": "v_def456",
        "published_version_id": null,
        "created_at": "2026-01-19T10:00:00Z"
      }
    ]
  }
}
```

### Step 4: List Versions

```bash
curl https://core.interactor.com/api/v1/workflows/approval_workflow/versions \
  -H "Authorization: Bearer <token>"
```

**Response:**
```json
{
  "data": {
    "versions": [
      {
        "version_id": "v_abc123",
        "status": "draft",
        "created_at": "2026-01-20T12:00:00Z"
      },
      {
        "version_id": "v_def456",
        "status": "published",
        "created_at": "2026-01-19T10:00:00Z"
      }
    ]
  }
}
```

### Step 5: Publish a Version

Workflows must be published before they can be executed:

```bash
curl -X POST https://core.interactor.com/api/v1/workflows/approval_workflow/versions/v_abc123/publish \
  -H "Authorization: Bearer <token>"
```

**Response:**
```json
{
  "data": {
    "version_id": "v_abc123",
    "status": "published",
    "published_at": "2026-01-20T12:05:00Z"
  }
}
```

---

## Workflow Instances

Instances are running executions of a workflow.

### Create Instance

Start a new workflow execution:

```bash
curl -X POST https://core.interactor.com/api/v1/workflows/approval_workflow/instances \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "namespace": "user_123",
    "input": {
      "id": "req_456",
      "amount": 5000,
      "requester": "john@example.com",
      "description": "New laptop for development"
    }
  }'
```

**Response:**
```json
{
  "data": {
    "id": "inst_xyz",
    "workflow_name": "approval_workflow",
    "version_id": "v_abc123",
    "namespace": "user_123",
    "status": "halted",
    "current_state": "await_approval",
    "workflow_data": {
      "request_id": "req_456",
      "amount": 5000,
      "status": "pending",
      "submitted_at": "2026-01-20T12:00:00Z"
    },
    "created_at": "2026-01-20T12:00:00Z"
  }
}
```

### Instance Status Values

| Status | Description |
|--------|-------------|
| `running` | Actively executing (in an action state) |
| `halted` | Paused, waiting for external input |
| `completed` | Finished successfully (reached terminal state) |
| `failed` | Terminated due to error |
| `cancelled` | Manually cancelled |

### List Instances

```bash
curl https://core.interactor.com/api/v1/workflows/instances \
  -H "Authorization: Bearer <token>"
```

**Query Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `namespace` | string | Filter by namespace |
| `workflow_name` | string | Filter by workflow |
| `status` | string | `running`, `halted`, `completed`, `failed`, `cancelled` |

**Example - List halted instances for a user:**
```bash
curl "https://core.interactor.com/api/v1/workflows/instances?namespace=user_123&status=halted" \
  -H "Authorization: Bearer <token>"
```

**Response:**
```json
{
  "data": {
    "instances": [
      {
        "id": "inst_xyz",
        "workflow_name": "approval_workflow",
        "status": "halted",
        "current_state": "await_approval",
        "created_at": "2026-01-20T12:00:00Z"
      },
      {
        "id": "inst_abc",
        "workflow_name": "onboarding_workflow",
        "status": "halted",
        "current_state": "verify_email",
        "created_at": "2026-01-19T15:30:00Z"
      }
    ]
  }
}
```

### Get Instance

```bash
curl https://core.interactor.com/api/v1/workflows/instances/inst_xyz \
  -H "Authorization: Bearer <token>"
```

**Response:**
```json
{
  "data": {
    "id": "inst_xyz",
    "workflow_name": "approval_workflow",
    "version_id": "v_abc123",
    "namespace": "user_123",
    "status": "halted",
    "current_state": "await_approval",
    "workflow_data": {
      "request_id": "req_456",
      "amount": 5000,
      "status": "pending"
    },
    "halting_presentation": {
      "type": "form",
      "title": "Approval Required",
      "description": "Please review and approve or reject this request.",
      "fields": [
        { "name": "approved", "type": "boolean", "label": "Approve this request?" },
        { "name": "comment", "type": "string", "label": "Comment (optional)", "multiline": true }
      ]
    },
    "threads": [
      {
        "id": "thread_main",
        "status": "halted",
        "current_state": "await_approval"
      }
    ],
    "history": [
      {
        "state": "request",
        "entered_at": "2026-01-20T12:00:00Z",
        "exited_at": "2026-01-20T12:00:01Z",
        "transition": "await_approval"
      },
      {
        "state": "await_approval",
        "entered_at": "2026-01-20T12:00:01Z"
      }
    ],
    "created_at": "2026-01-20T12:00:00Z"
  }
}
```

---

## Resuming Workflows

When a workflow reaches a halting state, it waits for external input.

### Resume with Input

```bash
curl -X POST https://core.interactor.com/api/v1/workflows/instances/inst_xyz/resume \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "input": {
      "approved": true,
      "comment": "Looks good, approved for Q1 budget"
    }
  }'
```

**Response:**
```json
{
  "data": {
    "id": "inst_xyz",
    "status": "completed",
    "current_state": "approved",
    "workflow_data": {
      "request_id": "req_456",
      "amount": 5000,
      "status": "pending",
      "approved": true,
      "comment": "Looks good, approved for Q1 budget"
    }
  }
}
```

The workflow continues execution based on the input and transition conditions.

### Cancel Instance

```bash
curl -X POST https://core.interactor.com/api/v1/workflows/instances/inst_xyz/cancel \
  -H "Authorization: Bearer <token>"
```

**Response:**
```json
{
  "data": {
    "id": "inst_xyz",
    "status": "cancelled",
    "cancelled_at": "2026-01-20T12:30:00Z"
  }
}
```

---

## Threads

Workflows can have parallel execution paths (threads).

### List Threads

```bash
curl https://core.interactor.com/api/v1/workflows/instances/inst_xyz/threads \
  -H "Authorization: Bearer <token>"
```

**Response:**
```json
{
  "data": {
    "threads": [
      {
        "id": "thread_main",
        "status": "halted",
        "current_state": "await_approval"
      },
      {
        "id": "thread_finance",
        "status": "completed",
        "current_state": "finance_approved"
      }
    ]
  }
}
```

### Resume Specific Thread

For workflows with multiple parallel threads:

```bash
curl -X POST https://core.interactor.com/api/v1/workflows/instances/inst_xyz/threads/thread_1/resume \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "input": {
      "department_approved": true
    }
  }'
```

**Response:**
```json
{
  "data": {
    "id": "inst_xyz",
    "status": "running",
    "threads": [
      {
        "id": "thread_1",
        "status": "completed",
        "current_state": "department_approved"
      },
      {
        "id": "thread_2",
        "status": "halted",
        "current_state": "await_finance_approval"
      }
    ]
  }
}
```

---

## History API

Query workflow execution history for debugging, monitoring, and audit.

### List History Events

```bash
curl https://core.interactor.com/api/v1/workflows/instances/inst_xyz/history \
  -H "Authorization: Bearer <token>"
```

**Query Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `limit` | integer | Max events (default: 100, max: 1000) |
| `cursor` | string | Pagination cursor |
| `types` | string | Filter by type: `transition`, `step`, `halt`, `error`, `lifecycle` |
| `since` | ISO8601 | Events after this timestamp |
| `until` | ISO8601 | Events before this timestamp |
| `thread` | string | Filter to specific thread |
| `include_data` | boolean | Include workflow_data snapshots |

**Response:**
```json
{
  "data": {
    "instance_id": "inst_xyz",
    "workflow_id": "wf_abc",
    "status": "completed",
    "events": [
      {
        "id": "evt_01HX...",
        "type": "lifecycle",
        "subtype": "created",
        "timestamp": "2026-01-20T12:00:00Z",
        "initial_state": "request"
      },
      {
        "id": "evt_01HX...",
        "type": "transition",
        "subtype": "state_change",
        "from_state": "request",
        "to_state": "processing",
        "trigger": "automatic",
        "changes": {
          "updated": {"status": {"from": "pending", "to": "processing"}}
        }
      }
    ],
    "pagination": {"has_more": false, "next_cursor": null}
  }
}
```

### Get Single Event

```bash
curl https://core.interactor.com/api/v1/workflows/instances/inst_xyz/events/evt_01HX... \
  -H "Authorization: Bearer <token>"
```

Add `?include_data=true` to include the `workflow_data` snapshot at that point.

### Error Dashboard

Query errors across all workflows:

```bash
curl "https://core.interactor.com/api/v1/workflows/errors?since=2026-01-20T00:00:00Z" \
  -H "Authorization: Bearer <token>"
```

---

## Halting Instructions

When a workflow halts, you can configure how the halting message is generated and presented to users.

### AI-Generated Instructions

Use AI to dynamically generate contextual messages based on workflow data:

```json
{
  "await_approval": {
    "type": "halting",
    "halting_instructions": {
      "type": "ai",
      "config": {
        "prompt": "Summarize this order and ask the user to approve or reject it.",
        "model": "claude-3-haiku-20240307",
        "include_data_paths": ["order", "customer", "risk_score"]
      }
    },
    "transition_mode": "selection",
    "transitions": [
      {"key": "approve", "to": "approved", "description": "Approve the order"},
      {"key": "reject", "to": "rejected", "description": "Reject the order"}
    ]
  }
}
```

**Simple format** - treats `instruction` as an AI prompt:

```json
{
  "halting_instructions": {
    "instruction": "Tell the user the strategy is ready for review. Highlight key metrics and risks.",
    "include_data": ["strategy", "benchmarks", "risk_assessment"]
  }
}
```

### Static Message Instructions

For static messages without AI generation:

```json
{
  "halting_instructions": {
    "type": "message",
    "config": {
      "title": "Approval Required",
      "message": "This order exceeds the automatic approval threshold and requires manual review."
    }
  }
}
```

### Halted Response

When halted, the API response includes `halted_options`:

```json
{
  "status": "halted",
  "halted_at_state": "await_approval",
  "halted_options": {
    "instruction": "Order #123 for $150.00 from Acme Corp is ready. Risk score: Low (23).",
    "include_data": ["order", "customer"],
    "transition_mode": "selection",
    "choices": [
      {"key": "approve", "description": "Approve the order", "to": "approved"},
      {"key": "reject", "description": "Reject the order", "to": "rejected"}
    ],
    "generated": true
  }
}
```

| Field | Description |
|-------|-------------|
| `instruction` | Message to display (AI-generated or static) |
| `generated` | `true` if AI-generated, `false` if static |
| `choices` | Available transitions for selection mode |

---

## Halting Presentations (Legacy)

> **Note**: The `presentation` format is still supported for backward compatibility. New workflows should use `halting_instructions` above.

When a workflow halts, specify how to present the required input to users.

> **Note**: The `title` and `description` fields shown in presentations are optional enhancements for better UX. The core API requires only `type` and the type-specific fields (`fields`, `options`, or `message`).

### Form Presentation

```json
{
  "type": "form",
  "title": "Approval Required",
  "description": "Please review the request details and provide your decision.",
  "fields": [
    {
      "name": "approved",
      "type": "boolean",
      "label": "Approve this request?",
      "required": true
    },
    {
      "name": "amount",
      "type": "number",
      "label": "Approved Amount",
      "default": "${workflow_data.amount}",
      "min": 0,
      "max": 100000
    },
    {
      "name": "notes",
      "type": "string",
      "label": "Notes",
      "multiline": true,
      "placeholder": "Add any notes or conditions..."
    },
    {
      "name": "priority",
      "type": "select",
      "label": "Priority",
      "options": [
        { "value": "low", "label": "Low" },
        { "value": "medium", "label": "Medium" },
        { "value": "high", "label": "High" }
      ],
      "default": "medium"
    }
  ]
}
```

### Choice Presentation

```json
{
  "type": "choice",
  "title": "Select Action",
  "message": "How would you like to proceed with this request?",
  "options": [
    {
      "value": "approve",
      "label": "Approve",
      "description": "Approve the request as submitted"
    },
    {
      "value": "reject",
      "label": "Reject",
      "description": "Reject the request"
    },
    {
      "value": "escalate",
      "label": "Escalate to Manager",
      "description": "Send to manager for review"
    },
    {
      "value": "request_info",
      "label": "Request More Information",
      "description": "Ask the requester for additional details"
    }
  ]
}
```

### Message Presentation

```json
{
  "type": "message",
  "title": "Processing",
  "message": "Waiting for external system response. This may take a few minutes.",
  "show_progress": true
}
```

> **Note**: The `show_progress` field is an optional UI hint. Client implementations may ignore it if not supported.

### Field Types

| Type | Description | Additional Properties |
|------|-------------|----------------------|
| `string` | Text input | `multiline`, `placeholder`, `maxLength` |
| `number` | Numeric input | `min`, `max`, `step` |
| `boolean` | Checkbox/toggle | - |
| `select` | Dropdown selection | `options` array |
| `date` | Date picker | `minDate`, `maxDate` |
| `file` | File upload | `accept`, `maxSize` |

> **Note**: Common field properties include `required`, `default`, and `label`. Additional properties like `placeholder`, `step`, `maxLength` may vary by Interactor version. Test with `/validate` endpoint to confirm supported properties.

---

## Workflow Logic

### Script Logic

Execute JavaScript code in action states:

```json
{
  "type": "script",
  "code": "const total = input.items.reduce((sum, item) => sum + item.price, 0); const needsApproval = total > 1000; return { ...workflow_data, total, needs_approval: needsApproval, calculated_at: new Date().toISOString() };"
}
```

**Available Variables:**
- `input` - The input provided when starting or resuming the workflow
- `workflow_data` - Current accumulated workflow data
- `context` - Additional context (namespace, instance_id, etc.)

### HTTP Logic

Make external API calls:

```json
{
  "type": "http",
  "method": "POST",
  "url": "https://api.yourservice.com/process",
  "headers": {
    "Authorization": "Bearer ${secrets.API_KEY}",
    "Content-Type": "application/json"
  },
  "body": {
    "order_id": "${workflow_data.order_id}",
    "amount": "${workflow_data.amount}",
    "customer_email": "${workflow_data.customer_email}"
  },
  "timeout": 30000,
  "retry": {
    "attempts": 3,
    "backoff": "exponential"
  }
}
```

> **Note**: The `timeout` and `retry` properties are optional enhancements. The core API requires only `type`, `method`, `url`, and optionally `headers` and `body`.

### Transition Conditions

Define conditions for state transitions:

```json
{
  "transitions": [
    {
      "target": "high_value_approval",
      "condition": {
        "field": "amount",
        "operator": "gt",
        "value": 10000
      }
    },
    {
      "target": "manager_approval",
      "condition": {
        "field": "amount",
        "operator": "gt",
        "value": 1000
      }
    },
    {
      "target": "auto_approve"
    }
  ]
}
```

**Operators:**

| Operator | Description | Example |
|----------|-------------|---------|
| `equals` | Exact match | `{ "field": "status", "equals": "approved" }` |
| `not_equals` | Not equal | `{ "field": "status", "not_equals": "rejected" }` |
| `gt` | Greater than | `{ "field": "amount", "operator": "gt", "value": 1000 }` |
| `gte` | Greater than or equal | `{ "field": "amount", "operator": "gte", "value": 1000 }` |
| `lt` | Less than | `{ "field": "amount", "operator": "lt", "value": 100 }` |
| `lte` | Less than or equal | `{ "field": "amount", "operator": "lte", "value": 100 }` |
| `contains` | String contains | `{ "field": "email", "operator": "contains", "value": "@company.com" }` |
| `in` | Value in array | `{ "field": "category", "operator": "in", "value": ["A", "B", "C"] }` |

### Complex Conditions

Use `and` / `or` for complex conditions:

```json
{
  "transitions": [
    {
      "target": "vp_approval",
      "condition": {
        "and": [
          { "field": "approved", "equals": true },
          { "field": "amount", "operator": "gt", "value": 10000 }
        ]
      }
    },
    {
      "target": "approved",
      "condition": {
        "or": [
          { "field": "amount", "operator": "lte", "value": 1000 },
          {
            "and": [
              { "field": "approved", "equals": true },
              { "field": "amount", "operator": "lte", "value": 10000 }
            ]
          }
        ]
      }
    },
    {
      "target": "rejected"
    }
  ]
}
```

---

## Complete Example: Multi-Level Approval

```json
{
  "name": "purchase_approval",
  "initial_state": "submit",
  "ai_guidance": "Multi-level purchase approval workflow. Amount thresholds: <$1000 auto-approve, $1000-$10000 manager, >$10000 VP required.",
  "states": {
    "submit": {
      "type": "action",
      "logic": {
        "type": "script",
        "code": "return { ...input, submitted_at: new Date().toISOString(), status: 'pending' }"
      },
      "transitions": [
        {
          "target": "auto_approved",
          "condition": { "field": "amount", "operator": "lte", "value": 1000 }
        },
        {
          "target": "manager_approval",
          "condition": { "field": "amount", "operator": "lte", "value": 10000 }
        },
        { "target": "manager_approval" }
      ]
    },

    "manager_approval": {
      "type": "halting",
      "presentation": {
        "type": "form",
        "title": "Manager Approval Required",
        "description": "Purchase request for ${workflow_data.description} - $${workflow_data.amount}",
        "fields": [
          { "name": "approved", "type": "boolean", "label": "Approve?", "required": true },
          { "name": "comment", "type": "string", "label": "Comment", "multiline": true }
        ]
      },
      "transitions": [
        {
          "target": "vp_approval",
          "condition": {
            "and": [
              { "field": "approved", "equals": true },
              { "field": "amount", "operator": "gt", "value": 10000 }
            ]
          }
        },
        {
          "target": "approved",
          "condition": { "field": "approved", "equals": true }
        },
        { "target": "rejected" }
      ]
    },

    "vp_approval": {
      "type": "halting",
      "presentation": {
        "type": "form",
        "title": "VP Approval Required",
        "description": "High-value purchase: ${workflow_data.description} - $${workflow_data.amount}",
        "fields": [
          { "name": "approved", "type": "boolean", "label": "VP Approval", "required": true },
          { "name": "budget_code", "type": "string", "label": "Budget Code" },
          { "name": "comment", "type": "string", "label": "Comment", "multiline": true }
        ]
      },
      "transitions": [
        {
          "target": "approved",
          "condition": { "field": "approved", "equals": true }
        },
        { "target": "rejected" }
      ]
    },

    "auto_approved": {
      "type": "action",
      "logic": {
        "type": "script",
        "code": "return { ...workflow_data, status: 'approved', approved_by: 'auto', approved_at: new Date().toISOString() }"
      },
      "transitions": [
        { "target": "notify_requester" }
      ]
    },

    "approved": {
      "type": "action",
      "logic": {
        "type": "script",
        "code": "return { ...workflow_data, status: 'approved', approved_at: new Date().toISOString() }"
      },
      "transitions": [
        { "target": "notify_requester" }
      ]
    },

    "rejected": {
      "type": "action",
      "logic": {
        "type": "script",
        "code": "return { ...workflow_data, status: 'rejected', rejected_at: new Date().toISOString() }"
      },
      "transitions": [
        { "target": "notify_requester" }
      ]
    },

    "notify_requester": {
      "type": "action",
      "logic": {
        "type": "http",
        "method": "POST",
        "url": "https://yourapp.com/api/notifications",
        "body": {
          "type": "purchase_decision",
          "email": "${workflow_data.requester}",
          "status": "${workflow_data.status}",
          "amount": "${workflow_data.amount}"
        }
      },
      "transitions": [
        { "target": "complete" }
      ]
    },

    "complete": {
      "type": "terminal"
    }
  }
}
```

---

## Implementation Examples

### Elixir Implementation (Phoenix)

> **Prerequisite**: This module requires the `MyApp.Interactor.Client` module from the `interactor-auth` skill. See that skill for the HTTP client implementation.

```elixir
defmodule MyApp.Interactor.Workflows do
  @moduledoc """
  Interactor Workflow management for state-machine based automations.

  Requires MyApp.Interactor.Client from interactor-auth skill.
  """

  alias MyApp.Interactor.Client

  # ============ Workflow Definitions ============

  @doc """
  Create a new workflow definition.
  """
  def create_workflow(definition) do
    Client.post("/workflows", definition)
  end

  @doc """
  Validate a workflow definition without saving.
  """
  def validate_workflow(definition) do
    Client.post("/workflows/validate", definition)
  end

  @doc """
  List all workflows.
  """
  def list_workflows do
    case Client.get("/workflows") do
      {:ok, %{"workflows" => workflows}} -> {:ok, workflows}
      error -> error
    end
  end

  @doc """
  List versions for a workflow.
  """
  def list_versions(workflow_name) do
    case Client.get("/workflows/#{workflow_name}/versions") do
      {:ok, %{"versions" => versions}} -> {:ok, versions}
      error -> error
    end
  end

  @doc """
  Publish a workflow version.
  """
  def publish_version(workflow_name, version_id) do
    Client.post("/workflows/#{workflow_name}/versions/#{version_id}/publish", %{})
  end

  # ============ Instances ============

  @doc """
  Create a new workflow instance.
  """
  def create_instance(workflow_name, user_id, input) do
    Client.post("/workflows/#{workflow_name}/instances", %{
      namespace: "user_#{user_id}",
      input: input
    })
  end

  @doc """
  Get a workflow instance by ID.
  """
  def get_instance(instance_id) do
    Client.get("/workflows/instances/#{instance_id}")
  end

  @doc """
  List workflow instances with optional filters.
  """
  def list_instances(filters \\ %{}) do
    query_params =
      filters
      |> Enum.map(fn
        {:user_id, id} -> {"namespace", "user_#{id}"}
        {:workflow_name, name} -> {"workflow_name", name}
        {:status, status} -> {"status", status}
      end)
      |> URI.encode_query()

    path = if query_params == "", do: "/workflows/instances", else: "/workflows/instances?#{query_params}"

    case Client.get(path) do
      {:ok, %{"instances" => instances}} -> {:ok, instances}
      error -> error
    end
  end

  @doc """
  Resume a halted workflow instance with input.
  """
  def resume_instance(instance_id, input) do
    Client.post("/workflows/instances/#{instance_id}/resume", %{input: input})
  end

  @doc """
  Cancel a workflow instance.
  """
  def cancel_instance(instance_id) do
    Client.post("/workflows/instances/#{instance_id}/cancel", %{})
  end

  # ============ Threads ============

  @doc """
  List threads for an instance.
  """
  def list_threads(instance_id) do
    case Client.get("/workflows/instances/#{instance_id}/threads") do
      {:ok, %{"threads" => threads}} -> {:ok, threads}
      error -> error
    end
  end

  @doc """
  Resume a specific thread.
  """
  def resume_thread(instance_id, thread_id, input) do
    Client.post(
      "/workflows/instances/#{instance_id}/threads/#{thread_id}/resume",
      %{input: input}
    )
  end

  # ============ Helpers ============

  @doc """
  Wait for a workflow to complete or halt.
  Returns {:ok, instance} when completed/halted, {:error, reason} on failure/timeout.
  """
  def wait_for_completion(instance_id, opts \\ []) do
    timeout_ms = Keyword.get(opts, :timeout, 300_000)
    poll_interval_ms = Keyword.get(opts, :poll_interval, 2_000)
    deadline = System.monotonic_time(:millisecond) + timeout_ms

    do_wait_for_completion(instance_id, deadline, poll_interval_ms)
  end

  defp do_wait_for_completion(instance_id, deadline, poll_interval_ms) do
    if System.monotonic_time(:millisecond) >= deadline do
      {:error, :timeout}
    else
      case get_instance(instance_id) do
        {:ok, %{"status" => "completed"} = instance} ->
          {:ok, instance}

        {:ok, %{"status" => "halted"} = instance} ->
          {:ok, instance}

        {:ok, %{"status" => "failed", "error" => error}} ->
          {:error, {:workflow_failed, error}}

        {:ok, %{"status" => "cancelled"}} ->
          {:error, :cancelled}

        {:ok, %{"status" => "running"}} ->
          Process.sleep(poll_interval_ms)
          do_wait_for_completion(instance_id, deadline, poll_interval_ms)

        {:error, _} = error ->
          error
      end
    end
  end
end
```

### Elixir Usage Example

```elixir
alias MyApp.Interactor.Workflows

# Create and publish a workflow
{:ok, version} = Workflows.create_workflow(purchase_approval_definition)
{:ok, _published} = Workflows.publish_version("purchase_approval", version["version_id"])

# Start a new instance
{:ok, instance} = Workflows.create_instance(
  "purchase_approval",
  "user_123",
  %{
    id: "PO-2026-001",
    amount: 5500,
    requester: "john@example.com",
    description: "Development laptop"
  }
)

IO.puts("Workflow started: #{instance["id"]}")
IO.puts("Current state: #{instance["current_state"]}")
IO.puts("Status: #{instance["status"]}")

# Handle halted state
case instance["status"] do
  "halted" ->
    IO.puts("Waiting for approval...")
    IO.inspect(instance["halting_presentation"], label: "Presentation")

    # Simulate manager approval
    {:ok, resumed} = Workflows.resume_instance(instance["id"], %{
      approved: true,
      comment: "Approved for Q1 budget"
    })

    IO.puts("New status: #{resumed["status"]}")
    IO.puts("New state: #{resumed["current_state"]}")

  _ ->
    :ok
end
```

### Elixir LiveView Integration

First, create a component to render workflow presentations dynamically:

```elixir
defmodule MyAppWeb.WorkflowComponents do
  use Phoenix.Component

  @doc """
  Renders a workflow form based on the halting presentation.
  """
  attr :presentation, :map, required: true
  attr :form, :any, required: true

  def workflow_form(assigns) do
    ~H"""
    <.form for={@form} phx-submit="submit_input" class="space-y-4">
      <%= if @presentation["title"] do %>
        <h2 class="text-xl font-semibold"><%= @presentation["title"] %></h2>
      <% end %>
      <%= if @presentation["description"] do %>
        <p class="text-gray-600"><%= @presentation["description"] %></p>
      <% end %>

      <%= case @presentation["type"] do %>
        <% "form" -> %>
          <%= for field <- @presentation["fields"] || [] do %>
            <.workflow_field field={field} form={@form} />
          <% end %>

        <% "choice" -> %>
          <p class="font-medium"><%= @presentation["message"] %></p>
          <div class="flex flex-wrap gap-2">
            <%= for option <- @presentation["options"] || [] do %>
              <button
                type="submit"
                name="input[choice]"
                value={option["value"]}
                class="px-4 py-2 bg-[#4CD964] hover:bg-[#3DBF55] text-white rounded-full"
              >
                <%= option["label"] %>
              </button>
            <% end %>
          </div>

        <% "message" -> %>
          <p><%= @presentation["message"] %></p>
      <% end %>

      <%= if @presentation["type"] == "form" do %>
        <button type="submit" class="px-6 py-2 bg-[#4CD964] hover:bg-[#3DBF55] text-white rounded-full">
          Submit
        </button>
      <% end %>
    </.form>
    """
  end

  attr :field, :map, required: true
  attr :form, :any, required: true

  defp workflow_field(assigns) do
    ~H"""
    <div class="space-y-1">
      <label class="block font-medium">
        <%= @field["label"] %>
        <%= if @field["required"], do: "*" %>
      </label>

      <%= case @field["type"] do %>
        <% "string" -> %>
          <%= if @field["multiline"] do %>
            <textarea
              name={"input[#{@field["name"]}]"}
              class="w-full border rounded-lg p-2"
              placeholder={@field["placeholder"]}
            ><%= @field["default"] %></textarea>
          <% else %>
            <input
              type="text"
              name={"input[#{@field["name"]}]"}
              value={@field["default"]}
              placeholder={@field["placeholder"]}
              class="w-full border rounded-lg p-2"
            />
          <% end %>

        <% "number" -> %>
          <input
            type="number"
            name={"input[#{@field["name"]}]"}
            value={@field["default"]}
            min={@field["min"]}
            max={@field["max"]}
            step={@field["step"]}
            class="w-full border rounded-lg p-2"
          />

        <% "boolean" -> %>
          <input
            type="checkbox"
            name={"input[#{@field["name"]}]"}
            value="true"
            checked={@field["default"] == true}
            class="h-5 w-5"
          />

        <% "select" -> %>
          <select name={"input[#{@field["name"]}]"} class="w-full border rounded-lg p-2">
            <%= for option <- @field["options"] || [] do %>
              <option value={option["value"]} selected={option["value"] == @field["default"]}>
                <%= option["label"] %>
              </option>
            <% end %>
          </select>

        <% "date" -> %>
          <input
            type="date"
            name={"input[#{@field["name"]}]"}
            value={@field["default"]}
            min={@field["minDate"]}
            max={@field["maxDate"]}
            class="w-full border rounded-lg p-2"
          />

        <% _ -> %>
          <input
            type="text"
            name={"input[#{@field["name"]}]"}
            value={@field["default"]}
            class="w-full border rounded-lg p-2"
          />
      <% end %>
    </div>
    """
  end
end
```

Then import it in your LiveView:

```elixir
defmodule MyAppWeb.WorkflowLive.Show do
  use MyAppWeb, :live_view

  import MyAppWeb.WorkflowComponents
  alias MyApp.Interactor.Workflows

  @impl true
  def mount(%{"id" => instance_id}, _session, socket) do
    if connected?(socket) do
      # Subscribe to workflow updates via PubSub
      Phoenix.PubSub.subscribe(MyApp.PubSub, "workflow:#{instance_id}")
    end

    case Workflows.get_instance(instance_id) do
      {:ok, instance} ->
        {:ok, assign(socket, instance: instance, form: to_form(%{}))}

      {:error, _} ->
        {:ok, push_navigate(socket, to: ~p"/workflows")}
    end
  end

  @impl true
  def handle_event("submit_input", %{"input" => input}, socket) do
    instance_id = socket.assigns.instance["id"]

    case Workflows.resume_instance(instance_id, input) do
      {:ok, updated_instance} ->
        {:noreply, assign(socket, instance: updated_instance)}

      {:error, reason} ->
        {:noreply, put_flash(socket, :error, "Failed to resume: #{inspect(reason)}")}
    end
  end

  @impl true
  def handle_event("cancel", _params, socket) do
    instance_id = socket.assigns.instance["id"]

    case Workflows.cancel_instance(instance_id) do
      {:ok, _} ->
        {:noreply, push_navigate(socket, to: ~p"/workflows")}

      {:error, reason} ->
        {:noreply, put_flash(socket, :error, "Failed to cancel: #{inspect(reason)}")}
    end
  end

  @impl true
  def handle_info({:workflow_updated, instance}, socket) do
    {:noreply, assign(socket, instance: instance)}
  end

  @impl true
  def render(assigns) do
    ~H"""
    <div class="workflow-instance">
      <h1>Workflow: <%= @instance["workflow_name"] %></h1>
      <p>Status: <span class={status_class(@instance["status"])}><%= @instance["status"] %></span></p>
      <p>Current State: <%= @instance["current_state"] %></p>

      <%= if @instance["status"] == "halted" do %>
        <.workflow_form
          presentation={@instance["halting_presentation"]}
          form={@form}
        />
      <% end %>

      <%= if @instance["status"] in ["running", "halted"] do %>
        <button phx-click="cancel" class="btn-secondary">Cancel Workflow</button>
      <% end %>
    </div>
    """
  end

  defp status_class("completed"), do: "text-green-600"
  defp status_class("failed"), do: "text-red-600"
  defp status_class("cancelled"), do: "text-gray-600"
  defp status_class("halted"), do: "text-yellow-600"
  defp status_class(_), do: "text-blue-600"
end
```

---

### TypeScript Implementation

```typescript
import { InteractorClient } from './interactor-client';

export class WorkflowManager {
  private client: InteractorClient;

  constructor(client: InteractorClient) {
    this.client = client;
  }

  // ============ Workflow Definitions ============

  async createWorkflow(definition: WorkflowDefinition): Promise<WorkflowVersion> {
    return this.client.request('POST', '/workflows', definition);
  }

  async validateWorkflow(definition: WorkflowDefinition): Promise<ValidationResult> {
    return this.client.request('POST', '/workflows/validate', definition);
  }

  async listWorkflows(): Promise<Workflow[]> {
    const result = await this.client.request<{ workflows: Workflow[] }>('GET', '/workflows');
    return result.workflows;
  }

  async listVersions(workflowName: string): Promise<WorkflowVersion[]> {
    const result = await this.client.request<{ versions: WorkflowVersion[] }>(
      'GET',
      `/workflows/${workflowName}/versions`
    );
    return result.versions;
  }

  async publishVersion(workflowName: string, versionId: string): Promise<WorkflowVersion> {
    return this.client.request(
      'POST',
      `/workflows/${workflowName}/versions/${versionId}/publish`
    );
  }

  // ============ Instances ============

  async createInstance(
    workflowName: strin

…(truncated)
