Skip to main content
Use a user activity node to define interactive steps in your agentic workflow where users provide or receive information in one of two ways:
  • In a multi-turn conversation, where the user provides or receives one piece of data at a time
  • Through a form, which prompts the user for several pieces of data in a single conversational turn
To add a user activity node to your agentic workflow, start by creating a userflow() object.

Multi-turn conversations user activity

To collect or deliver one piece of data at a time, call field() on the userflow() object. This method accepts the following input parameters:
name
string
required
Unique identifier for the node.
kind
UserFieldKind
required
Type of user activity. Supported types: Text, Date, DateTime, Time, Number, File, Boolean, Object, Choice.
display_name
string
Display name for the node.
description
string
Description of the node.
direction
string
required
Indicates whether the node is for input or output.
default
any
Default value for the node.
option
UserFieldOption
List of predefined options with their labels and values.
is_list
bool
Indicates whether the node accepts multiple values.
min
any
Minimum value or constraint.
max
any
Maximum value or constraint.
input_map
DataMap
Define input mappings using a structured collection of Assignment objects.
custom
dictionary
Dictionary for additional metadata or configuration.
Here is an example that shows how to use field():
Python
from typing import List
from pydantic import BaseModel, Field
from ibm_watsonx_orchestrate.flow_builder.flows import (
    Flow, flow, UserNode, START, END
)
from ibm_watsonx_orchestrate.flow_builder.types import Assignment, UserFieldKind
from ibm_watsonx_orchestrate.flow_builder.data_map import DataMap

class Name(BaseModel):
    """
    This class represents a person's name.

    Attributes:
        name (str): The person's first name.
    """
    first_name: str = Field(default="John Doe", description="First name")

@flow(
    name ="user_flow_example",
    display_name="user_flow_example",
    description="Example user flow.",
    input_schema=Name,
)
def build_user_flow(aflow: Flow = None) -> Flow:
    # user_flow which is a subflow to be added to the aflow
    user_flow = aflow.userflow()

    # add file upload
    user_node1 = user_flow.field(direction="input",name="upload", display_name="File upload 1",  kind=UserFieldKind.File)

    # add file download
    data_map = DataMap()
    data_map.add(Assignment(target_variable="self.input.value",value_expression="flow[\"userflow_1\"][\"File upload 1\"].output.value"))
    user_node2 = user_flow.field(direction="output",name="download", display_name="Download file",  kind=UserFieldKind.File, input_map=data_map)

    # add a Display user text field
    user_node3 = user_flow.field(direction="output",name="display_first_name", display_name="Display first name", kind=UserFieldKind.Text, text="Display of first name is {flow.input.first_name}")

    # add a text input field
    user_node4 = user_flow.field(direction="input",name="last_name", display_name="Last name",  kind=UserFieldKind.Text)
    
    # add a Number input field
    user_node5 = user_flow.field(direction="input",name="age", display_name="Age",  kind=UserFieldKind.Number)

    # create a data map to build an array to be assigned to a user field of kind List
    data_map = DataMap()
    data_map.add(Assignment(target_variable="self.input.value",value_expression="[\"Alice\", \"Bob\", \"Charlie\", \"Diana\", \"Ethan\", \"Fiona\", \"George\"]"))
    user_node6 = user_flow.field(direction="output",name="Friends", display_name="List of friends", kind=UserFieldKind.List, input_map=data_map)

    # add a Choice input field
    data_map_choices = DataMap()
    data_map_choices.add(Assignment(target_variable="self.input.choices",value_expression='["dog", "cat", "bird", "fish"]'))
    user_node7 = user_flow.field(direction="input",name="pet_choice",display_name="Select Your Pet",kind=UserFieldKind.Choice,text="Choose your favorite pet:",input_map=data_map_choices)
    
    # A user flow edges
    user_flow.edge(START, user_node1)
    user_flow.edge(user_node1, user_node2)
    user_flow.edge(user_node2, user_node3)
    user_flow.edge(user_node3, user_node4)
    user_flow.edge(user_node4, user_node5)
    user_flow.edge(user_node5, user_node6)
    user_flow.edge(user_node6, END)
    
    # add the user flow to the flow sequence to create the flow edges
    aflow.sequence(START, user_flow, END)

    return aflow

Forms user activity

