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

# Example 4: Lifecycle Actions

> Add polish with welcome messages, progress indicators, and counters using lifecycle hooks

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:

```json theme={null}
{
  "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:

```json theme={null}
"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:

```json theme={null}
"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:

```json theme={null}
{"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:

```json theme={null}
{"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:

```json theme={null}
{"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:

```json theme={null}
{"action": "inc", "name": "local.score", "by": 10}
```

### Enum Constraints

The `COLLECT_TIME` step uses an `enum` to constrain the contact time:

```json theme={null}
{
  "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:**

```json theme={null}
{
  "local.steps_completed": 1,
  "user_name": "Alice"
}
```

**After COLLECT\_EMAIL:**

```json theme={null}
{
  "local.steps_completed": 2,
  "user_name": "Alice",
  "user_email": "alice@example.com"
}
```

**After COLLECT\_TIME:**

```json theme={null}
{
  "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:

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](./example-05-conditional), you'll learn how to:

* Route to different steps based on user input
* Use JMESPath expressions for conditions
* Create parallel workflow paths
