Logic blocks
You can use logic blocks to include lightweight, limited Python logic in your agentic workflow to transform data and apply deterministic business rules. Logic blocks are intentionally restricted and do not provide a full Python execution environment.
When the agentic workflow reaches a logic block, it runs your logic in a restricted Python environment then continues to the next downstream node. You can use a logic block to process data transformations, implement custom logic or format messages before they continue to the next step in the agentic workflow. The logic block processing is fast, predictable, and free of any side effects.
The logic block acts as a programmable decision or transformation point in your agentic workflow, giving you greater flexibility and control.
-
Logic blocks support only inline logic and simple data transformations. If you need external libraries, API calls, reusable logic, or long‑running computation, use a tool instead of a logic block.
Logic block capabilities
The main capabilities of logic blocks are:
-
Predictable performance: Logic blocks run consistently so that workflows behave the same way each time.
-
Safe operation: Logic blocks run in a controlled environment that reduces security and stability risks.
-
Reliable scaling: Logic blocks work dependably even when workload or complexity increases.
You can use logic blocks for the following:
-
Initializing variables
-
Transforming data
-
Applying business logic
-
Message formatting
-
File processing
For more information about these use cases and examples, see Use cases and examples.
Capabilities excluded from logic blocks
Logic blocks run in a fixed, restricted Python environment. A logic block must not be used for the following:
-
Importing external Python libraries
-
Running arbitrary or long‑running logic
-
Calling external services or APIs
-
Acting as a reusable function or shared library
-
Replacing a Python‑based tool or microservice
These limits are intentional to ensure a secure, predictable, and efficient workflow environment.
Why logic blocks are constrained
Logic blocks run inline as part of a workflow. Using unrestricted Python creates the following risks:
-
Slows the workflow
-
Introduces security risks
-
Workflows are harder to debug and scale
To avoid these issues, logic blocks support fast, predictable logic, while tools can handle heavier processing or external operations.
Supported Python libraries
Within a logic block, you can read variables from different parts of the agentic workflow, apply custom logic, and define output variables for use by subsequent nodes.
Only the following standard libraries are available in logic blocks to ensure a safe and efficient workflow environment:
-
array -
calendar -
collections -
datetime -
enum -
json -
math -
random -
re -
string -
time -
yaml -
zoneinfo
Support for Python functions
Certain Python features are intentionally excluded from logic blocks to mitigate security risks and avoid unpredictable behavior.
The following functions are not available in logic blocks, but you can use the recommended and safer alternatives instead:
-
The
eval()function is not supported. Use(my_str == "True")instead ofeval(my_str), wheremy_stris either "True" or "False". -
For
yamlmodule onlysafe_load(),safe_load_all(),safe dump()andsafe_dump_all()API functions are supported. -
For
stringmodulestring.Formatteris not supported. Usestring.safe_format()instead ofstring.Formatter.format(). -
For
strfunctionstr.format()is not supported. Anyxxx.format()orxxx.format_map()API functions are not supported. -
Some built-in Python functions are not supported, including
import,class, andtype(). To check types, usesafe_type()instead oftype().
-
The list of available Python modules is fixed and cannot be changed or extended.
-
Certain libraries in the Python modules might not be available.
-
Variables can be referenced by using either ["varname"] or .varname. Using ["varname"] allows for the use of spaces and commas.
Python dictionary objects
Agentic workflow builder uses Python programming practices, therefore flow, self, and parent are treated as Python dictionaries. If you get an exception while reading from or assigning to a self-object, for example: self["input"]["customer"]["discount_rate"], it is likely because one of the containing objects (like "customer") does not exist yet. In that case, you need to first initialize the containing objects.
When a logic block runs, it uses Python dictionary objects to represent the inputs and outputs. You can use expression syntax to reference or set these values as needed. For more information about the syntax, see the Flow expressions section of the IBM wastonsx Orchestrate ADK.
Here is an example agentic workflow dictionary:
# flow represents the inputs and outputs of the outermost flow
flow = {
"input": {
"birthday": "1-10-2000",
"first_name": "John",
"last_name": "Doe",
"address": {
"street": "123 ABC Street",
"city": "NY",
"state": "NY",
"country": "USA"
},
"customer_status": "Bronze"
},
"output": {
"current_date": "<current date>",
"current_datetime": "<current datetime>",
"age": <number>,
"address": <str>
}
}
# self represents the inputs and outputs of the current node
self = {
"input": {
"customer_status": "Bronze",
"price": 1000
},
"output": {
"discount_rate": <number>
}
}
Data types
The following table lists the data types that are supported in agentic workflows, along with their equivalent Python and JSON types used in expressions and logic blocks.
|
Flow data types |
Python data types |
JSON data types |
|
|---|---|---|---|
|
String |
Str |
String |
|
|
Integer |
Int |
Integer |
|
|
Number |
Float |
Number |
|
|
Boolean |
Bool |
Boolean |
|
|
Date |
Str |
String |
|
|
Object |
Dict |
Object |
|
|
File |
WxoFile |
String |
Note:
-
To define an object data type in agentic workflows, you must adhere to the JSON schema standard. For more information, see the JSON Schema documentation.
-
In Python, dates are strings that use the ISO 8601 date format. JSON dates are strings with a “date” format, which uses the ISO 8601 date format, for example:
"startDate": {
"type": "string",
"description": "Employee's start date",
"format": "date"
}
For more information, see Date/time format for code blocks and expressions.
Data type conversion
Data type conversions allow transforming a value from one format to another. The following logic blocks provide Python expression examples of different types of data conversion.
Examples
String conversion
my_str = str("10")
my_int = int(my_str)
test_results += "my_str: " + my_str + " converted to my_int: " + str(my_int) + "\n"
my_str = str("1.5")
my_num = float(my_str)
test_results += "my_str: " + my_str + " converted to my_num: " + str(my_num) + "\n"
my_str = str("False")
my_bool = (my_str == "True")
test_results += "my_str: " + my_str + " converted to my_bool: " + str(my_bool) + "\n"
my_str = str("True")
my_bool = (my_str == "True")
test_results += "my_str: " + my_str + " converted to my_bool: " + str(my_bool) + "\n"
"%Y-%m-%d" my_str = str("2021-01-01")
my_date_str = my_str
test_results += "my_str: " + my_str + " converted to my_date_str: " + my_date_str + "\n"
my_date_date = datetime.datetime.strptime(my_date_str, "%Y-%m-%d").date()
my_date_date = my_date_date + datetime.timedelta(days=1)
my_date_str = my_date_date.strftime("%Y-%m-%d")
test_results += "added one day to my_date_str: " + my_date_str + "\n"
Integer conversion
my_int = int(10)
my_str = str(my_int)
test_results += "my_int: " + str(my_int) + " converted to my_str: " + my_str + "\n"
my_int = int(10)
my_num = float(my_int)
test_results += "my_int: " + str(my_int) + " converted to my_num: " + str(my_num) + "\n"
my_int = int(0)
my_bool = bool(my_int)
test_results += "my_int: " + str(my_int) + " converted to my_bool: " + str(my_bool) + "\n"
my_int = int(1)
my_bool = bool(my_int)
test_results += "my_int: " + str(my_int) + " converted to my_bool: " + str(my_bool) + "\n"
my_int = int(739470)
my_date_date = datetime.date.fromordinal(my_int)
my_date_str_isoformat = my_date_date.isoformat()
test_results += "my_int: " + str(my_int) + " converted to my_date_date: " + my_date_str_isoformat + "\n"
Number conversion
my_num = float(1.5)
my_str = str(my_num)
test_results += "my_num: " + str(my_num) + " converted to my_str: " + my_str + "\n"
my_num = float(1.5)
my_int = int(my_num)
test_results += "my_num: " + str(my_num) + " converted to my_int: " + str(my_int) + "\n"
my_int = math.ceil(my_num)
test_results += "my_num: " + str(my_num) + " converted to my_int using math.ceil(): " + str(my_int) + "\n"
my_int = math.floor(my_num)
test_results += "my_num: " + str(my_num) + " converted to my_int using math.floor(): " + str(my_int) + "\n"
my_num = float(0)
my_bool = bool(my_num)
test_results += "my_num: " + str(my_num) + " converted to my_bool: " + str(my_bool) + "\n"
my_num = float(1)
my_bool = bool(my_num)
test_results += "my_num: " + str(my_num) + " converted to my_bool: " + str(my_bool) + "\n"
my_num = float(739470)
my_date_date = datetime.date.fromordinal(int(my_num))
my_date_str_isoformat = my_date_date.isoformat()
test_results += "my_num: " + str(my_num) + " converted to my_date_date: " + my_date_str_isoformat + "\n"
Boolean conversion
my_bool = bool(True)
my_str = str(my_bool)
test_results += "my_bool: " + str(my_bool) + " converted to my_str: " + my_str + "\n"
my_bool = bool(False)
my_str = str(my_bool)
test_results += "my_bool: " + str(my_bool) + " converted to my_str: " + my_str + "\n"
my_bool = bool(True)
my_int = int(my_bool)
test_results += "my_bool: " + str(my_bool) + " converted to my_int: " + str(my_int) + "\n"
my_bool = bool(False)
my_int = int(my_bool)
test_results += "my_bool: " + str(my_bool) + " converted to my_int: " + str(my_int) + "\n"
my_bool = bool(True)
my_num = float(my_bool)
test_results += "my_bool: " + str(my_bool) + " converted to my_num: " + str(my_num) + "\n"
my_bool = bool(False)
my_num = float(my_bool)
test_results += "my_bool: " + str(my_bool) + " converted to my_num: " + str(my_num) + "\n"
Date conversions
my_date_date = datetime.date.today()
my_date_str_isoformat = my_date_date.isoformat()
my_date_str = my_date_date.strftime("%a %d %B %Y")
test_results += "my_date_date: " + my_date_str_isoformat + " converted to my_date_str: " + my_date_str + "\n"
my_date_date = datetime.date.today()
my_date_str_isoformat = my_date_date.isoformat()
my_int = int(my_date_date.toordinal())
test_results += "my_date_date: " + my_date_str_isoformat + " converted to my_int: " + str(my_int) + "\n"
my_date_date = datetime.date.today()
my_date_str_isoformat = my_date_date.isoformat()
my_num = float(my_date_date.toordinal())
test_results += "my_date_date: " + my_date_str_isoformat + " converted to my_num: " + str(my_num) + "\n"
self.output.test_results = test_results
Use cases and examples
You can use the logic block node to customize how your agentic workflow handles data. It is useful when standard nodes do not meet your specific needs. The following are some common use cases.
Message formatting
You can format or restructure messages as needed, while also moving information to the next node. The following logic block example shows a practical application of message formatting.
Examples
# Format address
addr = flow["input"]["address"]
formatted_address = f"{addr['street']}, {addr['city']}, {addr['state']}, {addr['country']}"
self["output"]["address"] = formatted_address
Flexible integration
You can place the logic block node anywhere in the agentic workflow to integrate with other nodes and tools. The following logic block sample shows a practical application of flexible integration.
Examples
today = datetime.date.today()
self.output.todays_date = today.isoformat()
A logic block that initializes a set of output variables by using primitive data types: string, integer, number, and boolean.
The output variables are:
-
name: String -
age: Integer -
salary: Number -
isActive: Boolean
In the logic block, each variable is initialized with a sample value.
self.output.name = "John Doe"
self.output.age = 37
self.output.salary = 12345.67
self.output.isActive = True
Custom data transformation
You can write Python logic to transform, filter, initialize variables, or enhance existing data as it passes through the agentic workflow. The following logic block examples show a practical application of custom data transformation.
Examples
address_json = '''
{
"street": "123 ABC Street",
"city": "NY",
"state": "NY",
"country": "USA"
}
'''
address = json.loads(address_json)
flow["input"]["address"]["state"] = address["state"]
{
"employee": {
"type": "object",
"description": "",
"properties": {
"id": {
"type": "integer",
"description": "Unique identifier for the employee"
},
"firstName": {
"type": "string",
"description": "Employee's first name"
},
"lastName": {
"type": "string",
"description": "Employee's last name"
},
"email": {
"type": "string",
"description": "Employee's email address"
},
"department": {
"type": "string",
"description": "Employee's department"
},
"salary": {
"type": "number",
"description": "Employee's salary"
},
"isActive": {
"type": "boolean",
"description": "Whether the employee is currently active"
},
"startDate": {
"type": "string",
"format": "date",
"description": "Employee's start date"
},
"badges": {
"type": "array",
"items": {
"type": "string"
},
"description": "Employee's badges"
},
"address": {
"type": "object",
"description": "",
"properties": {
"street": {
"type": "string"
},
"city": {
"type": "string"
},
"zipCode": {
"type": "string"
}
},
"default": ""
}
}
}
}
Business logic implementation
You can apply conditional logic, compute calculations and decisions, or run custom operations as needed in the agentic workflow. The following logic block examples show a practical application of business logic implementation.
Examples
# Get today's date
today = datetime.date.today()
# Get the user's birthday from the output of the previous user activity
birthday_str = flow["User activity 1"]["Ask for date of birth"].output.value
# Convert the user's birthday into a date object
birthday = datetime.datetime.strptime(birthday_str, "%Y-%m-%d").date()
# Calculate the user's age
age = today.year - birthday.year - ((today.month, today.day) < (birthday.month, birthday.day))
# Return the user's age
self.output.age = age
product = flow.input.product
age = flow.input.age
if product == "Alcohol" and age < 18:
self.output.accept_payment = False
elif product == "Fireworks" and age < 18:
self.output.accept_payment = False
else:
self.output.accept_payment = True
# Ensure customer_status and price are available
status = flow["input"].get("customer_status")
price = flow["input"].get("price", 0)
discount_rate = 0
if status == "Bronze":
if price < 100:
discount_rate = 0
elif price < 500:
discount_rate = 0.01
elif price < 900:
discount_rate = 0.02
else:
discount_rate = 0.03
elif status == "Silver":
if price < 100:
discount_rate = 0.02
elif price < 500:
discount_rate = 0.04
elif price < 900:
discount_rate = 0.05
else:
discount_rate = 0.07
elif status == "Gold":
if price < 100:
discount_rate = 0
elif price < 500:
discount_rate = 0.05
elif price < 900:
discount_rate = 0.07
else:
discount_rate = 0.10
self["output"]["discount_rate"] = discount_rate
# Format address using if-then logic
addr = flow["input"]["address"]
formatted_address = addr["street"]
if addr.get("city"):
formatted_address += f", {addr['city']}"
if addr.get("state"):
formatted_address += f", {addr['state']}"
if addr.get("country"):
formatted_address += f", {addr['country']}"
self["output"]["address"] = formatted_address
today = datetime.date.today()
three_days_ago = today - datetime.timedelta(days=3)
self.output.three_days_ago = three_days_ago.isoformat()
File processing
You can use the logic block node to manage file operations such as handling single or multiple file uploads, extracting or transforming file contents, and generating files for download. The following example demonstrates a practical file processing scenario.
Available functions for file processing
You can use system file functions or WxoFile instance methods to run file operations.
System File Functions
Following are the system.file methods to work with files:
-
system.file.get_name(file)– Returns the file name -
system.file.get_type(file)– Returns the file type -
system.file.get_size(file)– Returns the file size -
system.file.get_content(file)– Returns the file content as bytes
WxoFile Instance Methods
Following are the instance methods available on a WxoFile object:
-
file.get_name()– Returns the file name -
file.get_type()– Returns the file type -
file.get_size()– Returns the file size -
file.get_content()– Returns the file content as bytes
Following are practical examples that demonstrate how to access files from flow inputs, user activity forms and user activity file upload and retrieve their URL, name, type, and size by using both system.file functions and WxoFile instance methods.
input_file = flow.input.externalFile
self.output.file_name = system.file.get_name(input_file)
self.output.file_type = system.file.get_type(input_file)
self.output.file_size = system.file.get_size(input_file)
self.output.file_content = system.file.get_content(input_file)
input_file = flow.input.externalFile
self.output.file_name = input_file.get_name()
self.output.file_type = input_file.get_type()
self.output.file_size = input_file.get_size()
self.output.file_content = input_file.get_content()
form_single_file = flow["User activity 1"]["Form 1"].output["single file upload"]
self.output.file_name = system.file.get_name(form_single_file)
self.output.file_type = system.file.get_type(form_single_file)
self.output.file_size = system.file.get_size(form_single_file)
self.output.file_content = system.file.get_content(form_single_file)
form_single_file = flow["User activity 1"]["Form 1"].output["single file upload"]
self.output.file_name = form_single_file.get_name()
self.output.file_type = form_single_file.get_type()
self.output.file_size = form_single_file.get_size()
self.output.file_content = form_single_file.get_content()
form_multi_file = flow["User activity 1"]["Form 1"].output["Multi file upload"]
self.output.file_count = len(form_multi_file)
self.output.first_file_name = system.file.get_name(form_multi_file[0])
self.output.first_file_type = system.file.get_type(form_multi_file[0])
self.output.first_file_size = system.file.get_size(form_multi_file[0])
self.output.first_file_content = system.file.get_content(form_multi_file[0])
form_multi_file = flow["User activity 1"]["Form 1"].output["Multi file upload"]
self.output.file_count = len(form_multi_file)
self.output.first_file_name = form_multi_file[0].get_name()
self.output.first_file_type = form_multi_file[0].get_type()
self.output.first_file_size = form_multi_file[0].get_size()
self.output.first_file_content = form_multi_file[0].get_content()
normal_single_file = flow["User activity 1"]["File upload 1"].output.value
self.output.file_name = system.file.get_name(normal_single_file)
self.output.file_type = system.file.get_type(normal_single_file)
self.output.file_size = system.file.get_size(normal_single_file)
self.output.file_content = system.file.get_content(normal_single_file)
normal_single_file = flow["User activity 1"]["File upload 1"].output.value
self.output.file_name = normal_single_file.get_name()
self.output.file_type = normal_single_file.get_type()
self.output.file_size = normal_single_file.get_size()
self.output.file_content = normal_single_file.get_content()
System context variables
System context variables let you store and reuse values across a workflow. They act as shared data points, and the system context variables persists for the entire duration of the workflow. If a context variable is updated at the beginning of the workflow such as in a logic block, the value remains available throughout the entire workflow run.
The following methods are available:
get_variable method
The get_variable method retrieves a system context variable by name.
-
variable_name: Specifies the variable key.
system.context.get_variable("variable_name")
set_variable method
The set_variable method stores a value in the system by using the variable key.
-
variable_name: Specifies the variable key.
-
variable_value: Specifies the value to store.
system.context.set_variable('variable_name', "variable_value")
Predefined system context variables
Predefined variables are always available in the workflow. You do not need to set them manually by using the set_variable method. Use the get_variable method to access them at any point in the workflow.
The predefined variables are:
-
wxo_email_id -
wxo_tenant_id -
wxo_user_name