To prompt for multiple pieces of data in a single turn, instantiate the form() object to create a form node. This method accepts the following input parameters:
name
string
required
The internal name of the form.
display_name
string
The display name shown on the form.
instructions
string
Instructions text for the form.
submit_button_label
string
The label for the submit button. Defaults to Submit if not set.
cancel_button_label
string
The label for the cancel button. If set to None, hides the button.

Forms fields

After instantiating the form object, add fields to it. Each field type requires its own configuration. Supported field types include:
Creates a text input field. Configure it as a single-line input or a multi-line text area.Parameters:
name
string
required
The internal name of the field.
label
string
Display label for the field.
required
bool
Whether the field is required. Defaults to False.
single_line
bool
Whether the field uses a single line. If False, creates a multi-line text area. Defaults to True.
placeholder_text
string
Placeholder text for the field.
help_text
string
Help text for the field.
default
any
Default value for the field, passed as DataMap.
regex
string
Regular expression pattern for input validation.
regex_error_message
string
Custom error message for failed regex validation. Defaults to: “Input does not match the required pattern”.
Creates a boolean input field. Render it as a checkbox or radio buttons.Parameters:
name
string
required
The internal name of the field.
label
string
Display label for the field.
single_checkbox
bool
Whether to display as a single checkbox. If False, displays as radio buttons. Defaults to True.
default
any
Default value for the field, passed as input_map.
true_label
string
Label for the true option. Defaults to True.
false_label
string
Label for the false option. Defaults to False.
Creates a date range input field with start and end date pickers.Parameters:
name
string
required
The internal name of the field.
label
string
Display label for the field.
required
bool
Whether the field is required. Defaults to False.
start_date_label
string
Label for the start date field.
end_date_label
string
Label for the end date field.
default_start
any
Default value for the start date, passed as DataMap.
default_end
any
Default value for the end date, passed as DataMap.
min_date
any
DataMap that sets the minimum date constraint and maps to self.input.min_date.
max_date
any
DataMap that sets the maximum date constraint and maps to self.input.max_date.
Creates a date input field.Parameters:
name
string
required
The internal name of the field.
label
string
Display label for the field.
required
bool
Whether the field is required. Defaults to False.
default
any
Default value for the field, passed as DataMap.
min_date
any
Minimum date value for the allowed range.
max_date
any
Maximum date value for the allowed range.
multiple_dates
bool
Whether the field supports selecting more than one date. Defaults to False.
Creates a datetime or time input field.
name
string
required
Internal name of the field.
label
string
Display label for the field.
required
bool
Whether the field is required. Defaults to False.
default
any
Default value for the field, provided through a DataMap.
min_time
any
Minimum allowed datetime or time value.
max_time
any
Maximum allowed datetime or time value.
inputType
UserFieldKind
Type of field, either DateTime or Time. Defaults to DateTime.
Creates a datetime or time range input field with start and end pickers.
name
string
required
Internal name of the field.
label
string
Display label for the field.
required
bool
Whether the field is required. Defaults to False.
start_date_label
string
Label for the start value.
end_date_label
string
Label for the end value.
default_start
any
Default start value, provided through a DataMap.
default_end
any
Default end value, provided through a DataMap.
min_time
any
Minimum allowed value for the range.
max_time
any
Maximum allowed value for the range.
Creates a number input field.Parameters:
name
string
required
The internal name of the field.
label
string
Display label for the field.
required
bool
Whether the field is required. Defaults to False.
is_integer
bool
Whether the field accepts only integers. If False, accepts decimal numbers. Defaults to True.
help_text
string
Help text for the field.
default
any
Default value for the field, passed as DataMap.
minimum
any
Minimum allowed value, passed as DataMap.
maximum
any
Maximum allowed value, passed as DataMap.
Creates a file upload field.Parameters:
name
string
required
The internal name of the field.
label
string
Display label for the field.
instructions
string
Instructions for the file upload.
required
bool
Whether the field is required. Defaults to False.
allow_multiple_files
bool
Whether multiple files can be uploaded. Defaults to False.
file_max_size
int
Maximum file size in MB. Defaults to 10.
supported_file_types
List[string]
List of supported file extensions, for example pdf and docx.
min_num_files
Any
Minimum number of files. Valid only when allow_multiple_files=True.
max_num_files
Any
Maximum number of files. Valid only when allow_multiple_files=True.
Creates a message output field to display static text.Parameters:
name
string
required
The internal name of the field.
label
string
Display label for the field.
message
string
The message text to display.
Creates a field output to display dynamic values.Parameters:
name
string
required
The internal name of the field.
label
string
Display label for the field.
value
any
The value to display, passed as DataMap.
Creates a list output field to display tabular data.Parameters:
name
string
required
The internal name of the field.
label
string
Display label for the field.
choices
any
The list of items to display, passed in a DataMap.
columns
dict[string, str]
Mapping of source property names to table column labels. Only those columns appear if present.
Creates a list input field to display tabular data.Parameters:
name
string
required
The internal name of the field.
label
string
Display label for the field.
isRowAddable
bool
Whether the user can add rows.
isRowDeletable
bool
Whether the user can delete rows.
default
any
The list of items to display, passed in a DataMap.
columns
dict[string, str]
Mapping of source property names to table column labels. Only those columns appear if present.
Creates a file download field.Parameters:
name
string
required
The internal name of the field.
label
string
Display label for the field.
value
any
The file to download, passed as DataMap.
Creates a single-choice input field. Display options as a dropdown or as radio buttons.Parameters:
name
string
required
The internal name of the field.
label
string
Display label for the field.
required
bool
Whether the field is required. Defaults to False.
choices
any
The list of available choices, passed as DataMap.
show_as_dropdown
bool
Whether to display options as a dropdown. If False, display as radio buttons. Defaults to True.
dropdown_item_column
string
Column name used for display text in dropdown.
placeholder_text
string
Placeholder text for the dropdown.
default
any
Default selected value, passed as DataMap.
columns
dict[string, str]
Mapping of source property names to display labels for complex choice objects.
Creates a multi-choice input field. Display options as a multi-select dropdown or as checkboxes.Parameters:
name
string
required
The internal name of the field.
label
string
Display label for the field.
required
bool
Whether the field is required. Defaults to False.
choices
any
The list of available choices, passed as DataMap.
show_as_dropdown
bool
Whether the field shows as a dropdown. If False, the field shows as checkboxes. Defaults to True.
dropdown_item_column
string
Column name for the display text in a dropdown.
placeholder_text
string
Placeholder text for the dropdown.
default
any
Default selected values, provided as a DataMap.
columns
dict[string, str]
Mapping of source property names to display labels for complex choice objects.
minItems
any
Minimum number of items the user must select.
maxItems
any
Maximum number of items the user can select.
Creates a user selection field in the form.Parameters:
name
string
required
Internal name of the field.
label
string
Display label for the field.
required
bool
Whether the field is required. Defaults to False.
multiple_users
bool
Whether the user can select more than one person. Defaults to False.
min_num_users
any
Minimum number of users to select. Applies only when multiple_users=True. Accepts an integer or a DataMap for dynamic configuration through expressions.
max_num_users
any
Maximum number of users to select. Applies only when multiple_users=True. Accepts an integer or a DataMap for dynamic configuration through expressions.
Here is an example that shows how to use form():
Python
from typing import List
from pydantic import BaseModel, Field
from ibm_watsonx_orchestrate.flow_builder.flows import (
    Flow, flow, UserNode, START, END
)
from ibm_watsonx_orchestrate.flow_builder.types import Assignment, UserFieldKind
from ibm_watsonx_orchestrate.flow_builder.data_map import DataMap

