Skip to main content
The final tutorial example demonstrates external tool integration—calling APIs during workflow execution and controlling which tools are available at each step.

Objective

In this example, you’ll learn:
  • How to use the call action to invoke external tools
  • How to use tools.allow for progressive tool disclosure
  • How to use tools.call to force tool execution
  • How to integrate external validation and notifications into your workflow

The Scenario

Your contact form needs to integrate with external systems:
  1. Validate email domains using an external API before accepting
  2. Send a notification to CRM when the form is completed
  3. Restrict tool access during sensitive data collection steps
This creates a production-ready workflow with real integrations.

Implementation

Here’s the complete tool definition:
{
  "type": "context",
  "context": {
    "task": {
      "type": "steps",
      "version": "v1alpha",
      "id": "contact-form-with-integration",
      "tool": {
        "name": "submit_contact_info",
        "description": "Submit contact form information with external integrations"
      },
      "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
            }
          ],
          "tools": {
            "allow": ["submit_contact_info"]
          },
          "on": {
            "enter": [
              {"action": "say", "text": "Welcome! Let's collect your contact information."}
            ],
            "submit": [
              {"action": "save"}
            ]
          },
          "next": [{"id": "COLLECT_EMAIL"}]
        },
        {
          "id": "COLLECT_EMAIL",
          "goal": "Collect and validate 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.",
            "The system will validate the email domain automatically."
          ],
          "inputs": [
            {
              "name": "user_email",
              "type": "string",
              "description": "The user's email address",
              "required": true
            }
          ],
          "tools": {
            "allow": ["submit_contact_info", "validate_email_domain"]
          },
          "on": {
            "enter": [
              {"action": "set", "name": "local.email_validated", "value": false}
            ],
            "submit": [
              {
                "action": "call",
                "name": "validate_email_domain",
                "arguments": {
                  "email": "{{inputs.user_email}}"
                }
              },
              {"action": "save"}
            ]
          },
          "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"]
            }
          ],
          "tools": {
            "allow": ["submit_contact_info"]
          },
          "on": {
            "submit": [
              {"action": "save"}
            ]
          },
          "next": [{"id": "CONFIRM_AND_NOTIFY"}]
        },
        {
          "id": "CONFIRM_AND_NOTIFY",
          "goal": "Confirm information and send CRM notification",
          "instructions": [
            "Summarize the collected information for the user:",
            "- Name: {{user_name}}",
            "- Email: {{user_email}}",
            "- Preferred contact time: {{contact_time}}",
            "Ask them to confirm, then call the submit_contact_info tool to complete."
          ],
          "inputs": [],
          "tools": {
            "allow": ["submit_contact_info", "send_crm_notification"],
            "call": true
          },
          "on": {
            "enter": [
              {
                "action": "call",
                "name": "send_crm_notification",
                "arguments": {
                  "name": "{{user_name}}",
                  "email": "{{user_email}}",
                  "contact_time": "{{contact_time}}",
                  "source": "contact_form_workflow"
                }
              }
            ]
          }
        }
      ]
    }
  },
  "tool": {
    "type": "function",
    "function": {
      "name": "contact_form_integration_workflow",
      "description": "Multi-step workflow with external tool integration",
      "parameters": {
        "type": "object",
        "properties": {},
        "required": []
      }
    }
  }
}

Key Concepts

The call Action

The call action queues an external tool call to be executed:
{
  "action": "call",
  "name": "validate_email_domain",
  "arguments": {
    "email": "{{inputs.user_email}}"
  }
}
How it works:
  1. The action is queued during lifecycle hook execution
  2. Arguments are template-rendered with current variables
  3. The tool call is injected into the conversation
  4. The tool executes and returns a response
  5. The agent sees the response and can react accordingly
Template variables in arguments:
  • {{inputs.user_email}} — Current step input
  • {{user_name}} — Global variable (saved from earlier step)
  • {{local.counter}} — Task-local variable

Progressive Tool Disclosure with tools.allow

Control which tools are visible at each step:
// COLLECT_NAME: Only submit tool (no external tools needed)
"tools": {
  "allow": ["submit_contact_info"]
}

// COLLECT_EMAIL: Submit + email validation tool
"tools": {
  "allow": ["submit_contact_info", "validate_email_domain"]
}

// CONFIRM_AND_NOTIFY: Submit + CRM notification tool
"tools": {
  "allow": ["submit_contact_info", "send_crm_notification"]
}
Benefits:
  • Security: Prevent access to sensitive tools during early steps
  • Focus: Agent only sees relevant tools for the current task
  • Progressive disclosure: Unlock capabilities as the workflow advances
Note: The submit tool is always available, regardless of tools.allow.

Forcing Tool Calls with tools.call

