Skip to content

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:

  1. Named tasks - reusable tasks that can be called from dialogs
  2. Declared tasks - named tasks with input/output validation and documentation
  3. Event-based tasks - triggered by specific events
  4. 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