class Name(BaseModel):
    """
    This datatype represents a person's name.

    Attributes:
        name (str): The person's first name.
    """
    first_name: str = Field(default="John", description="First name")
    last_name: str = Field(default="Doe", description="Last name")

class Book(BaseModel):
    """
    This datatype represents a book.

    Attributes:
        title (str): The book's title.
        author (str): The book's author.
    """
    title: str = Field(default="Shaken", description="Book title")
    author: str = Field(default="John Grisham", description="Author")

class StringListNames(BaseModel):
    listOfNames: List[Name] = Field(
        default=[{"John", "Doe"}, {"Jane", "Doe"}, {"Jean", "Doe"}],
        description="A list of Name objects with first and last names."
    )

class MyDate(BaseModel):
    """
    This datatype represents date information.

    Attributes:
        dateStart (str): The start date.
        dateEnd (str): The end date.
        dateEvent (str): The event date.
    """
    dateStart: str = Field(default="2023-01-01", description="Start date")
    dateEnd: str = Field(default="2023-12-31", description="End date")
    dateEvent: str = Field(default="2023-06-15", description="Event date")

class FlowInput(BaseModel):
    salutations: List[str] = Field(
        default=["Mr", "Mrs"],
        description="A list of string salutations."
    )
    listOfLanguages: List[str] = Field(
        default=["java", "python", "typescript"],
        description="A list of languages."
    )
    salary_expectation: int = Field(
        default=200000,
        description="Expected salary as an integer number."
    )

    friends: StringListNames = Field(
        default=StringListNames(
            listOfNames=[
                Name(first_name="John", last_name="Doe"),
                Name(first_name="Jane", last_name="Doe"),
                Name(first_name="Jean", last_name="Doe")
            ]
        ),
        description="A list of friends with their names."
    )
    books: List[Book] = Field(
        default=[Book()],
        description="A list of books."
    )

    event_date: MyDate = Field(
        default=MyDate(),
        description="The event date"
    ),

    listOfFruits: List[str] = Field(
        default=["apple", "oranges", "bananas"],
        description="A list of fruits."
    )

    listOfPreferredFruits: List[str] = Field(
        default=["apple", "oranges"],
        description="A list of preferred fruits."
    )

