Skip to content



Sends the given message to the user.

say "Hello! Great that you're here."

Next to normal text you can also send any UTF-8 characters, including emojis.

say "😂 Very funny!"

You can also use string interpolation to create dynamic messages:

time_of_day = "morning"
say "Good #{time_of_day}!"

Will say "Good morning!".

Message formatting

URLs in text are automatically linked. Newline characters (\n) can be used to create a line break:

say "Click this link:\n"

When using web-based chat, you can use Markdown formatting for bold, italic, etc.

say "**bold text**"
say "*italic text*"
say "`monospace text`"
say "This is a [link]("


On web channels you can create a tooltip in a chat bubble by using the following Markdown link syntax:

say "Please enter your [VAT](## \"Value Added Tax\") number."

Message formatting for voice actions

On the voice channels, Google Assistant and Alexa, we convert markdown tags to SSML speech output.

The following conversions take place:

**emphasized speech**<emphasis level="strong">emphasized speech</emphasis>

**moderate speech**<emphasis level="moderate">moderate speech</emphasis>

_reduced speech_<emphasis level="reduced">reduced speech</emphasis>

Furthermore, certain punctuations are converted to pauses in the speech. The use of a , corresponds to a pause of 200ms; ... and correspond to a pause of 500ms.



Use the class: option to specify a specific presentation for the message. The class attribute will become part of the rendered HTML in the chat bubble. Combined with custom CSS rules, the class: option can be used to create a customized presentation for certain messages.

For example, this sets the class "large" on the message:

say "string", class: "large"

The predefined classes in Web client are:

  • large - font size 2x as large as normal
  • system - gray, smaller text, no bubble around text


To let the bot impersonate another person, it is possible to specify the as: option, with a given user object.

The object needs to have at least a user_id and a first_name attribute. profile_picture can be used to specify the avatar.

@person [user_id: "arjan", first_name: "Arjan", profile_picture: "http://..."]

dialog main do
  say "Hello my name is Arjan", as: @person

Messages like this will still appear on the left side of the chat window, and thus are meant for simulating persons other than the current user, e.g. for a group chat.

@arjan [user_id: "alice", first_name: "Alice", profile_picture: ""]
@faye [user_id: "pete", first_name: "pete", profile_picture: ""]

dialog main do

  say "Hi there, in the web chat this looks like Alice is saying it.", as: @alice
  say "Cool huh.", as: @alice

  pause 2

  say "Hi there i'm Pete :-)", as: @pete
  say "bybye", as: @pete

  say "Now Alice is talking again", as: @alice

as: :user

To let the bot impersonate the current user:

say "Hello from yourself", as: :user

These messages will appear on the right side of the chat window.


To specify how long (in seconds) the typing indicator should show before displaying the message in the say statement.

say "This takes long", typing_indicator: 20

The given value is specified in seconds.


To specify how long the delay should be before showing the message:

say "one ", delay: 0
say "two", delay: 0
say "three", delay: 0

Note that with delay: 0 you will still see the typing indicator. However, delay: 0 can be combined with typing_indicator: 0 if you require so.


ask presents a text message to the user, and waits for the response from the user. This response can be stored in a variable. When no variable name is given, the response of the user is stored in the variable named answer.

ask "how old are you?"
say answer

When used in an assignment, it is possible to store the response in another variable, like in the next example:

age = ask "how old are you?"
say age



When the expecting: option is set with a list of strings, entities or intents, the bot will repeat the question until the message is matched against the value(s) in the expecting option. This is used for validation purposes, when you need the user to type something specific to continue. The items in the expecting will also be displayed as quick-replies if they have a label attribute. If a media file is expected, expecting can be used to create a quick reply button which immediately triggers a file upload.

The most simple case for using expecting is to present a list of quick reply buttons below the question, like this:

ask "Did you laugh?", expecting: ["Yes", "No"]

The bot does not continue until the user literally types the string "Yes" or "No". So this form does not use any form of fuzziness or intent matching.

