Skip to content

Flows

A flow is a special YAML file that represents a graphical conversation flow, as a graph consisting of nodes and links. Flows can be edited using a graphical flow editor.

Introduction

For simple bots and IVRs, having users use plain Bubblescript is often a bridge too far; the learning curve is simply too high for end users who just want to configure and "click together" a simple bot.

To address this, in the past a Flows skill has been implemented, as a content-manageable way to create linear flows. The skill defines flows inside a single YAML file, where each flow consists of a linear sequence of steps, each of which can be guarded with a tag expression to check whether or not it executes. From this YAML file, Bubblescript is generated which executes the variuos steps in the flow.

The flows skill has a few shortcomings however:

  • Lack of extensibility: the flows skill needs to know everything about other skills in order to expose all step types
  • Flow steps cannot be made specific on various channels
  • The CMS user interface is suboptimal

To overcome this, work has begun to create a node-based conversation flow system, something that is quite common in other platforms. However, unlike other platforms, Bubblescript is still the backend of this flow system, giving it advantages in terms of extensibility and non-linearity.

Some design goals have been stated:

  • Each flow is managed in its own file
  • Flows have top-level properties (on which channel it executes, etc)
  • Each flow is flowchart that consists of typed nodes and links between nodes
  • Nodes and links have common properties, but also type-specific ones
  • Nodes can have variants that are more specific, e.g. 'show image', 'show audio', 'ask phone'
  • Nodes and links are aware of their capabilities and are only applicable on channels that support these capabilities
  • The types and variants of node is extensible so skills can add new node types and variants, in order to provide more specific functionality.
  • All configuration UI ("property panels") for flow/node/link properties is supported by React JSONSchema Form

The initial flow UI is a "flat" click-through UI; which shows a single click path through a tree.

The text/yaml+flow YAML schema defines a schema for a node-based dialog builder.

Documentation around flows is not yet complete

A flow consists of nodes and links.

Nodes have a type and a variant. Each type needs to have at least a single variant. Usually, the 'main' variant is called default.

Links have currently two types: next and expecting. Links can currently not be customized as the Bubblescript generator and the flow UI builder has predefined behaviour for links.

Flow extensibility

The flow_schema YAML file can be used to add additional node types and variants to the flow editor.

# adding new node types
types: []

# adding new node variants
variants: []

# removing platform-defined defaults
omit_types: []
omit_variants: []

For each bot, the flow system looks in all folders for script files called flow_schema, and merges these files together into a single definition file.

This way, you can add new variants and types, overwrite predefined variants or types, or disable certain variant/type combinations (by using the omit_ fields).

flow_schema files are merged in script order, so the 'highest' flow definition wins.

Platform defaults

The following is an overview of all base types and variants in the system.