@flow(
    name ="user_flow_application_form",
    display_name="Application form",
    description="Creates a sample application form.",
    input_schema=FlowInput,
)

def build_user_form(aflow: Flow = None) -> Flow:

    user_flow = aflow.userflow()
    user_flow.spec.display_name= "Application"

    # Create form with default submit button and visible cancel button
    user_node_with_form = user_flow.form(
        name="ApplicationForm",
        display_name="Application",
        cancel_button_label="Cancel"
    )
    
    data_map = DataMap()
    data_map.add(Assignment(target_variable="self.input.choices", value_expression="flow.input.salutations"))
 
    #Single-choice: Salutation
    user_node_with_form.single_choice_input_field(name="salutation", label="Salutation", required=True, choices=data_map, 
                                                  show_as_dropdown=True, placeholder_text="Please enter your title")
   
    #Boolean: Married
    user_node_with_form.boolean_input_field(name="married", label="Married", single_checkbox = True, true_label="Married", false_label="Not married")
    
    #Text: Last Name
    user_node_with_form.text_input_field(name="lastName", label="Last name", required=True, placeholder_text="Enter your name here", help_text="Enter name", 
        regex="^[a-zA-Z0-9\s]+$", regex_error_message="No special characters allowed")
    
    #Number: Age
    user_node_with_form.number_input_field(name="age", label="Age", required=True, help_text="Enter your age")

    data_map_salary = DataMap()
    data_map_salary.add(Assignment(target_variable="self.input.default", value_expression="flow.input.salary_expectation"))
    
    #Number: Desired Salary
    user_node_with_form.number_input_field(name="salary", label="Desired salary", is_integer=False, help_text="Your dream salary is here", default=data_map_salary)
  
    data_map_desired_salary = DataMap()
    data_map_desired_salary.add(Assignment(target_variable="self.input.value", value_expression="flow.input.salary_expectation"))

     #Field: Projected salary
    user_node_with_form.field_output_field(name="acknowledge", label="Projected salary", value = data_map_desired_salary)

    #FileUpload
    data_map_min_files = DataMap()
    data_map_min_files.add(Assignment(target_variable="self.input.min_num_files", value_expression="1"))

    data_map_max_files = DataMap()
    data_map_max_files.add(Assignment(target_variable="self.input.max_num_files", value_expression="2"))

    user_node_with_form.file_upload_field(name="credentials", label="Upload credentials", allow_multiple_files=True, file_max_size=256,
                                          min_num_files=data_map_min_files, max_num_files=data_map_max_files)

    #Date: End Date
    data_map_default_date = DataMap()
    data_map_default_date.add(Assignment(target_variable="self.input.default",value_expression="flow.input.event_date.dateEnd"))

    data_map_min_date = DataMap()
    data_map_min_date.add(Assignment(target_variable="self.input.min_date",value_expression="\"2026-01-05\""))
    
    data_map_max_date = DataMap()
    data_map_max_date.add(Assignment(target_variable="self.input.max_date",value_expression="\"2026-01-12\""))

    user_node_with_form.date_input_field(name="endDate", label="End Date", required=True, default=data_map_default_date, 
                                        min_date=data_map_min_date, max_date=data_map_max_date)
  
    data_map_list_source = DataMap()
    data_map_list_source.add(Assignment(target_variable="self.input.choices", value_expression="flow.input.listOfLanguages"))
    
    #Output List: Qualification
    user_node_with_form.list_output_field(name="strength", label="Qualification", choices=data_map_list_source)

    #Output list: Friends table
    data_map_list_friends = DataMap()
    data_map_list_friends.add(Assignment(target_variable="self.input.choices", value_expression="flow.input.friends.listOfNames"))
    user_node_with_form.list_output_field(name="friends", label="Friends", choices=data_map_list_friends, columns={"first_name": "First", "last_name": "Last"})

    #Mult-chocice: List of fruits dowpdown primitives
    data_map_multi_choice = DataMap()
    data_map_multi_choice.add(Assignment(target_variable="self.input.choices", value_expression="flow.input.listOfFruits"))

    data_map_multi_choice_default = DataMap()
    data_map_multi_choice_default.add(Assignment(target_variable="self.input.default", value_expression="flow.input.listOfPreferredFruits"))

    user_node_with_form.multi_choice_input_field(name="multi-choice", label="List of Fruits", required=False, choices=data_map_multi_choice, 
                                                  show_as_dropdown=True, placeholder_text="Please enter your choice", default=data_map_multi_choice_default,
                                                  minItems=1, maxItems=2)
    
    #Mult-chocice: Books dowpdown complex
    data_map_list_books = DataMap()
    data_map_list_books.add(Assignment(target_variable="self.input.choices", value_expression="flow.input.books"))
    user_node_with_form.multi_choice_input_field(name="multi-choice_as_dropdown_1", label="List of Books", required=False, choices=data_map_list_books, 
                                                  show_as_dropdown=True, dropdown_item_column="title", placeholder_text="Please enter your choice")
 
    #Mult-chocice: List of names table
    data_map_list_friends_choice2 = DataMap()
    data_map_list_friends_choice2.add(Assignment(target_variable="self.input.choices", value_expression="flow.input.friends.listOfNames"))

    user_node_with_form.multi_choice_input_field(name="multi-choice_as_table", label="List of Names", required=False, choices=data_map_list_friends_choice2, 
                                                  show_as_dropdown=False, placeholder_text="Please enter your choice", columns={"first_name": "First", "last_name": "Last"} )

    #Input list: Books editable table
    data_map_list_books = DataMap()
    data_map_list_books.add(Assignment(target_variable="self.input.default", value_expression="flow.input.books"))

    user_node_with_form.list_input_field(name="books", label="Books", default=data_map_list_books, columns={"title": "Book Title", "author": "Book Author"})
    #Input list: Fuits editable table
    data_map_list_fruits = DataMap()
    data_map_list_fruits.add(Assignment(target_variable="self.input.default", value_expression="flow.input.listOfFruits"))

    user_node_with_form.list_input_field(name="Fruits", label="Preferred fruits", default=data_map_list_fruits)

    # User field: Select multiple approvers (min 1, max 5)
    data_map_min_users = DataMap()
    data_map_min_users.add(Assignment(target_variable="self.input.min_num_users", value_expression="1"))
    data_map_max_users = DataMap()
    data_map_max_users.add(Assignment(target_variable="self.input.max_num_users", value_expression="5"))
    user_node_with_form.user_input_field(name="approvers", label="Select Approvers", required=True, multiple_users=True,
                                         min_num_users=data_map_min_users, max_num_users=data_map_max_users)

    # User field: Select a single assignee (min/max not applicable for single user)
    user_node_with_form.user_input_field(name="assignee", label="Select Assignee", required=True, multiple_users=False)
    #Output Message: Successful submission
    user_node_with_form.message_output_field(name="success", label="Successful submission", message="Application successfully completed.")
 
    # Add additional buttons using the new API
    save_draft_btn = user_flow.add_button("Save Draft")
    review_btn = user_flow.add_button("Submit for Review")
    
    # Create different nodes for each button action
    submit_node = user_flow.script(
        name="process_submit",
        script='print("Processing full submission...")'
    )
    
    save_draft_node = user_flow.script(
        name="process_save_draft",
        script='print("Saving as draft...")'
    )
    
    review_node = user_flow.script(
        name="process_review",
        script='print("Submitting for review...")'
    )

    # Connect nodes using the new edge API
    user_flow.edge(START, user_node_with_form)
    
    # Connect default "Submit" button using button_label parameter
    user_flow.edge(user_node_with_form, submit_node, button_label="Submit")
    user_flow.edge(submit_node, END)
    
    # Connect "Save Draft" button using Button object
    user_flow.edge(save_draft_btn, save_draft_node)
    user_flow.edge(save_draft_node, END)
    
    # Connect "Submit for Review" button using Button object
    user_flow.edge(review_btn, review_node)
    user_flow.edge(review_node, END)

    # Script to initialize friends list
    init_script = """
flow.input.friends.listOfNames = [
    {"first_name": "John", "last_name": "Doe"},
    {"first_name": "Alice", "last_name": "Smith"},
    {"first_name": "Bob", "last_name": "Johnson"}
]
flow.input.books=[
    { "title": "Shaken", "author":"John Grisham"},
    {"title": "The Client", "author":"John Grisham"},
]
flow.input.listOfFruits = ["apple", "oranges", "bananas"]
"""
    init_data = aflow.script(name="init_data", script=init_script)
    
    # add the user flow to the flow sequence to create the flow edges
    aflow.sequence(START, init_data, user_flow, END)
  
    return aflow

