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.
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:
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:
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
- Analyzes conversation: Reviews recent WhatsApp message history
- Evaluates conditions: Uses AI to match user intent against condition descriptions
- Returns label: Chooses the best matching condition label for routing
- Routes workflow: Uses the label to follow the matching outgoing edge
- Fallback: Uses first condition if AI evaluation fails
Function mode
- Executes function: Calls your deployed Kapso Function with current workflow state
- Your logic runs: Function executes your custom logic - check data, call APIs, apply rules, etc.
- Returns decision: Function returns JSON with
next_edge set to a condition label
- Routes workflow: Uses the returned label to follow the matching outgoing edge
- 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
{
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"
}
]
}
}
Your function must return JSON with next_edge set to one of the labels from available_edges:
{
next_edge: "premium", // REQUIRED: Must match a condition label
vars: { // OPTIONAL: Updated workflow variables
customer_tier: "premium",
discount_applied: true
}
}
Example function
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:
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" } });
}
Common pattern: route based on which button the user clicked.
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
Interactive → Wait → Decide