Skip to main content
Building on the multi-step flow, this example adds lifecycle actions to create a polished user experience with welcome messages and progress tracking.

Objective

In this example, you’ll learn:
  • How to use on.enter hooks to execute actions when entering a step
  • How to use the say action for verbatim text delivery
  • How to use the set action to initialize variables
  • How to use the inc action to track progress with counters
  • How to combine multiple actions in lifecycle hooks

The Scenario

Your contact form from Example 3 works, but feels robotic—no greeting, no sense of progress. You want to:
  1. Welcome users at the start
  2. Show progress indicators (“Step 1 of 3”, “Step 2 of 3”, etc.)
  3. Track how many steps have been completed
This creates a friendlier, more professional experience.

Implementation

Here’s the complete tool definition:
{
  "type": "context",
  "context": {
    "task": {
      "type": "steps",
      "version": "v1alpha",
      "id": "contact-form-with-lifecycle",
      "tool": {
        "name": "submit_contact_info",
        "description": "Submit contact form information with progress tracking"
      },
      "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"}
            ]
          }
        }
      ]
    }
  },
  "tool": {
    "type": "function",
    "function": {
      "name": "contact_form_lifecycle_workflow",
      "description": "Multi-step workflow with lifecycle actions",
      "parameters": {
        "type": "object",
        "properties": {},
        "required": []
      }
    }
  }
}

Key Concepts

The on.enter Hook

The on.enter hook executes when the workflow enters a step, before the agent starts collecting inputs:
"on": {
  "enter": [
    {"action": "say", "text": "Welcome! Step 1 of 3."},
    {"action": "set", "name": "local.steps_completed", "value": 0}
  ]
}
Use on.enter for:
  • Welcome messages and progress indicators
  • Initializing step-local variables
  • Pre-populating inputs from existing data

The on.submit Hook

The on.submit hook executes after validation passes, before evaluating transitions:
"on": {
  "submit": [
    {"action": "save"},
    {"action": "inc", "name": "local.steps_completed"}
  ]
}
Use on.submit for:
  • Persisting inputs to global variables
  • Updating counters
  • Triggering side effects (like tool calls)

The say Action

The say action queues text that the agent must include verbatim:
{"action": "say", "text": "Welcome! Let's collect your contact information."}
The agent receives this text as part of the step instructions and includes it in the response. This significantly improves verbatim compliance compared to prescribing exact wording only in the main prompt, though the agent may still introduce minor variations.

The set Action

The set action initializes or updates a variable:
{"action": "set", "name": "local.steps_completed", "value": 0}
Variables can be:
  • Task-local (local.*): Scoped to this workflow instance
  • Global (no prefix): Shared across the conversation

The inc Action

The inc action increments a numeric counter:
{"action": "inc", "name": "local.steps_completed"}
If the variable doesn’t exist, it’s created with value 0 before incrementing. Optional: specify a custom increment amount:
{"action": "inc", "name": "local.score", "by": 10}

Enum Constraints

The COLLECT_TIME step uses an enum to constrain the contact time:
{
  "name": "contact_time",
  "type": "string",
  "enum": ["morning", "afternoon", "evening", "night"]
}
This forces the agent to normalize user input:
  • “8am” → “morning”
  • “after lunch” → “afternoon”
  • “around 7pm” → “evening”
This normalization is essential for reliable conditional branching in Example 5.

How It Works

Here’s the conversation flow:
User: "Hello"

[Platform initializes workflow, enters COLLECT_NAME]
  → on.enter executes:
    - say: "Welcome! Let's collect your contact information. Step 1 of 3."
    - set: local.steps_completed = 0

Agent: "Welcome! Let's collect your contact information. Step 1 of 3. What is your name?"

User: "My name is Alice"

[Agent calls submit_contact_info(user_name="Alice")]
  → Validation passes
  → on.submit executes:
    - save: user_name → global
    - inc: local.steps_completed → 1
  → Transition to COLLECT_EMAIL
  → on.enter executes:
    - say: "Great! Step 2 of 3."

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

User: "[email protected]"

[Agent calls submit_contact_info(user_email="[email protected]")]
  → on.submit: save, inc (counter → 2)
  → Transition to COLLECT_TIME
  → on.enter: say "Almost done! Step 3 of 3."

Agent: "Almost done! Step 3 of 3. What is your preferred contact time?"

User: "8am"

[Agent normalizes "8am" to "morning" based on enum]
[Agent calls submit_contact_info(contact_time="morning")]
  → on.submit: save, inc (counter → 3)
  → No next step (terminal)
  → Workflow completes

Agent: "Thank you! I've recorded your contact information."

State After Each Step

After COLLECT_NAME:
{
  "local.steps_completed": 1,
  "user_name": "Alice"
}
After COLLECT_EMAIL:
{
  "local.steps_completed": 2,
  "user_name": "Alice",
  "user_email": "[email protected]"
}
After COLLECT_TIME:
{
  "local.steps_completed": 3,
  "user_name": "Alice",
  "user_email": "[email protected]",
  "contact_time": "morning"
}

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. Start a conversation and say “hello”
  4. Notice the welcome message and progress indicator
  5. Complete all three steps
  6. Observe how the agent normalizes contact time to one of the enum values
Test the enum: Try saying “8am” or “after lunch” and see how the agent maps it to “morning” or “afternoon”.

What’s Next

This example collects contact time but treats all preferences the same. In Example 5: Conditional Branching, you’ll learn how to:
  • Route to different steps based on user input
  • Use JMESPath expressions for conditions
  • Create parallel workflow paths