Dynamic form input

Form fields can change based on user actions. You define the dependencies between the user’s interactions and your tools to control dynamic behavior in the form. This includes progressive selection, showing or hiding widgets, and updating widget labels. Use the following methods to configure dynamic forms:
Adds a visibility behaviour that shows or hides fields based on conditions.
name
string
required
Unique identifier for this behaviour.
on_change_to_field
string
required
Field that triggers evaluation of visibility rules when its value changes.
rules
list[dict]
required
List of visibility rules. Each rule specifies a condition and an impacted_field that changes visibility.
display_name
string
Display name for this behaviour.
Adds a label-changing behaviour that updates field labels based on conditions.
name
string
required
Unique identifier for this behaviour.
on_change_to_field
string
required
Field that triggers label evaluation when its value changes.
rules
list[dict]
required
List of label rules. Each rule includes a condition and an impacted_field whose label changes.
display_name
string
Display name for this behaviour.
Adds a value-source behaviour that populates a field using results from a tool. Supports a simplified API or a manual API.
name
string
required
Unique identifier for this behaviour.
on_change_to_field
string
required
Field that triggers execution of the tool when its value changes.
impacted_field
string
required
Field that receives values returned by the tool.

Simplified API parameters

tool_name
string
Name of the tool to call, for example get_states_or_provinces.
tool_id
string
UUID of the tool.
field_mappings
dict[string, string]
Mapping of tool parameters to field expressions. For example country maps to parent.field.country.
client
any
Client instance that enables automatic retrieval of the tool schema.