The tools.call: true setting forces the agent to make a tool call:
{
  "id": "CONFIRM_AND_NOTIFY",
  "tools": {
    "allow": ["submit_contact_info", "send_crm_notification"],
    "call": true
  },
  "on": {
    "enter": [
      {"action": "call", "name": "send_crm_notification", "arguments": {...}}
    ]
  }
}
What happens:
  1. Step is entered, on.enter executes
  2. call action queues the CRM notification
  3. tools.call: true sets tool_choice: required for the agent
  4. Agent must make a tool call (the queued one executes)
  5. Agent receives the tool response and can continue
Use cases:
  • Fetching required data before proceeding
  • Triggering mandatory side effects (notifications, logging)
  • Ensuring external validation happens

External Tool Assumptions

This example assumes two external tools are available: 1. validate_email_domain
{
  "name": "validate_email_domain",
  "description": "Validates that an email domain exists and accepts mail",
  "parameters": {
    "properties": {
      "email": {"type": "string", "description": "Email to validate"}
    },
    "required": ["email"]
  }
}
Returns: {"valid": true/false, "domain": "example.com", "message": "..."} 2. send_crm_notification
{
  "name": "send_crm_notification",
  "description": "Sends a lead notification to the CRM system",
  "parameters": {
    "properties": {
      "name": {"type": "string"},
      "email": {"type": "string"},
      "contact_time": {"type": "string"},
      "source": {"type": "string"}
    },
    "required": ["name", "email"]
  }
}
Returns: {"success": true, "lead_id": "12345"}

How It Works

Step-by-Step Flow

User: "Hello"

[Platform enters COLLECT_NAME]
  → tools.allow: only submit_contact_info visible
  → on.enter: say "Welcome!"

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

User: "Alice"

[Agent calls submit_contact_info(user_name="Alice")]
  → on.submit: save user_name
  → Transition to COLLECT_EMAIL
  → tools.allow: submit + validate_email_domain visible

Agent: "What is your email address?"

User: "[email protected]"

[Agent calls submit_contact_info(user_email="[email protected]")]
  → on.submit: call validate_email_domain(email="[email protected]")
  → External tool validates domain
  → on.submit: save user_email
  → Transition to COLLECT_TIME

Agent: "Great, your email has been validated. What is your preferred contact time?"

User: "morning"

[Agent calls submit_contact_info(contact_time="morning")]
  → on.submit: save contact_time
  → Transition to CONFIRM_AND_NOTIFY
  → on.enter: call send_crm_notification(...)
  → CRM receives notification
  → tools.call: true forces tool execution

Agent: "I've recorded your information and notified our team. Here's what we have:
- Name: Alice
- Email: [email protected]
- Preferred contact time: morning

Is this correct?"

User: "Yes"

[Agent calls submit_contact_info()]
  → No next step (terminal)
  → Workflow completes

Agent: "Thank you! We'll be in touch soon."

Tool Visibility Per Step

StepAvailable Tools
COLLECT_NAMEsubmit_contact_info
COLLECT_EMAILsubmit_contact_info, validate_email_domain
COLLECT_TIMEsubmit_contact_info
CONFIRM_AND_NOTIFYsubmit_contact_info, send_crm_notification

Best Practices

1. Minimize Tool Access

Only expose tools that are needed for the current step:
// Good: Restricted to what's needed
"tools": {"allow": ["submit_contact_info", "lookup_patient"]}

// Avoid: All tools visible when only one is needed
"tools": {}  // null = all tools

2. Use call for Mandatory Operations

When a tool must be called (validation, notification), use the call action:
"on": {
  "submit": [
    {"action": "call", "name": "log_audit_event", "arguments": {...}}
  ]
}

3. Combine tools.call with on.enter for Data Fetching

When you need data before the step can proceed:
{
  "id": "SHOW_ACCOUNT_BALANCE",
  "tools": {"call": true},
  "on": {
    "enter": [
      {"action": "call", "name": "get_account_balance", "arguments": {"id": "{{account_id}}"}}
    ]
  }
}

4. Handle Tool Failures Gracefully

External tools can fail. Consider adding error handling steps:
"next": [
  {"if": "local.validation_passed", "id": "NEXT_STEP"},
  {"if": "local.validation_failed", "id": "HANDLE_ERROR"},
  {"id": "RETRY"}
]

Try It

To test this workflow, you’ll need:
  1. The workflow JSON above assigned to an agent
  2. External tools (validate_email_domain, send_crm_notification) available in your environment
Note: If external tools aren’t available, you can modify the example to remove the call actions and test the tools.allow behavior independently.

Summary

Congratulations! You’ve completed the Step Workflows tutorial. You now know how to:
  1. Create basic workflows with steps and terminal states (Example 1)
  2. Collect and validate inputs with automatic schema generation (Example 2)
  3. Chain multiple steps with transitions and data persistence (Example 3)
  4. Add polish with lifecycle actions, say messages, and counters (Example 4)
  5. Route conditionally based on user input (Example 5)
  6. Handle validation failures with retry loops (Example 6)
  7. Integrate external tools with progressive disclosure (Example 7)
For complete reference documentation, see Step Workflow Reference.