To be a bit more "loose" in what is accepted, you can define Yes and No intents (either in the code or in the Intent Manager:

# define intents which use fuzzy string matching.
@yes intent(match: "yes|yep|sure|indeed", label: "Yes")
@no intent(match: "no|nope", label: "No")

dialog main do
  ask "Did you laugh?", expecting: [@yes, @no]
  branch do
    answer == @yes ->
      say "You laughed!"
    answer == @no ->
      say "Apparently you did not laugh."

In above example, two intents are defined, with a label. These labels will be shown when the intents are given to the expecting clause of the ask.

expecting can also be used to present a specialized input widget or form control to the user.

ask "Please choose an item",
  expecting: input_method("item_picker", items: ["One", "Two"])

expecting can also be used to extract an entity from the user using Duckling entity, for instance, to ask for a date:

ask "When do you need the package?",
  expecting: entity(duckling: "time")

In above example, the answer.value variable will contain a valid ISO date based on what the user says ("tomorrow", etc).

Some other builtin validators for expecting are the following:

  • :file
  • :image
  • :location
  • :video
  • :text
  • :email
# Expect an image from the user
ask "Please upload a selfie", expecting: :image

# Match on any text input (same as when you omitted 'expecting')
ask "Where are you currently?", expecting: :text

# E-mail regular expression
ask "What is your email?", expecting: :email

# Trigger a file upload
ask "Which file do you want to upload?", expecting: :file

# Trigger a location picker
ask "Where are you currently?", expecting: :location

The built-in validators can also be combined in a list as follows:

# Ask the user to send some information about themselves
ask "Can you send me a picture or your location?", expecting: [:image, :location]

This will show both input widgets to the user.

expecting: with DTMF

Explicit DTMF choices can be added to the expecting clause by wrapping the expecting option with an intent and adding the DTMF option:

dialog __main__ do
  ask "Yes or no", expecting: [intent(label: "Yes", dtmf: "0")]

When no DTMFs are explicitly added, they are implicit on the expecting, the first choice being DTMF 1, the second DTMF 2, etc. Either none or all of the 'expecting' options need to have an dtmf property, mixing implicit with explicit DTMFs is not possible.


The help dialog is invoked when a user enters something that does not match the expected input:

dialog asking do
  ask "Did you laugh?", expecting: [@yes, @no], help_dialog: help

dialog help do
  say "You said: #{message}."
  say "Please answer yes or no instead."

The variable message is available in the help_dialog which contains the last utterance of the user.

An alternative to help_dialog is to define an inner dialog called __unknown__ to catch anything that does not match the expectation:

dialog asking do
  dialog __unknown__ do
    say "Please answer yes or no."

  ask "Did you laugh?", expecting: [@yes, @no]

This is usually clearer than the help_dialog option and also allows you to give a more fine-grained reply.

Note that this __unknown__ will not trigger when another trigger that is placed outside the scope of this dialog. For instance:

dialog asking do
  dialog __unknown__ do
    say "Please answer yes or no."

  ask "Did you laugh?", expecting: [@yes, @no]

dialog trigger: "maybe" do
  say "You said maybe"

If you now answer the question with maybe, you will trigger the dialog trigger "maybe" instead of the __unknown__. If you want to keep the resolving of triggers inside the dialog where the ask is, you can use the :text trigger.

dialog main do
  dialog trigger: :text do
    say "Please answer yes or no."

  ask "Did you laugh?", expecting: [@yes, @no]

dialog trigger: "maybe" do
  say "You said maybe"


Quick-replies provide the user with answers to choose from. However, these options are mere hints, they are not used for validation unless specified with the expecting option.

# Only the quick-replies
ask "Did you laugh?", quick_replies: ["Yes", "No"]

# `quick-replies` with expecting as validation
ask "Did you laugh?", quick_replies: ["yep", "nah"], expecting: [@yes, @no]

# The `quick_replies:` are shown but `expecting:` is matched:
ask "What's your email?", quick_replies: ["no thanks"], expecting: [@email, @no]

It is possible to combine expecting: and quick_replies:; in that case, the given quick_replies are used but the validation is done on the expecting clause.

Small icon-sized images can be embedded in the quick replies like this:

ask "Please choose your option", quick_replies: [
  [title: "option 1", image_url: ""],
  [title: "option 2", image_url: ""] ]

When using specification method, the following options are available:

  • title:, the text shown to the user,
  • image_url:, the url of an image to use,


An extra list of quick replies which are appended after the normal quick replies. These quick replies do not participate in the :cycle and :exhaust quick replies modes.


An extra list of quick replies which are prepended before the normal quick replies. These quick replies do not participate in the :cycle and :exhaust quick replies modes.


Sometimes, the user might not want to respond or forgets to respond to your question. For these cases you can add a timeout to the ask, so that the script continues after a short while. To prevent a script from "hanging", you can let the ask expire by supplying a timeout value which is then used as the answer.

@ask_timeout 30

# the ask dialog now overrides the timeout set as a constant
ask "How are you?", timeout: 4, timeout_value: :yes, expecting: [@yes, @no]

if answer == :yes do
  # this ask will have a timeout of 30 seconds
  ask "..."

If no timeout_value is given, the builtin dialog __ask_timeout__ will be triggered on a timeout:

ask "How are you?", timeout: 4

dialog __ask_timeout__ do
  say "Hey, wake up!"
  ask "Do you want to continue?"

When using ask from within a __ask_timeout__ dialog (without a timeout value), it will not trigger a new ask timeout dialog, but that ask will block infinitely.

To set a global timeout on all asks, use the @ask_timeout constant:

@ask_timeout 4

dialog main do
  ask "How are you?", timeout: 4

dialog __ask_timeout__ do
  say "Hey, wake up!"
  ask "Do you want to continue?"


The max_unknown option to ask ensures that the user can hit the __unknown__ inner dialog only N times.

@ask_max_unknown 3

# the ask dialog now overrides the max_unknown set as a constant
# if we say something other than yes or no for more than 3 times, the ask continues, assuming '@yes'.
ask "How are you?", max_unknown: 3, max_unknown_value: @yes, expecting: [@yes, @no]

If no max_unknown_value is given, the builtin dialog __ask_max_unknown__ will be triggered on a max_unknown:

ask "How are you?", max_unknown: 4

dialog __ask_max_unknown__ do
  say "I am sorry I really dont get what you want."

To set a global max_unknown on all asks, use the @ask_max_unknown constant:

@ask_max_unknown 3

dialog main do
  ask "How are you?", expecting: ["Good"]

dialog __ask_max_unknown__ do
  say "I dont get it.."


If an ask or prompt is labelled with `training_label:``, then any messages in response to that interaction are tagged with that training_label, even if they end up triggering another dialog (match_global).


A prompt is a special form of ask. It presents an automatic quick-replies menu based on its labeled inner dialogs. Paired with the continue statement, the prompt construct is an easy way to create an automatic navigational structure in your bot.

dialog simple_prompt do

  dialog label: "Tell a joke" do
    say "Here comes the joke…"

  dialog label: "Order a pizza" do
    say "Please wait while ordering…"

  dialog label: "Stop" do
    say "Alright"

  prompt "What would you like to do?"

  say "See you next time"

The inner dialogs can be combined with intent trigger: arguments to make the prompt matching more fuzzy.

Using the continue statement in an inner dialog will cause the interpreter to continue the execution right after the last prompt statement that was encountered. In the example above, this is used to provide the "stop" option from the prompt.



It is possible to let the quick replies be different every time the ask is displayed, while the ask is being repeated. This is typically used in a prompt to vary the quick replies slightly each time.

quick_replies_mode: :static — the default; just show the same quick replies each time.

quick_replies_mode: :cycle — each time the ask returns, the quick replies get cycled; the one that was the first in the previous prompt is now the last.

quick_replies_mode: :exhaust — each time the ask returns, the quick replies that was clicked last will have disappeared from the quick replies.


With the dialog option, you can specify another dialog that will be invoked whenever the prompt is called. This dialog has access to a local variable _quick_replies in order to display the quick replies through a say or something else.


dialog main do
  dialog label: "Bananas" do
    say "I like bananas!"

  dialog label: "Kiwis" do
    say "Kiwis are the best!"

  dialog label: "Oranges" do
    say "Never liked oranges..."

  prompt dialog: display_choices, quick_replies_mode: :exhaust

  say "We are done"

dialog display_choices do
  branch length(prompt.labels) do
  0 ->
  1 ->
    say "The last fruit opinion you can ask of me", quick_replies: prompt.labels
    say "I can tell you what I think about these fruits", quick_replies: prompt.labels


The goto statement is used to jump to a different dialog. It can be compared to the "goto" statement of languages like Basic. When the target dialog is done, execution will not return to the dialog that the goto was originally called from.

goto mydialog



goto mydialog, :replace

This is the default behaviour of goto. goto will not to return to the calling dialog, it will replace the current dialog on the stack.


goto mydialog, :reset

After finishing the dialog in the goto, the stack will be empty and the bot will become idle, or the __root__ dialog will be automatically invoked, if it exists.


Invoke is used to switch (temporarily) to a different dialog. It can be compared to a function call in a normal programming language. After the invoked dialog is finished, the original dialog will be continued after the invoke statement.

invoke mydialog

An invoke normally pushes the dialog on top of the current dialog stack, returning there after finishing.

Note that you can not use expressions in the name of the dialog, they always need to point to an existing dialog in one of the bot's scripts. The studio will show an error message when you have an invoke which points to a non-existing dialog.

invoke and goto can be used interchangably; they only differ in their default behaviour. For example, the following two calls are equivalent:

invoke mydialog, :replace
goto mydialog



By invoking message:, you can internally simulate the sending of a user message.

It will cause the entered message (which can be an expression) to be handled by the global dialog message matching system, as if a user had typed it. So in this example, the main dialog will output "Hi Pete!" when executed:

dialog main do
  invoke message: "my name is Pete"

dialog trigger: "pete" do
  say "Hi Pete!"

Invoke dialog with message trigger, resetting the stack

Resets the stack (just like invoke dialog, :reset would), and then performs the message matching just like `invoke message: "message" would.

dialog main do
  invoke reset: "my name is Pete"


By invoking event:, you can internally simulate the sending of a user event.

dialog main do
  invoke event: "button_click"

dialog event: "button_click" do
  say "You clicked!"

And with a payload:

dialog main do
  n = 5
  invoke event: "button_click", payload: n

dialog event: "button_click" do
  say "You clicked #{event.payload} times!"


By invoking attachment:, you can internally simulate the sending of an image or file.

dialog main when attachment do
  invoke attachment: attachment

dialog attachment: :image do
  say "Thank you for the image"


By invoking location:, you can internally simulate the sending of an image or file.

dialog main when location do
  invoke location: location

dialog __unknown_location__ do
  say "Thanks for the location pin"

These invoke with message, attachment and location variants are especially important on whatsapp because main is invoked after the starting of a bot, even if the user types a message first. (user-initiated conversation).


show is the generic way of showing any media, other than plain text, to the user. For example, the following shows an image:

show image ""

See the UI elements section to see all the kinds of media that can be presented to the user.


Instantly stops the interaction with the bot. The bot will not continue talking until the user starts talking again.

On some platforms, for instance on telephony, stop causes the conversation to end (see: close).

dialog trigger: "bye" do
  say "Thank you for your time."
  say "This line is never executed"


Stops the interaction with the bot, and closes the conversation. The user cannot interact with this conversation ever again.

On some platforms, for instance on telephony, close causes line to hang up. On others, it hides the chat input, and/or shows a control that indicates that the conversation is over.

On channels that do not support any close controls, but are "single conversation", e.g. Whatsapp, Facebook messenger, etc, the next time a user starts chatting, a new conversation with the user is opened.

dialog trigger: "bye" do
  say "Thank you for your time, I hope it helped."

__closed__ dialog

When close is invoked, a dialog __closed__ is invoked, as the final dialog of the conversation. This dialog should only be used for displaying a final message or a "conversation closed" user interface. At this point, the user is not supposed to interact with the conversation again.

dialog __closed__ do
  show input_method("closed",
    caption: "Conversation closed",
    description: "Thank you for your time! See you later."

__closed__ task

When close is invoked, a dialog __closed__ is invoked if it exists, as described below. After the closed dialog finishes, the conversation is over. However, after close the __closed__ task gets called to perform any cleanups, conversation summarization, etcetera. These can be potentially long-lasting tasks.

All tasks called __closed__ are executed (given their guards). So skills can defined their own cleanup mechanisms.

task __closed__ do
  perform some_summarization_task
  create_conversation_note("Conversation summary: " + summary)


Switch the language of the user based on the input and the modifiers in the opts. If no modifiers are given, the modifiers that are present will be added to the new language.

user.locale = "en.FORMAL"
switch_language "nl"
log user.locale # "nl.FORMAL"

If a locale has any modifiers, they will overwrite any existing modifiers:

user.locale = "en.FORMAL"
switch_language "nl.SSML"
log user.locale # "nl.SSML"


Stops the current dialog and clears the dialog stack. When a __root__ menu is defined, it is invoked.

dialog trigger: "bye" do
  say "Thank you for your time."


The await statement works similar to ask, except that no prompt or quick replies are shown while awaiting.

The first argument to await is the argument you would give to expecting: in an ask.

For example, the following waits for a valid email address and stores the global variable answer.

await @email

The following blocks until an event with the name "click" has been fired:

await event("click")

Like ask, the result can also be stored in a variable:

email = await @email
say email


The emit statement sends an event to the runtime that is executing the bot. These events can be used for instance to trigger backchannel events or trigger events on the website. Emits an event to the client, or to another chat process. An event consists of a named message (a string), plus an optional payload.

The event itself is a string; it can have an optional extra parameter which will be sent as payload.

dialog status_update do
  emit "status_update", text: "#{user.first_name} started using the bot"

dialog main do
  emit "start"
  say "Hello!"
  emit "end", [user_id:]

See the documentation on events and scheduling how events can be used on the platform.


Will pause the execution of the dialog for the given amount of time, in seconds.

pause 3         # pause the dialog for 3 seconds


Shows the typing indicator for the given amount of time, in seconds.

type 3          # pause for 3 seconds, while showing typing indicator


A block that will choose a line within the do..end and execute that once.

say random 10

returns a random number (int) between 0 and 10

say random [1,2,3,4]

returns a random element from the list

random do
  say "Hi!"
  say "Hello!"
  say "Howdy!"


With once you can say things 'only once' stepping down a statement on every invocation.

once do
  say "Hi!"
  say "Hi again!"
  say "We meet again"

This can be used to only say 'Hi' the first time and second time say 'Hi again' for instance. Every iteration once will select the next statement down. When there is an after present the statement after will be invoked on every successive iteration. Without after the once block will not do anything (when exhausted).

You can provide a name to the variable that is used to store the counter. This can be used to reset it so that the once block cycles instead of getting exhausted.

once i do
  say "Hi!"
  say "Hi again!"
  say "We meet again"
  dialog.once.i = nil # reset this once block to start again (cycle)


Resets a once block by name.

dialog __main__ do
  once example do
    say "Hi!"
    say "Hi again!"

dialog trigger: "bye" do
  reset_once "example"

remember / forget

remember is the way to persist information for a user or conversation in the platform's builtin contacts database (the CRM). All information stored by remember is saved with the bot's user and can be viewed in the studio. In a next conversation, these variables will be set again when new session starts. This way, you only have to ask a user his name once, for example.

variable = 123
remember variable
forget variable

NOTE: variables can be deep 'object maps':

car.brand = "Volvo"
car.engine.hp = 200

remember car

This remembers the object car and its nested values.

tag / untag

Sets a tag or clears a tag on the current conversation. Tags are a handy way to organize conversations, they can be used for filtering and finding users and conversations in the studio.

tag "lead"
untag "lead"

Tags are stored on two places: for the current conversation, the tags are collected in the conversation.tags variable; at the same time, tags are also collected in the user.tags variable. The difference between these is that the user variable is shared between all the conversations that the user is involved in, while conversation.tags are local to the current conversation.

Do not manipulate the conversation.tags or user.tags directly, always use tag and untag.

To display the tag counts in the same graph in analytics (for funnel analysis) you can use a namespace:

tag "funnel:1-signup"

All tags in the "funnel" namespace will be shown in the Analyze seection of the studio, in a separate section. To order the tags according to the funnel steps its best to number them.


Prints a message, for debugging purposes:

log "This is a log message"

Log messages can contain any valid expression and are logged in the Console tab in the Botsquad studio, and in the conversation's history of the inbox.


Make a decision, based on an expression

if exits != "Yes" do
  say "Lets continue"

else is supported as well:

if exits != "Yes" do
  say "Lets continue"
  say "Ok lets quit"


As an extended version of the if statement, branch allows you to switch over multiple conditions. As soon as the first condition matches, the corresponding block is invoked, while the other blocks are skipped.

ask "What is your name?"
branch answer do
  "Steve" -> say "Hi Steve!"
  "Pete"  -> say "Hello Peter"

See the full Branch reference


When called on a single variable, erases the variable from the global scope:

name = "joe"
unset name
# here the name variable no longer exists

When called on a map key, the map key is deleted from the variable:

person = %{"name" => "joe"}
# the 'person' variable is now an empty map


Analogous to invoke, a task can be executed by using the perform statement:

perform calculate

Tasks do not have arguments. As the DSL has a global scope, any parameters that are required inside the task should be defined just before the perform statement.


buttons "What do you want to do next?" do
  "Open CNN" ->
    url ""
  "Say hello" ->
    postback "say_hello"

NOTE: though buttons are similar to quick_replies they invoke dialogs via a postback event: rather than trigger: and are not displayed as user message in the conversation.


A statement that starts a test block. Use normal bubblescript statements inside of the block to test a bot. The string after the test statements is the name of the test. The name is usually a description of what the test is supposed to check.



This option can be used to invoke a specific dialog when the test is run.

# Bot script

dialog not_main do
  say "This is not the main dialog"

# Test script
test "a different dialog is invoked", dialog: not_main do
  expect "This is not the main dialog"


Context can be added to override certain variable values in the dialog, or to supply variables to a dialog that would otherwise be set in a previous dialog. This is helpful because it allows you to setup an environment for the test.


# Bot Script
dialog main do
  say "Hii #{name}!"

# Test Script
test "say name", context: %{name: "Test"} do
  expect "Hii Test"


Use this statement to express what you expect a bot to say at a certain moment. At the moment, only the say and ask statements of a bot are supported, we intend to support show in the future. Whatever is put into expect from the side of the bot, can be stored in a variable as well by using assignment. If no variable is given, the expected is stored in the variable "message". This can be useful in combination with assert.

test "arithmatic" do
  say "1 + 1"
  expect "2"

test "bot says my name" do
  expect "What is your name?"
  say "Tester"
  greeting = expect "Hi Tester!"
  assert greeting == "Hi Tester!"


To make sure that a variable contains a certain value, we can use assert to make sure that an expression validates to true. The operators that are supported by the assert statement are the following: == != > < <= >=. assert is mainly useful for testing tasks. If a task sets the value of a variable, this value can be asserted using assert.

test "something" do
  assert 11 == 11
  a = "b"
  assert a == "b"
  assert 10 > 20

assert only works for variables that are available in the test script. It is not possible to test the (internal) variables of your bot. An exception for these are tasks; in a test script it is possible to perform a task; and then assert on the variables that the task has set. See Testing bots for more information.


The redirect statement forwards the current conversation to another bot.

redirect bot: bot_id

This redirects the conversation, stopping the current bot and the new bot will continue the conversation, starting with the main dialog like normal (unless other options are given, see below).

In the new bot, the variable holds the bot ID of the original bot. This can be used to redirect the conversation back.

All remembered user and conversation data is passed into the new conversation.

Effort is taken to align the locale of the conversation to the original locale, if the target bot supports the locale.

Redirect options

tag: - By providing the tag: option, you can set a tag in the new conversation to indicate that the conversation has been redirected.

message: - The message: can be used to use the given string as the starting message in the new conversation. This allows you to create "empty" bots that only forward their conversation to another bot:

dialog __unknown__ do
  emit "$redirect", [bot: bot_id, tag: "redirected", message: message.text]

location: - Similar to the message: option, sends the given location to the new bot upon redirect.

attachment: - Similar to the message: option, sends the given attachment to the new bot upon redirect.

Redirects do not work on Slack and Discord.

It is not possible to pass any other information to the new conversation; however all remembered conversation/user variables will persist.


The continue statement is used to break out of the current ask or prompt statement. The conversation continues after the prompt or ask, filling the answer variable with either :continue or an explicit return value, if given.

dialog trigger: "help" do

dialog main do
  ask "What would you like to do?", expecting: ["Sleep"]
  if answer == :continue do
    say "We have continued"

Or with an explicit continue value:

dialog trigger: "help" do
  continue :help_requested

dialog main do
  ask "What would you like to do?", expecting: ["Sleep"]
  if answer == :help_requested do
    say "We have continued"


Throw an error in the current bot process, with the given message.

Can be used for error handling, when it does not make sense to continue the current conversation due to an API call failing for instance.

fatal will cause the __error__ dialog to be triggered, to be able to guide the user onto a new track.

dialog __main__ do
  perform my_task

task my_task do
  http_get ""

  if http.status_code == 500 do
    fatal "Internal server error!"

dialog __error__ do
  say "Sorry, something went wrong: " + error.message


The submit statement can be used in tests to submit data.

submit message("hello", data: "hello_data")
submit location(lon: 70, lat: -30)
submit attachment(url: "", type: "image")

This can be used to send data to an ask:

# script: main
dialog main do
  location = ask "Where are you?", expecting: :location
  say location.lon

# script: test_main
test "sending a valid location" do
  expect("Where are you?")
  submit location(lon: 50, lat: 40)