Manual API parameters

tool
string
Tool identifier in name colon UUID format.
tool_input_schema
dict
Schema that describes the expected inputs for the tool.
tool_input_map
dict
Mapping of input parameters to values or expressions used by the tool.

Common optional parameters

display_name
string
Display name for this behaviour.
Here is an example that shows how to configure dynamic forms:
Python
from typing import List
from pydantic import BaseModel, Field
from ibm_watsonx_orchestrate.flow_builder.flows import (
    Flow, flow, UserNode, START, END
)
from ibm_watsonx_orchestrate.flow_builder.types import Assignment
from ibm_watsonx_orchestrate.flow_builder.data_map import DataMap
from ibm_watsonx_orchestrate.flow_builder.utils import RuleBuilder


class FlowInput(BaseModel):
    """Input schema for the flow."""
    countries: List[str] = Field(
        default=["USA", "Canada"],
        description="A list of available countries."
    )
    usa_states: List[str] = Field(
        default=["California", "Texas", "New York", "Florida"],
        description="A list of US states."
    )
    canada_provinces: List[str] = Field(
        default=["Ontario", "Quebec", "British Columbia", "Alberta"],
        description="A list of Canadian provinces."
    )
    default_country: str = Field(
        default="USA",
        description="Default country selection."
    )
    default_region: str = Field(
        default="California",
        description="Default region selection."
    )