types:
  - priority: 1000
    type: next
    kind: link
    caption: Next node
    schema:
      type: object
      properties: {}
    ui_schema:
  - priority: 1100
    type: labelled
    kind: link
    caption: Labelled choice
    schema:
      type: object
      required:
        - label
      properties:
        label:
          title: Label
          $ref: '#/definitions/__i18nstring'
        dtmf:
          type: string
          enum:
            - '1'
            - '2'
            - '3'
            - '4'
            - '5'
            - '6'
            - '7'
            - '8'
            - '9'
            - '0'
            - '*'
            - '#'
          title: DTMF Key
        intent:
          type: string
          title: Intent
          pattern: ^[a-z][a-zA-Z0-9_]*$
      additionalProperties: false
    ui_schema:
      label:
        ui:field: i18n
      intent:
        ui:widget: intent_picker
      ui:order:
        - label
        - intent
        - dtmf
  - priority: 1200
    type: trigger
    kind: link
    caption: Dialog trigger
    schema:
      type: object
      properties:
        intent:
          type: string
          title: Intent
          pattern: ^[a-z][a-zA-Z0-9_]*$
        system_dialog:
          type: string
          enum:
            - unknown
          title: System dialog
      additionalProperties: false
    ui_schema:
      intent:
        ui:widget: intent_picker
      ui:order:
        - intent
        - system_dialog
  - priority: 1300
    type: condition
    kind: link
    caption: Condition
    schema:
      type: object
      required:
        - condition
      properties:
        label:
          type: string
          title: Label
        condition:
          type: array
          title: Condition
      additionalProperties: false
    ui_schema:
      ui:order:
        - condition
        - label
      condition:
        ui:field: flow_condition
  - priority: 1000
    type: entry
    kind: node
    caption: Entrypoint
    schema:
      type: object
      properties:
        guard:
          type: array
          title: When
        frontends:
          type: array
          items:
            type: string
          title: Channels
      additionalProperties: false
    ui_schema:
      guard:
        ui:field: flow_condition
      frontends:
        ui:field: frontend_picker
      ui:order:
        - frontends
        - guard
  - priority: 1100
    type: say
    kind: node
    caption: Say
    schema:
      type: object
      required:
        - text
      properties:
        text:
          title: Text
          $ref: '#/definitions/__i18nstring'
      additionalProperties: false
    ui_schema:
      text:
        ui:widget: autosize_textarea
        ui:field: speechmarkdown
  - priority: 1200
    type: ask
    kind: node
    caption: Ask
    schema:
      type: object
      required:
        - text
      properties:
        text:
          title: Question
          $ref: '#/definitions/__i18nstring'
        assign:
          type: string
          title: Assign to
          pattern: (?!^(?:answer|message|bot|dialog|user|conversation|event)$)^[a-z][a-zA-Z0-9_]*(.[a-z][a-zA-Z0-9_]*)*$
        remember:
          type: boolean
          title: Remember
        repeat_text:
          title: Try again
          $ref: '#/definitions/__i18nstring'
      additionalProperties: false
    ui_schema:
      text:
        ui:widget: autosize_textarea
        ui:field: i18n
      assign:
        ui:placeholder: answer
      repeat_text:
        ui:widget: autosize_textarea
        ui:field: i18n
      ui:order:
        - text
        - repeat_text
        - assign
        - remember
  - priority: 1300
    type: show
    kind: node
    caption: Show
    schema:
      type: object
      required:
        - url
      properties:
        url:
          type: string
          format: uri
          title: Media URL
        caption:
          title: Caption
          $ref: '#/definitions/__i18nstring'
    ui_schema:
      caption:
        ui:field: i18n
  - priority: 1400
    type: send
    kind: node
    caption: Send
    schema:
      type: object
      properties:
        message:
          type: string
          title: Message
        subject:
          type: string
          title: Subject
    ui_schema:
      message:
        ui:widget: autosize_textarea
      ui:order:
        - subject
        - message
  - priority: 1700
    type: escalate
    kind: node
    caption: Escalate
    schema:
      type: object
      required:
        - message
      properties:
        message:
          type: string
          title: Help message
        tag:
          type: string
          title: Tag
      additionalProperties: false
    ui_schema:
      message:
        ui:widget: autosize_textarea
      ui:order:
        - tag
        - message
        - '*'
  - priority: 1800
    type: control_flow
    kind: node
    caption: Control flow
    schema:
      type: object
      properties: {}
      additionalProperties: false
    ui_schema:
  - priority: 1900
    type: dial
    kind: node
    caption: Dial
    schema:
      type: object
      required:
        - number
        - sip
      properties:
        number:
          type: string
          title: Number
        sip:
          default: invite
          type: string
          enum:
            - invite
            - refer
          title: SIP
      additionalProperties: false
    ui_schema:
      number:
        ui:widget: phone_number
      sip:
        ui:widget: radio
        ui:options:
          inline: true
      ui:order:
        - number
        - sip
        - '*'
