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

# Decide node

> Route workflows with AI or custom logic

Routes the workflow to different paths based on AI analysis or your own routing logic.

## Decision modes

Choose between two decision types:

* **AI-Powered**: AI interprets user intent from conversation
* **Function (Custom Code)**: Run your own routing logic - business rules, data checks, API calls, etc.

## Configuration

### Common parameters

* `id`: Unique node identifier
* `conditions`: List of possible paths with labels and descriptions

### AI-powered mode

* `decision_type`: Set to `"ai"` (default)
* `provider_model_name`: AI model to use for decision making
* `llm_temperature`: Model creativity, 0.0-1.0 (default: 0.0)
* `llm_max_tokens`: Maximum tokens for response (default: 10000)

### Function mode

* `decision_type`: Set to `"function"`
* `function_id`: ID of the deployed Kapso Function to execute

## Workflow library example

AI decision:

```javascript theme={null}
workflow.addNode("classify_intent", {
  type: "decide",
  decisionType: "ai",
  providerModel: "gpt-5-mini",
  conditions: [
    { label: "sales", description: "The user wants to buy or compare plans" },
    { label: "support", description: "The user needs help with an existing account" }
  ]
});

workflow.addEdge("classify_intent", "sales_reply", { label: "sales" });
workflow.addEdge("classify_intent", "support_reply", { label: "support" });
```

Function decision:

```javascript theme={null}
workflow.addNode("route_with_code", {
  type: "decide",
  decisionType: "function",
  functionSlug: "classify-message",
  conditions: [
    { label: "urgent", description: "Needs immediate human attention" },
    { label: "normal", description: "Can be handled automatically" }
  ]
});
```

## How it works

### AI-powered mode

1. **Analyzes conversation**: Reviews recent WhatsApp message history
2. **Evaluates conditions**: Uses AI to match user intent against condition descriptions
3. **Returns label**: Chooses the best matching condition label for routing
4. **Routes workflow**: Uses the label to follow the matching outgoing edge
5. **Fallback**: Uses first condition if AI evaluation fails

### Function mode

1. **Executes function**: Calls your deployed Kapso Function with current workflow state
2. **Your logic runs**: Function executes your custom logic - check data, call APIs, apply rules, etc.
3. **Returns decision**: Function returns JSON with `next_edge` set to a condition label
4. **Routes workflow**: Uses the returned label to follow the matching outgoing edge
5. **Updates variables**: Optionally updates workflow variables if function returns `vars`

**Important**: Condition labels must exactly match your outgoing edge labels for proper routing in both modes.

## Function decision contract

When using function mode, your function receives the standard execution payload plus an `available_edges` array containing all possible edge labels from your conditions.

### Request payload

```typescript theme={null}
{
  execution_context: {
    vars: {...},           // Workflow variables
    system: {...},         // System data
    context: {...},        // Execution context
    metadata: {...}        // Metadata
  },
  flow_events: [...],      // Recent workflow events (last 10)
  flow_info: {
    id: "flow_abc123",     // Flow ID
    name: "My Flow",       // Flow name
    step_id: "step_xyz"    // Current step ID
  },
  available_edges: [       // Condition labels from your DecideNode
    "premium",
    "standard",
    "trial"
  ],
  whatsapp_context: {      // Only present when triggered by WhatsApp
    conversation: {
      id: "conv_123",
      phone_number: "+1234567890",
      status: "open",
      last_active_at: "2024-01-15T10:30:00Z",
      whatsapp_config_id: "config_abc",
      metadata: {},
      created_at: "2024-01-10T08:00:00Z",
      updated_at: "2024-01-15T10:30:00Z"
    },
    messages: [            // All messages, ordered by created_at (oldest first)
      {
        id: "msg_001",
        message_type: "text",          // text, image, video, document, audio, location, interactive, template, reaction, contacts
        content: "Hello!",
        direction: "inbound",          // inbound (from user), outbound (from bot)
        status: "delivered",
        processing_status: "processed",
        whatsapp_message_id: "wamid_abc",
        origin: "cloud_api",
        phone_number: "+1234567890",
        has_media: false,
        reply_option_id: null,         // Button/list ID when user clicks interactive reply
        reply_option_title: null,      // Button/list title when user clicks interactive reply
        interactive_type: null,        // button_reply, list_reply, nfm_reply (for interactive responses)
        interactive_data: null,        // Full interactive payload
        created_at: "2024-01-15T10:00:00Z",
        updated_at: "2024-01-15T10:00:00Z"
      }
    ]
  }
}
```