@flow(
    name="user_activity_with_dynamic_forms_full",
    display_name="Dynamic Form - A simple example",
    description="Demonstrates dynamic form behaviours",
    input_schema=FlowInput,
)
def build_dynamic_form_full(aflow: Flow = None) -> Flow:
    
    # Create user flow
    user_flow = aflow.userflow()
    user_flow.spec.display_name = "Example dynamic form"
    
    # Create form node
    form_node = user_flow.form(
        name="dynamic_form", 
        display_name="Dynamic form example",
        cancel_button_label="Cancel")
    
    #FIELD 1: Country
    country_choices = DataMap()
    country_choices.add(Assignment(
        target_variable="self.input.choices",
        value_expression="flow.input.countries"
    ))
    
    country_default = DataMap()
    country_default.add(Assignment(
        target_variable="self.input.default",
        value_expression="flow.input.default_country"
    ))
    
    form_node.single_choice_input_field(
        name="country",
        label="Country",
        required=False,
        choices=country_choices,
        default=country_default,
        show_as_dropdown=True,
        placeholder_text="Select an option"
    )
    
    #FIELD 2: Visibility Behaviour
    # Controls city and url fields
    form_node.visibility_behaviour_field(
        name="visibility_behaviour",
        on_change_to_field="country",
        rules=[
            RuleBuilder.visibility_rule(
                field_name="country",
                field_value="USA",
                impacted_field="city",
                visible_when_true=True,
                operator="equals"
            ),
            RuleBuilder.visibility_rule(
                field_name="country",
                field_value="Canada",
                impacted_field="url",
                visible_when_true=True,
                operator="equals"
            )
        ],
        display_name="Visibility rule"
    )
    
    # FIELD 3: Label Behaviour
    # Controls code, region, and documents field labels
    form_node.label_behaviour_field(
        name="label_behaviour",
        on_change_to_field="country",
        rules=[
            RuleBuilder.label_rule(
                field_name="country",
                field_value="USA",
                impacted_field="code",
                label_when_true="Zip code",
                label_when_false="Postal code",
                operator="equals"
            ),
            RuleBuilder.label_rule(
                field_name="country",
                field_value="USA",
                impacted_field="region",
                label_when_true="State",
                label_when_false="Province",
                operator="equals"
            ),
            RuleBuilder.label_rule(
                field_name="country",
                field_value="USA",
                impacted_field="documents",
                label_when_true="US W-2 Form",
                label_when_false="Canadian T4 Form",
                operator="equals"
            )
        ],
        display_name="Label rule"
    )
    
    # FIELD 4: Value-Source Behaviour
    # Populates region field options from tool
    form_node.value_source_behaviour_field(
        name="value_source_behaviour",
        on_change_to_field="country",
        impacted_field="region",
        tool_name="get_states_or_provinces",
        tool_id="9f0ecb53-dbd9-4e41-be46-29c8d47d6df8",
        field_mappings={
            "country": "parent.field.country"
        },
        display_name="ProgSelect"
    )
    
    # FIELD 5: Region
    region_choices = DataMap()
    region_choices.add(Assignment(
        target_variable="self.input.choices",
        value_expression='',
        has_no_value=True
    ))
    region_choices.add(Assignment(
        target_variable="self.input.display_text",
        value_expression='',
        has_no_value=True
    ))
    region_choices.add(Assignment(
        target_variable="self.input.display_items",
        value_expression='',
        has_no_value=True
    ))
    
    form_node.single_choice_input_field(
        name="region",
        label="Region",
        required=False,
        choices=region_choices,
        show_as_dropdown=True,
        placeholder_text="Select an option"
    )
    
    # FIELD 6: Code
    form_node.text_input_field(
        name="code",
        label="Code",
        required=False
    )
    
    # FIELD 7: City
    form_node.text_input_field(
        name="city",
        label="City",
        required=False
    )
    
    # FIELD 8: Documents
    form_node.file_upload_field(
        name="documents",
        label="Documents"
    )
    
    # FIELD 9: URL
    form_node.text_input_field(
        name="url",
        label="URL",
        required=False
    )
    
    # Add edges
    user_flow.edge(START, form_node)
    user_flow.edge(form_node, END)
    
    # Script to initialize input data
    init_script = """
# Initialize countries list
flow.input.countries = ["USA", "Canada"]

# Initialize US states
flow.input.usa_states = ["California", "Texas", "New York", "Florida", "Illinois", "Pennsylvania"]

# Initialize Canadian provinces
flow.input.canada_provinces = ["Ontario", "Quebec", "British Columbia", "Alberta", "Manitoba", "Saskatchewan"]

# Set default selections
flow.input.default_country = "USA"
flow.input.default_region = "California"
"""
    init_data = aflow.script(name="init_data", script=init_script)
    
    # Add user flow to main flow with initialization
    aflow.sequence(START, init_data, user_flow, END)
    
    return aflow