Skip to main content
Building on lifecycle actions, this example adds smart routing—different follow-up paths based on user preferences.

Objective

In this example, you’ll learn:
  • How to use conditional next entries for branching logic
  • How to write JMESPath expressions for conditions
  • How to create parallel workflow paths with different endings
  • The importance of fallback routes

The Scenario

Your contact form collects the user’s preferred contact time. Now you want to:
  1. Route morning/afternoon preferences → schedule a phone call
  2. Route evening/night preferences → send an email follow-up
This creates personalized experiences based on user input.

Implementation

Here’s the complete tool definition:
{
  "type": "context",
  "context": {
    "task": {
      "type": "steps",
      "version": "v1alpha",
      "id": "contact-form-with-branching",
      "tool": {
        "name": "submit_contact_info",
        "description": "Submit contact form information with conditional routing"
      },
      "steps": [
        {
          "id": "COLLECT_NAME",
          "goal": "Collect the user's name",
          "instructions": [
            "Ask the user for their name.",
            "Once you have their name, call the submit_contact_info tool with the user_name parameter."
          ],
          "inputs": [
            {
              "name": "user_name",
              "type": "string",
              "description": "The user's name",
              "required": true
            }
          ],
          "on": {
            "enter": [
              {"action": "say", "text": "Welcome! Let's collect your contact information. Step 1 of 3."},
              {"action": "set", "name": "local.steps_completed", "value": 0}
            ],
            "submit": [
              {"action": "save"},
              {"action": "inc", "name": "local.steps_completed"}
            ]
          },
          "next": [{"id": "COLLECT_EMAIL"}]
        },
        {
          "id": "COLLECT_EMAIL",
          "goal": "Collect the user's email address",
          "instructions": [
            "Ask the user for their email address.",
            "Once you have their email, call the submit_contact_info tool with the user_email parameter."
          ],
          "inputs": [
            {
              "name": "user_email",
              "type": "string",
              "description": "The user's email address",
              "required": true
            }
          ],
          "on": {
            "enter": [{"action": "say", "text": "Great! Step 2 of 3."}],
            "submit": [
              {"action": "save"},
              {"action": "inc", "name": "local.steps_completed"}
            ]
          },
          "next": [{"id": "COLLECT_TIME"}]
        },
        {
          "id": "COLLECT_TIME",
          "goal": "Collect the user's preferred contact time",
          "instructions": [
            "Ask the user for their preferred contact time.",
            "Once you have their preference, call the submit_contact_info tool with the contact_time parameter."
          ],
          "inputs": [
            {
              "name": "contact_time",
              "type": "string",
              "description": "The user's preferred contact time",
              "required": true,
              "enum": ["morning", "afternoon", "evening", "night"]
            }
          ],
          "on": {
            "enter": [{"action": "say", "text": "Almost done! Step 3 of 3."}],
            "submit": [
              {"action": "save"},
              {"action": "inc", "name": "local.steps_completed"}
            ]
          },
          "next": [
            {
              "if": "contact_time == 'morning' || contact_time == 'afternoon'",
              "id": "SCHEDULE_CALL"
            },
            {
              "if": "contact_time == 'evening' || contact_time == 'night'",
              "id": "SEND_EMAIL"
            },
            {
              "id": "SEND_EMAIL"
            }
          ]
        },
        {
          "id": "SCHEDULE_CALL",
          "goal": "Confirm phone follow-up for daytime contact preference",
          "instructions": [
            "Inform the user that we'll call them during business hours (morning or afternoon).",
            "Thank them for providing their information.",
            "Call the submit_contact_info tool to complete this step."
          ],
          "inputs": [],
          "next": []
        },
        {
          "id": "SEND_EMAIL",
          "goal": "Confirm email follow-up for evening/night contact preference",
          "instructions": [
            "Inform the user that we'll send them an email since they prefer evening or night contact.",
            "Thank them for providing their information.",
            "Call the submit_contact_info tool to complete this step."
          ],
          "inputs": [],
          "next": []
        }
      ]
    }
  },
  "tool": {
    "type": "function",
    "function": {
      "name": "contact_form_branching_workflow",
      "description": "Multi-step workflow with conditional branching",
      "parameters": {
        "type": "object",
        "properties": {},
        "required": []
      }
    }
  }
}

Key Concepts

Conditional next Entries