omit_types: []
omit_variants: []
variants:
  - links:
      - next
    priority: 1000
    type: entry
    kind: node
    caption: Main
    schema:
    ui_schema:
    generator_template: ''
    omit: []
    type_ui_schema:
    ui_template: ''
    variant: default
  - links:
      - next
    priority: 1100
    type: entry
    kind: node
    caption: Unknown
    schema:
    ui_schema:
    generator_template: ''
    omit: []
    type_ui_schema:
    ui_template: ''
    variant: unknown
  - links:
      - next
    priority: 1200
    type: entry
    kind: node
    caption: Root
    schema:
    ui_schema:
    generator_template: ''
    omit: []
    type_ui_schema:
    ui_template: ''
    variant: root
  - links:
      - next
    priority: 1300
    type: entry
    kind: node
    caption: Intent
    schema:
      type: object
      required:
        - intent
      properties:
        intent:
          type: string
          title: Intent
          pattern: ^[a-z][a-zA-Z0-9_]*$
      additionalProperties: false
    ui_schema:
      intent:
        ui:widget: intent_picker
      ui:order:
        - intent
    generator_template: ''
    omit: []
    type_ui_schema:
    ui_template: ''
    variant: trigger
  - links:
      - next
    priority: 1400
    type: entry
    kind: node
    caption: DTMF
    schema:
      type: object
      required:
        - dtmf
      properties:
        dtmf:
          type: string
          enum:
            - '1'
            - '2'
            - '3'
            - '4'
            - '5'
            - '6'
            - '7'
            - '8'
            - '9'
            - '0'
            - '*'
            - '#'
          title: DTMF Key
      additionalProperties: false
    ui_schema:
      ui:order:
        - dtmf
    generator_template: ''
    omit: []
    type_ui_schema:
    ui_template: ''
    variant: dtmf
  - links:
      - next
    priority: 1000
    type: say
    kind: node
    caption:
    schema:
    ui_schema:
    generator_template: |
      say {{ say.text | as_string -}}

      {{ node | goto_next }}
    omit: []
    type_ui_schema:
    ui_template: ''
    variant: default
  - links:
      - labelled
      - trigger
    priority: 1000
    type: ask
    kind: node
    caption: Choice
    schema:
    ui_schema:
    generator_template: |
      ask [{{ ask.text | as_string -}}
        {%- if ask.repeat_text -%}, {{ask.repeat_text | as_string-}}{%- endif -%}
      ]

      {%- if node.out.labelled -%}, expecting: [
        {%- for link in node.out.labelled -%}
        entity(return: {{ link.labelled.label | as_string }}, label: {{ link.labelled.label | as_string }}, match: json_build({{ link.labelled.label | as_string }})),
        {%- endfor -%}
      ]
      {% endif %}

      {% if ask.assign %}
        {{ ask.assign }} = answer.text
        {% if ask.remember %}remember {{ ask.assign }}{% endif %}
      {% endif %}

      {% if node.out.labelled %}
      branch answer do
        {% for link in node.out.labelled %}
          {{ link.labelled | as_trigger }} ->
          {{ link | goto }}
        {% endfor %}
      end
      {% endif %}
    omit: []
    type_ui_schema:
    ui_template: ''
    variant: default
  - links:
      - next
    priority: 1100
    type: ask
    kind: node
    caption: Open question
    schema:
    ui_schema:
    generator_template: |
       {% if ask.assign %}{{ ask.assign }} = {% endif -%}
       ask {{ ask.text | as_string }}
       {% if ask.assign and ask.remember %}
       remember {{ ask.assign }}
       {% endif %}
       {{ node | goto_next}}
    omit:
      - repeat_text
    type_ui_schema:
    ui_template: ''
    variant: open
  - links:
      - labelled
      - trigger
    priority: 1200
    type: ask
    kind: node
    caption: Prompt
    schema:
    ui_schema:
    generator_template: |
      prompt {{ ask.text | as_string }}

      {% for link in node.out.labelled %}
      dialog trigger: {{ link.labelled | as_trigger }}, label: {{ link.labelled.label | as_string }} do
        {{ link | goto }}
      end
      {% endfor %}

      {% for link in node.out.trigger %}
      {% assign t = link.trigger %}
      dialog {% if t.system_dialog %}__{{ t.system_dialog }}__{% else %}trigger: @{{ t | as_trigger }}{% endif %} do
        {{ link | goto }}
      end
      {% endfor %}
    omit:
      - assign
    type_ui_schema:
    ui_template: ''
    variant: prompt
  - links:
      - next
    priority: 1000
    type: show
    kind: node
    caption: Image
    schema:
    ui_schema:
    generator_template: |
         show {{ show.variant }}({{ show.url | as_string }}){% if show.caption %}, caption: {{ show.caption | as_string }}{% endif %}

         {{ node | goto_next }}
    omit: []
    type_ui_schema:
      url:
        ui:widget: image
        ui:options:
          giphy: true
    ui_template: ''
    variant: image
  - links:
      - next
    priority: 1100
    type: show
    kind: node
    caption: Video
    schema:
    ui_schema:
    generator_template: |
         show {{ show.variant }}({{ show.url | as_string }}){% if show.caption %}, caption: {{ show.caption | as_string }}{% endif %}

         {{ node | goto_next }}
    omit: []
    type_ui_schema:
      url:
        ui:widget: file
        ui:options:
          accept: video/*
    ui_template: ''
    variant: video
  - links:
      - next
    priority: 1200
    type: show
    kind: node
    caption: Audio
    schema:
    ui_schema:
    generator_template: |
         show {{ show.variant }}({{ show.url | as_string }}){% if show.caption %}, caption: {{ show.caption | as_string }}{% endif %}

         {{ node | goto_next }}
    omit: []
    type_ui_schema:
      url:
        ui:widget: file
        ui:options:
          accept: audio/*
    ui_template: ''
    variant: audio
  - links:
      - next
    priority: 1300
    type: show
    kind: node
    caption: File
    schema:
    ui_schema:
    generator_template: |
         show {{ show.variant }}({{ show.url | as_string }}){% if show.caption %}, caption: {{ show.caption | as_string }}{% endif %}

         {{ node | goto_next }}
    omit: []
    type_ui_schema:
      url:
        ui:widget: file
    ui_template: ''
    variant: file
  - links:
      - next
    priority: 1000
    type: send
    kind: node
    caption: Note
    schema:
      type: object
      properties:
        tag:
          type: string
          title: Note tag
      additionalProperties: false
    ui_schema:
      tag:
        ui:field: tag_picker
    generator_template: |
      create_conversation_note({{ send.subject | as_string }} + "
      " + {{ send.message | as_string }}
      {%- if send.note.tag -%}, [{{ send.note.tag | as_string }}]{%- endif -%})

      {{ node | goto_next }}
    omit: []
    type_ui_schema:
    ui_template: ''
    variant: note
  - links:
      - next
    priority: 1100
    type: send
    kind: node
    caption: E-mail
    schema:
      type: object
      properties:
        email:
          type: string
          title: E-mail address
      additionalProperties: false
    ui_schema:
    generator_template: |
      mail({{ send.email.email | as_string }}, {{ send.subject | as_string }}, {{ send.message | as_string }})

      {{ node | goto_next }}
    omit: []
    type_ui_schema:
    ui_template: 'Send email with subject: {{ send.subject }}'
    variant: email
  - links:
      - next
    priority: 1200
    type: send
    kind: node
    caption: SMS
    schema:
      type: object
      properties:
        phone:
          type: string
          title: Phone number
      additionalProperties: false
    ui_schema:
      phone:
        ui:widget: phone_number
    generator_template: |
      sms_notify({{ send.sms.phone | as_string }}, {{ send.subject | as_string }} + " " + {{ send.message | as_string }})

      {{ node | goto_next }}
    omit: []
    type_ui_schema:
    ui_template: 'Send SMS to {{ send.sms.phone }} with subject: {{ send.subject }}'
    variant: sms
  - links: []
    priority: 1000
    type: close
    kind: node
    caption:
    schema:
    ui_schema:
    generator_template: |
      close
    omit: []
    type_ui_schema:
    ui_template: ''
    variant: default
  - links:
      - next
    priority: 1000
    type: pause
    kind: node
    caption: Pause
    schema:
    ui_schema:
    generator_template: |
      pause {{ pause.timeout }}
      {{ node | goto_next }}
    omit: []
    type_ui_schema:
    ui_template: ''
    variant: default
  - links:
      - next
    priority: 1100
    type: pause
    kind: node
    caption: Stop
    schema:
    ui_schema:
    generator_template: |
      stop
      {{ node | goto_next }}
    omit:
      - timeout
    type_ui_schema:
    ui_template: ''
    variant: stop
  - links:
      - next
    priority: 1000
    type: escalate
    kind: node
    caption:
    schema:
    ui_schema:
    generator_template: |
      tag "escalated"

      {% if escalate.tag %}
      tag {{ escalate.tag | as_string }}
      {% endif %}
      escalate({{ escalate.message | as_string }})

      {{ node | goto_next }}
    omit: []
    type_ui_schema:
    ui_template: |
      Escalate: <b>{{ escalate.message }}</b>
      {% if escalate.tag %} to: <b>{{ escalate.tag }}</b>{% endif %}
    variant: default
  - links:
      - condition
    priority: 1000
    type: control_flow
    kind: node
    caption: Branch
    schema:
    ui_schema:
    generator_template: |
      branch do
      {% for link in node.out.condition %}
      {{ link.condition | branch_condition }} ->
        {{ link | goto }}
      {% endfor %}
      end
    omit: []
    type_ui_schema:
    ui_template: ''
    variant: branch
  - links: []
    priority: 1100
    type: control_flow
    kind: node
    caption: Go to flow
    schema:
      type: object
      properties:
        flow:
          type: string
          title: Flow
      additionalProperties: false
    ui_schema:
      flow:
        ui:widget: flow_picker
    generator_template: |
      {% if node.control_flow.goto.flow %}
      goto flow_{{ node.control_flow.goto.flow }}
      {% endif %}
    omit: []
    type_ui_schema:
    ui_template: ''
    variant: goto
  - links: []
    priority: 1200
    type: control_flow
    kind: node
    caption: Close
    schema:
    ui_schema:
    generator_template: |
      close
    omit: []
    type_ui_schema:
    ui_template: ''
    variant: close
  - links:
      - next
    priority: 1300
    type: control_flow
    kind: node
    caption: Pause
    schema:
      type: object
      required:
        - timeout
      properties:
        timeout:
          type: number
          title: Wait time (seconds)
      additionalProperties: false
    ui_schema:
    generator_template: |
      pause {{ node.control_flow.pause.timeout }}
      {{ node | goto_next }}
    omit: []
    type_ui_schema:
    ui_template: ''
    variant: pause
  - links:
      - next
    priority: 1000
    type: dial
    kind: node
    caption: Number
    schema:
    ui_schema:
    generator_template: |
      tag "dial:{{ dial.number }}"

      {% if dial.sip == 'invite' %}
      dial "{{ dial.number }}"
      {% else %}
      refer "{{ dial.number }}"
      {% endif %}

      tag "dial:failed"

      {{ node | goto_next }}
    omit: []
    type_ui_schema:
    ui_template: 'Dial: {{ number }}'
    variant: dial

Generated from platform version: 2.57.0

CMS definitions

A definition for a single flow:

type: flow
title: "Single flow"

storage:
  type: script
  script_title: single_flow

generators:
  - type: flow
    script_title: generated/single_flow

A definition for a collection of flows:

title: "My flows"
type: flow
storage:
  type: script
  collection: flows/
  collection_editable: true
generators:
  - type: flow
    script_title: generated/{{ data_script_title }}