Example 4: Lifecycle Actions
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.enterhooks to execute actions when entering a step - How to use the
sayaction for verbatim text delivery - How to use the
setaction to initialize variables - How to use the
incaction 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:
- Welcome users at the start
- Show progress indicators ("Step 1 of 3", "Step 2 of 3", etc.)
- 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: "alice@example.com"
[Agent calls submit_contact_info(user_email="alice@example.com")]
→ 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": "alice@example.com"
}
After COLLECT_TIME:
{
"local.steps_completed": 3,
"user_name": "Alice",
"user_email": "alice@example.com",
"contact_time": "morning"
}
Try It
To test this workflow in the Syllable Console:
- Create a new tool with the JSON above
- Assign it to an agent
- Start a conversation and say "hello"
- Notice the welcome message and progress indicator
- Complete all three steps
- 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