### Response format

Your function must return JSON with `next_edge` set to one of the labels from `available_edges`:

```typescript theme={null}
{
  next_edge: "premium",    // REQUIRED: Must match a condition label
  vars: {                  // OPTIONAL: Updated workflow variables
    customer_tier: "premium",
    discount_applied: true
  }
}
```

### Example function

```javascript theme={null}
async function handler(request, env) {
  const body = await request.json();
  const availableEdges = body?.available_edges || [];
  const executionContext = body?.execution_context || {};
  const vars = executionContext.vars || {};
  const context = executionContext.context || {};

  // Your custom routing logic - can be anything:

  // 1. Business rules
  const accountAge = vars.account_age_days || 0;
  const totalSpent = vars.lifetime_value || 0;

  // 2. External API check (optional)
  // const res = await fetch(`https://api.example.com/users/${context.phone_number}/tier`);
  // const userTier = await res.json();

  // 3. Time-based logic
  // const hour = new Date().getHours();
  // if (hour < 9 || hour > 17) nextEdge = "closed";

  let nextEdge = availableEdges[0] || "default";

  if (totalSpent > 1000 && accountAge > 365) {
    nextEdge = "premium";
  } else if (accountAge > 30) {
    nextEdge = "standard";
  } else {
    nextEdge = "trial";
  }

  return new Response(JSON.stringify({
    next_edge: nextEdge,
    vars: {
      customer_tier: nextEdge,
      routing_timestamp: new Date().toISOString()
    }
  }), {
    headers: { "Content-Type": "application/json" }
  });
}
```

### Accessing WhatsApp data

When your flow is triggered by a WhatsApp message, use `whatsapp_context` to access conversation data:

```javascript theme={null}
async function handler(request, env) {
  const body = await request.json();
  const availableEdges = body?.available_edges || [];
  const whatsappContext = body?.whatsapp_context;

  // Guard: ensure WhatsApp context exists
  if (!whatsappContext) {
    return new Response(JSON.stringify({
      next_edge: availableEdges[0]
    }), { headers: { "Content-Type": "application/json" } });
  }

  const { conversation, messages } = whatsappContext;

  // Get user's phone number
  const userPhone = conversation.phone_number;

  // Get the last message
  const lastMessage = messages[messages.length - 1];
  const lastContent = lastMessage?.content || "";
  const lastDirection = lastMessage?.direction; // "inbound" or "outbound"

  // Count inbound messages
  const userMessages = messages.filter(m => m.direction === "inbound");

  // Check for media
  const hasMedia = messages.some(m => m.has_media);

  // Access interactive reply data (button/list clicks)
  const buttonId = lastMessage?.reply_option_id;
  const buttonTitle = lastMessage?.reply_option_title;

  // Route based on conversation state
  let nextEdge = "returning_user";
  if (userMessages.length < 2) {
    nextEdge = "new_user";
  }

  return new Response(JSON.stringify({
    next_edge: nextEdge,
    vars: { user_phone: userPhone }
  }), { headers: { "Content-Type": "application/json" } });
}
```

### Routing by button ID

Common pattern: route based on which button the user clicked.

```javascript theme={null}
async function handler(request, env) {
  const body = await request.json();
  const availableEdges = body?.available_edges || [];
  const messages = body?.whatsapp_context?.messages || [];

  // Get the last inbound message
  const lastInbound = [...messages].reverse().find(m => m.direction === "inbound");

  // Get button ID from interactive reply
  const buttonId = lastInbound?.reply_option_id;

  // Route by button ID (button IDs often match edge labels)
  let nextEdge = availableEdges[0];
  if (buttonId && availableEdges.includes(buttonId)) {
    nextEdge = buttonId;
  }

  return new Response(JSON.stringify({
    next_edge: nextEdge,
    vars: { selected_button: buttonId }
  }), { headers: { "Content-Type": "application/json" } });
}
```

**Tip**: Set your button `id` values to match your condition labels for direct routing.

## Usage patterns

**Question → Wait → Decide → Route**

```mermaid theme={null}
graph LR
    A[Send text<br/>Ask question] --> B[Wait for response]
    B --> C[Decide<br/>Route by intent]
    C --> D[Send text<br/>Support path]
    C --> E[Send text<br/>Sales path]
```

**Interactive → Wait → Decide**

```mermaid theme={null}
graph LR
    A[Send interactive<br/>Menu options] --> B[Wait for response]
    B --> C[Decide<br/>Route by selection]
```