The next array can contain multiple entries with conditions:
"next": [
  {
    "if": "contact_time == 'morning' || contact_time == 'afternoon'",
    "id": "SCHEDULE_CALL"
  },
  {
    "if": "contact_time == 'evening' || contact_time == 'night'",
    "id": "SEND_EMAIL"
  },
  {
    "id": "SEND_EMAIL"
  }
]
Evaluation rules:
  1. Entries are evaluated in order (top to bottom)
  2. The first entry whose condition evaluates to true wins
  3. An entry without if is a fallback (always matches)
  4. If no entries match, the workflow cannot proceed (error state)

JMESPath Expressions

JMESPath is the default expression language for conditions. Common patterns:
// String comparison
"if": "contact_time == 'morning'"

// Logical OR
"if": "contact_time == 'morning' || contact_time == 'afternoon'"

// Logical AND
"if": "is_vip && has_consent"

// Negation
"if": "!opted_out"

// Numeric comparison (note backticks for literals)
"if": "local.attempts >= `3`"
Important: In JMESPath, boolean and numeric literals require backticks:
  • flag == \true`(notflag == true`)
  • count >= \3`(notcount >= 3`)

Variable Access in Conditions

After on.submit actions execute, you can access:
VariableAccess Pattern
Saved global variablescontact_time (directly by name)
Task-local variableslocal.steps_completed
Current step inputsinputs.contact_time
The save action runs before transition evaluation, so contact_time is available for routing decisions.

Fallback Routes

Always include a fallback route (no if condition) as the last entry:
"next": [
  {"if": "condition1", "id": "STEP_A"},
  {"if": "condition2", "id": "STEP_B"},
  {"id": "DEFAULT_STEP"}  // Catches everything else
]
This prevents the workflow from getting stuck if none of the conditions match.

Multiple Terminal Steps

This example has two terminal steps (SCHEDULE_CALL and SEND_EMAIL), each with:
  • Unique instructions tailored to that path
  • No inputs (confirmation only)
  • Empty next: [] (terminal)
The user’s journey ends differently based on their preference:
User prefers morning → SCHEDULE_CALL → "We'll call you during business hours"
User prefers evening → SEND_EMAIL → "We'll send you an email"

How It Works

Path A: Morning/Afternoon Preference

User: "Hello"
Agent: "Welcome! Step 1 of 3. What is your name?"

User: "Alice"
Agent: "Great! Step 2 of 3. What is your email address?"

User: "[email protected]"
Agent: "Almost done! Step 3 of 3. What is your preferred contact time?"

User: "morning"

[Agent calls submit_contact_info(contact_time="morning")]
  → on.submit: save contact_time
  → Evaluate next:
    - "morning == 'morning' || morning == 'afternoon'" → TRUE
  → Transition to SCHEDULE_CALL

Agent: "Thank you! We'll call you during business hours."

[Agent completes SCHEDULE_CALL]
  → Workflow ends

Path B: Evening/Night Preference

... (same first 3 steps) ...

User: "evening"

[Agent calls submit_contact_info(contact_time="evening")]
  → on.submit: save contact_time
  → Evaluate next:
    - "evening == 'morning' || evening == 'afternoon'" → FALSE
    - "evening == 'evening' || evening == 'night'" → TRUE
  → Transition to SEND_EMAIL

Agent: "Thank you! We'll send you an email since you prefer evening contact."

[Agent completes SEND_EMAIL]
  → Workflow ends

Workflow Structure

                    ┌─────────────────┐
                    │  COLLECT_NAME   │
                    └────────┬────────┘

                    ┌────────▼────────┐
                    │  COLLECT_EMAIL  │
                    └────────┬────────┘

                    ┌────────▼────────┐
                    │  COLLECT_TIME   │
                    └────────┬────────┘

           ┌─────────────────┴─────────────────┐
           │                                   │
    morning/afternoon                    evening/night
           │                                   │
    ┌──────▼──────┐                    ┌───────▼───────┐
    │ SCHEDULE_   │                    │  SEND_EMAIL   │
    │    CALL     │                    │               │
    └─────────────┘                    └───────────────┘
    (terminal)                          (terminal)

Try It

To test this workflow in the Syllable Console:
  1. Create a new tool with the JSON above
  2. Assign it to an agent
  3. Test both paths:
    • Say “morning” as your preferred time → expect phone call confirmation
    • Say “evening” as your preferred time → expect email confirmation
Verify the routing: Each path should deliver different confirmation messages based on the user’s preference.

What’s Next

This example routes based on valid input, but what if the user provides invalid data? In Example 6: Retry Loop, you’ll learn how to:
  • Validate email format before proceeding
  • Allow retry attempts when validation fails
  • Use counters to limit retries
  • Loop back to the same step on failure