Tasks
A task
is a construct which is comparable to a dialog. However,
tasks are meant for non-interactive, non-interruptible computations, therefore,
no statements are allowed that perform user-level side effects, like say
,
ask
or show
.
There are three types of tasks in Bubblescript:
- Named tasks - reusable tasks that can be called from dialogs
- Declared tasks - named tasks with input/output validation and documentation
- Event-based tasks - triggered by specific events
- Hook-based tasks - tasks that execute at specific points in the interpreter lifecycle
Named Tasks¶
Named tasks are the primary way to organize reusable, non-interactive computations in Bubblescript. They serve as modular units of code that can be called from dialogs or other tasks:
task calculate_total do
total = price * quantity * (1 + tax_rate)
end
task apply_discount do
total = total * 0.9 # Apply 10% discount
end
Named tasks can be executed using the perform
statement from dialogs or other tasks:
dialog checkout do
perform calculate_total
perform apply_discount
say "Your final total is: " + total
end
Key benefits of named tasks:
- Modular and reusable code organization
- Clean separation between computation and user interaction
- Can be called multiple times from different contexts
- Execute outside the dialog context, keeping the dialog stack clean
- More performant than dialogs for computational operations
Declared Tasks¶
Declared tasks are an extension of named tasks that add schema validation and documentation. They allow you to specify:
- Input validation through the
in:
parameter - Output validation through the
out:
parameter - Documentation through the
description:
parameter
In the implementation of the task, the _args
variable holds the in
parameter of the task. Declared tasks that have an 'out' parameter, MUST have a return
statement.
Here's an example of a declared task, that uses the @schemas.custom_user
schema:
# schemas.yml
custom_user:
type: object
properties:
name:
type: string
subscription:
type: object
properties:
type:
type: string
enum: ["free", "professional", "enterprise"]
# task declaration
declare task get_price,
in: @schemas.custom_user,
out: %{type: "number"},
description: """
Gets the price for the user's subscription
"""
task get_price do
# the '_args' variable holds the 'in' parameter of the task
branch _args.subscription.type do
"free" -> return 0
"professional" -> return 10
"enterprise" -> return 100
else
# This is required, as all branches in the task must return.
fatal "Unexpected subscription type"
end
end
Key benefits of declared tasks:
Declaring tasks with schemas and descriptions provides several important benefits:
-
Runtime Validation: Automatically validates inputs and outputs against declared schemas, catching errors early and preventing invalid data propagation
-
Self-Documenting Code: Description fields provide clear explanations of task functionality, serving as built-in documentation that stays synchronized with the code
-
Enhanced IDE Support: Schemas and descriptions enable better IDE features like hover documentation and input/output format hints
-
Improved Code Reliability: Schema validation enforces strict typing and data validation, preventing common data-related bugs
-
LLM Integration Ready: Structured format with explicit types and documentation makes tasks ideal for AI tool integration
Event-based Tasks¶
Event-based tasks are executed when a specific event occurs:
task event: "ping" do
counter = counter + 1
end
The advantage of using tasks over dialogs here is that tasks execute
outside of the current dialog context. Therefore, the current dialog
continues undisturbed, and the dialog stack and history remain untouched.
As a result, they execute without any other effects (like __returning__
)
and are also much more performant than dialogs.
Hook-based Tasks¶
Hook-based tasks are special tasks that execute at specific points in the Bubblescript interpreter's lifecycle. They are defined using the before:
keyword in their declaration.
All tasks with a before: hook are executed one after the other, not just the first one like with
perform
.
task before: dialog
¶
Executed just before the dialog with the given name is executed:
dialog main do
say welcome
end
task before: main do
welcome = "Good afternoon"
end
task before: __user_message__
¶
The before: __user_message__
hook gets executed any time a user's message
enters into the system, regardless of it being caught by an ask
or not. This
hook can also be used to enrich the message
variable with extra metadata.
Guard Clauses¶
Any type of task (event-based, named, or hook) can have guard clauses which will decide whether the task will be executed or not:
dialog main do
perform calculate
# depending on the user.frontend, the revenue will be different.
say "You earned: " + revenue
end
task calculate when user.frontend == "slack" do
revenue = revenue + 200
end
task calculate do
revenue = revenue + 100
end