Zayo

Developer Zayo

Zayo is an extensible Slack bot that can be taught to do almost anything using an easy-to-read Ruby DSL.

Skills

Skills are single-file modules that teach the bot to do something. An example skill looks like this:

Skill.define 'ask for food' do
  on_start do
    schedule.cron '50 11 * * 1-5', :ask_for_food
  end

  def ask_for_food
    in_channel '#random-food' do
      message = say "Who wants to go have lunch?"

      schedule.at 10.minutes.from_now, :lets_go, message
    end
  end

  def lets_go(ask_message)
    reactions = ask_message.reactions

    if reactions.any?
      say "Let's go!"
    else
      ask_message.react_with ':forever-alone:'
    end
  end
end

The above skill will ask Who wants to go have lunch? in #random-food at 11:50 every work day. 10 minutes after that, it will either say Let's go! or react with :forever-alone: depending on if there are any reactions below its first message.

Understanding message intent

Using Wit.ai, Zayo can infer user intent from free-text messages. This is typically how interactions with Zayo are started.

To teach Zayo to understand a query, just provide some examples in the skill code, then attach code whenever this intent is detected in a message:

Skill.define 'just talking' do
  teach do
    intent 'talkative/hi' do
      sample 'hi'
      sample 'hey'
      sample 'hello'
      sample 'hello, zayo'
    end

    intent 'talkative/introduce_yourself' do
      sample 'introduce yourself'
      sample 'please introduce yourself'
      sample 'please make an introduction'
      sample 'who are you?'
    end
  end

  on_intent 'talkative/hi' do
    say 'Hey :)'
  end

  on_intent 'talkative/introduce_yourself' do
    say 'I am Zayo, nice to meet you! :)'
  end
end

Intents can also extract parts of the message (called "entities" in Wit.ai):

Skill.define 'just talking' do
  teach do
    entity :name, lookups: ['free-text']

    intent 'talkative/introduction' do
      sample('hey, I am Georgi')    { entity :name, 'Georgi'   }
      sample('I am Batman')         { entity :name, 'Batman'   }
      sample('my name is Superman') { entity :name, 'Superman' }
    end
  end

  on_intent 'talkative/introduction' do |event|
    say "Nice to meet you, #{event.entities.single.name}! I am Zayo"
  end
end

Interactions and context

Zayo can interact with Slack users by asking questions, replying in threads, posting messages and reacting to messages. A Zayo command does not need to have all of its parameters in a single message. For example:

Skill.define 'just talking' do
  teach do
    entity :name, lookups: ['free-text']

    intent 'talkative/introduction' do
      sample 'have we been introduced?'
      sample "let's have an introduction"
      sample "I don't know you"
      sample('hey, I am Georgi')    { entity :name, 'Georgi'   }
      sample('I am Batman')         { entity :name, 'Batman'   }
      sample('my name is Superman') { entity :name, 'Superman' }
    end
  end

  on_intent 'talkative/introduction' do |event|
    name = event.entities.single.name

    if name
      say "Nice to meet you, #{name}! I am Zayo"
    else
      say 'I am Zayo'
      name = ask_for_string 'What is your name?'

      say "Nice to meet you, #{name}!"
    end
  end
end

The script for this interaction can proceed in two different ways, depending on whether the user's name is known in the original message or not:

> (user) Hey, I am Batman.
> (zayo) Nice to meet you, Batman! I am Zayo

or

> (user) Have we been introduced?
> (zayo) I am Zayo
> (zayo) What is your name?
> (user) Batman
> (zayo) Nice to meet you, Batman!

Persistent state

Skills can persist data such as lists, counters, etc. Persistent storage is Zayo's "memory":

Skill.define 'names' do
  teach do
    entity :name, lookups: ['free-text']

    intent 'talkative/introduction' do
      sample 'have we been introduced?'
      sample "let's have an introduction"
      sample "I don't know you"
      sample('hey, I am Georgi')    { entity :name, 'Georgi'   }
      sample('I am Batman')         { entity :name, 'Batman'   }
      sample('my name is Superman') { entity :name, 'Superman' }
    end

    intent 'talkative/hi' do
      sample 'hi'
      sample 'hey'
    end
  end

  on_intent 'talkative/introduction' do |event|
    name = event.entities.single.name

    storage do |store|
      store[:names] ||= {}
      store[:names][context.user.id] = name
    end

    if name
      say "Nice to meet you, #{name}! I am Zayo"
    else
      say 'I am Zayo'
      name = ask_for_string 'What is your name?'

      say "Nice to meet you, #{name}!"
    end
  end

  on_intent 'talkative/hi' do |event|
    name = storage { |store| (store[:names] || {})[context.user.id] }

    if name
      say "Hey, #{name}!"
    else
      say "Hello :)"
    end
  end
end

Extracting dates and time from messages

Skills can parse natural-language date and time input. This is done with Duckling (more specifically, a fork to support BG input: https://github.com/georgyangelov/duckling/tree/add-bg-time).

Skill.define 'reminder' do
  teach do
    entity :about, lookups: ['free-text']

    intent 'reminder/new' do
      sample 'remind me'
      sample('remind me to do the dishes') { entity :about, 'do the dishes' }
      sample('remind me tomorrow to buy a beer') { entity :about, 'buy a beer' }
      sample('remind me to call Joe in 10 minutes') { entity :about, 'call Joe' }
      sample('remind me to empty the trash can tomorrow') { entity :about, 'empty the trash can' }
    end
  end

  on_intent 'reminder/new' do |event|
    about = event.entities.single.name
    about = ask_for_string 'What should I remind you about?' if about.blank?

    time = event.extracted_time future: true
    time = ask_for_time 'When should I remind you?' unless time

    if time.past?
      say 'I can\'t remind in the past, unfortunately :disappointed:'
      next
    end

    schedule.at time, :remind, about
  end

  def remind(about)
    say "Hey! You wanted me to remind you about `#{about}`"
  end
end

extracted_time and ask_for_time try to disambiguate any datetimes they are not sure about. For example, if given tuesday as an input, it will give the user a couple of options (this tuesday, next tuesday) and ask for a choice. Another example is for times such as at 10 - it will ask if this is at 10pm or at 10am.

API Docs

Want to learn more? Take a look at: