# Workflows

> Design deterministic automation flows for Aerostack bots. Directed acyclic graphs of nodes with branching logic, tool calls, LLM reasoning, identity verification, and human handoffs.

The workflow engine gives your bot deterministic automation. Instead of letting the LLM decide everything, you design a directed graph of nodes and edges that define exactly how each message is processed. The engine executes the graph step by step, following your branching logic, calling the tools you specify, and sending the messages you define.

---

## When to Use Workflows vs Agent Loop

| Scenario | Agent Loop | Workflow |
|----------|-----------|----------|
| Open-ended conversation and Q&A | Best choice | Overkill |
| Predictable multi-step processes (refunds, onboarding) | Unreliable | Best choice |
| Conditional routing and triage | Possible but fragile | Best choice |
| Guaranteed tool execution order | Not possible | Best choice |
| Identity verification before data access | Not available | `auth_gate` node |
| Human approval for high-stakes actions | Not available | `human_handoff` action |
| Delegating to specialist bots | Not available | `delegate_to_bot` node |
| Maximum flexibility for unexpected questions | Best choice | Limited |

**Rule of thumb:** If you can draw the flow on a whiteboard with boxes and arrows, use a workflow. If the user might ask anything and you want the LLM to figure it out, use agent loop.

---

## How Workflows Work

Workflows are JSON graphs stored in the bot's `workflow_json` field. Each workflow has:

- **Nodes** — individual processing steps (27 types available across 4 categories)
- **Edges** — connections between nodes that define execution order

When `workflow_enabled` is set to `1`, incoming messages are processed through the workflow graph instead of the agent loop.

```mermaid
flowchart TD
    A[Message arrives] --> B[trigger node]
    B --> C[llm_call: Classify intent]
    C --> D{logic: Route by intent}
    D --> |billing| E[mcp_tool: Look up account]
    D --> |refund| F[auth_gate: Verify identity]
    D --> |other| G[llm_call: General response]
    E --> H[send_message: Billing info]
    F --> I[mcp_tool: Check order]
    I --> J{logic: Amount > $500?}
    J --> |yes| K[human_handoff: Finance approval]
    J --> |no| L[mcp_tool: Process refund]
    K --> M[send_message: Escalated]
    L --> N[send_message: Refund confirmed]
    G --> O[send_message: Response]
```

---

## Execution Model

1. **Entry point:** The engine finds all `trigger` nodes and starts execution from them
2. **BFS traversal:** Nodes are executed in breadth-first order following edges
3. **Variable passing:** Each node can read from and write to a shared `variables` context
4. **Branching:** Logic nodes control which edges are followed via `sourceHandle` values
5. **Safety limits:** Maximum 50 nodes per execution, with cycle detection

### Workflow JSON Structure

```json
{
  "nodes": [
    {
      "id": "node_1",
      "type": "trigger",
      "data": { "triggerType": "message_received", "label": "On Message" },
      "position": { "x": 0, "y": 0 }
    },
    {
      "id": "node_2",
      "type": "llm_call",
      "data": {
        "prompt": "Classify the user's intent: {{message}}",
        "outputVariable": "intent",
        "label": "Classify Intent"
      },
      "position": { "x": 200, "y": 0 }
    },
    {
      "id": "node_3",
      "type": "send_message",
      "data": {
        "message": "Your intent was classified as: {{intent.text}}",
        "label": "Respond"
      },
      "position": { "x": 400, "y": 0 }
    }
  ],
  "edges": [
    { "id": "e1", "source": "node_1", "target": "node_2" },
    { "id": "e2", "source": "node_2", "target": "node_3" }
  ]
}
```

---

## All 14 Node Types

| Node Type | Purpose | Key Capability |
|-----------|---------|---------------|
| `trigger` | Entry point | Sets `message`, `user_id`, `platform` variables |
| `llm_call` | Call the LLM | Variable interpolation in prompts |
| `logic` | if/else, switch, while | Deterministic branching |
| `mcp_tool` | Call workspace tools | Any MCP, Skill, or Function |
| `send_message` | Reply to user | Template interpolation |
| `action` | Side effects | set_variable, end_conversation, human_handoff, create_ticket |
| `loop` | Iteration | for_each, count, while (max 50 iterations) |
| `code_block` | Run JavaScript | Sandboxed execution with variable access |
| `auth_gate` | Identity verification | OTP, magic link, 4 providers + custom HTTP |
| `schedule_message` | Delayed messages | Send at a future time |
| `send_proactive` | Cross-channel messages | Send to a different channel or user |
| `delegate_to_bot` | Bot-to-bot routing | Call another bot (max 3 hops) |
| `error_handler` | Graceful fallback | Catch and handle errors |
| `parallel` | Fan-out execution | Multiple paths in parallel |

See [Node Types](/bots/workflows/node-types) for complete configuration details and examples for each type.

---

## Enabling Workflows

### Via Dashboard

1. Navigate to your bot's detail page
2. Open the **Workflow** tab
3. Use the visual builder to add and connect nodes
4. Toggle **Enable Workflow** to activate

### Via API

```bash
curl -X PATCH https://api.aerostack.dev/api/bots/YOUR_BOT_ID \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "workflow_json": "{\"nodes\":[...],\"edges\":[...]}",
    "workflow_enabled": 1
  }'
```

When a workflow is enabled, the bot uses the workflow engine exclusively. The agent loop is bypassed entirely. Disable the workflow to return to agent loop mode.

---

## Validation

When you update `workflow_json`, Aerostack validates the structure:

- The JSON must parse successfully
- It must contain `nodes` (array) and `edges` (array)
- Invalid JSON returns a `400` error

Runtime validations:
- A workflow with no `trigger` node returns "No trigger node in workflow"
- A workflow with no nodes returns "Workflow is empty"
- Cycles are detected and prevented (max 50 node executions)

---

## Workflow Runs

Every workflow execution is recorded:

| Field | Description |
|-------|-------------|
| `status` | `running`, `completed`, or `error` |
| `nodes_executed` | How many nodes were executed |
| `nodes_total` | Total nodes in the workflow |
| `execution_log` | Details of each node's execution and timing |
| `error_message` | Error details if the workflow failed |
| `duration_ms` | Total execution time |

Use the [Testing & Debugging](/bots/testing) page to learn how to inspect workflow runs.

---

## Combining AI and Deterministic Logic

Workflows and agent loop are mutually exclusive per message. But `llm_call` nodes within a workflow still use the bot's configured LLM — you get AI reasoning at specific points while maintaining deterministic flow control.

**Example pattern:**
1. `trigger` on message
2. `llm_call` to classify intent (AI decides the category)
3. `logic` to branch (deterministic routing)
4. `mcp_tool` to fetch data (guaranteed execution)
5. `llm_call` to generate a response using the data (AI writes the reply)
6. `send_message` to the user

This gives you the best of both: structured flow with AI-powered decision-making where it matters.

---

## Next Steps

- [Node Types](/bots/workflows/node-types) — All 14 node types with configuration and examples
- [Variables & Conditions](/bots/workflows/variables) — Variable interpolation and condition syntax
- [Workflow Recipes](/bots/workflows/examples) — 6 complete workflow examples
- [Human Handoffs](/bots/handoffs) — Dedicated guide to the human_handoff action
- [Bot Teams](/bots/bot-teams) — Dedicated guide to delegate_to_bot
