On Call TA: Distributed Event Network Federation Through Subscription


Summary

Personal event networks can be federated to provide groups with interactive services. By allowing apps to customize the owner's experience, use and manage personal data, and take advantage of shared services, personal event networks provide operating system like services for their owners.

[]

Recently I wrote about the theory behind federating distributed personal event networks through subscription. This post is about a working demonstration of event subscription in personal event networks.

Scenario

I have two TA for CS462. My TAs are usually in the building, but not sitting at the TA cubicle for CS462. They are happy to be "on call" and answer student questions almost any time if they don't have to sit in the cubicle away from their usual desk. They have built a KRL ruleset that allows students to text a number and request a meeting. The problem is that you have to copy the ruleset and customize it for each TA. I thought this was a great example for how a federated group of event networks could work together, so I built it as an example.

Personal Event Networks

A little background on personal event networks. Everyone with a Kynetx account has a personal event network. What that means is that when an event is raised to the network any rule installed in the network and listening for that event will respond. Multiple rules can respond to any given event and merely installing a ruleset changes the collection of rules and events that will be seen. We call this concept salience.

Personal event networks can store personal information in a way that an installed ruleset can see and use the data. We've also built a general-purpose notification event API and have a simple ruleset that responds to notification events.

General Set Up

For this demo, there are three personal event networks: one for the class that is hooked to the SMS number, and one for each of the TAs. The setup supports any number of TAs. The personal event networks for the TAs subscribe to events from an app (ruleset) installed in the class' personal event network.

TA network interaction

We've configured a Twilio SMS number to raise an event (twilio:sms) to the class personal event network whenever it receives an SMS message. A ruleset installed in the class personal event network called On-call TA: Dispatcher watches for this event and dispatches a schedule:inquiry event to the personal even networks of any subscribers. TAs indicate whether they are on call or not by scheduling appointments with the title "On Call" on their personal Google calendar, not a shared one.

The TAs install a ruleset called On-call TA: Demo in their personal event networks. This ruleset is looking for a schedule:inquiry event. The ruleset uses personal data from the network's personal data management ruleset to customize it. The ruleset's job is to notify the TA when a schedule:inqury event is received. The notification takes the form of a notification:status event that is handled by the Simple Notifications ruleset. In this case, the Simple Notification ruleset sends an SMS message to the TA. Notice that the TA picks which notification ruleset is installed and this determines how they will be notified. Message and channel are separated.

Any TA who receives a notification and can see the student merely responds to the text and the student is notified that help is on the way. If no TA is on call, the student is told that no one is on call right now.

A few points to emphasize:

  • None of these rulesets know about the others. They are connected by the event network in a loosely coupled maner.
  • The subscriptions are made using single-channel tokens that create a one-to-one link between networks. This protects against SPAM and other communications abuses. The relationship can be revoked as easily as it is created.
  • The behavior of the rulesets isn't specialized to an individual. The rulesets are general purpose. All the personal data is retrieved from the personal data manager installed in the personal event network.
  • Anyone who wants to see the schedule:inquiry events from the class personal event network must subscribe to them. At present, that is done by modifying a hand-coded data structure (see below), but it could, of course, be automated to varying degrees.

The Gory Details

What follows are the gory details of how it all works. Keep reading if you're into that sort of thing.

Dispatching and Subscription

You communicate with a personal event network by raising an event to it using an event signal URL (ESL). Each ESL for a personal event network contains an identifying token (in the form of a GUID). The token uniquely identifies the personal event network that will see the event. An owner of a personal event network can create as many unique ESLs as she likes. Consequently, every entity raising an event to a personal event network has a one-to-one channel to communicate events. These ESLs can be revoked by the owner of the personal event network at will, protecting the personal event network from unwanted events. They could even be set to expire after a given number of uses or after a given amount of time (note: expriry behaviors are not currently built out in the Kynetx system).

Subscribing to events thus requires that the owner of the personal event network provide an ESL, or at least the identifying token from the ELS, to the event publisher. Once a publisher has an ESL, they can raise events to the subscriber whenever appropriate. In the case of the TA dispatcher, any interested parties--presumably the TAs--subscribe to schedule:inquiry events from the Dispatcher ruleset by supplying a token from their personal event network. The ruleset also needs other information from subscribers. We store those subscriptions in a data structure inside the Dispatcher ruleset:

subscribers = 
   [{"name":"Phil",
     "phone":"801362XXXX",
     "token":"072a3730-2e9a-012f-d2da-00163e411455",
     "calendar":"https://www.google.com/calendar/..."
    },
    {"name":"John",
     "phone":"801602XXXX",
     "token":"fc435280-2b60-012f-cfeb-00163e411455",
     "calendar":"https://www.google.com/calendar/..."
    }
   ];

This is hardcoded in the Dispatcher ruleset for now, but could easily be something that is configurable with a little more work.

The ruleset performs a bit of semantic translation on the twilio:sms event to determine whether it is a schedule inquiry or a response from the TA since they both are SMS messages to the same ruleset. If the SMS represents a schedule inquiry, the rule raises an explicit event named schedule_inquiry, causing the following rule to be selected:

rule dispatch {
  select when explicit schedule_inquiry
    foreach subscribers setting (subscriber)
      pre {
        resp_cookie = math:random(99);
      }
      if(onnow("On Call", subscriber{"calendar"})) then {
        send_event(subscriber{"token"},"schedule","inquiry")
          with attrs = {"from" : event:attr("From"),
                        "message": event:attr("Body"),
                        "cookie": resp_cookie
                        };
      }
      fired {
        set ent:return_receipt{"x"+resp_cookie} 
            event:attr("From");
      } else {
        ent:missed_call_count += 1 from 0;
      }
}

The rule loops over subscribers and checks their calendar to see if there is a "On Call" event on the calendar. If so, it sends an event to their personal event network. If not, an entity variable called missed_call_count is incremented (more on this in a minute). The send_event action is something I defined for this demo. It takes the token from the subscriber and the event domain and type (schedule and inquiry in this case). Any event attributes that need to be sent are given as optional parameters to the action. Here's the definition:

send_event = defaction(subscriber, dom, type) {
  configure using attrs = {}
  mk_esl = function(token, d, t) {
    eid = math:random(9999999);
    a = [token, eid, d, t];
    "http://cs.kobj.net/sky/event/" + a.join("/");
  };
  http:post(mk_esl(subscriber,dom,type)) 
    with params = attrs          
};

Notice the embedded definition of mk_esl that takes a token, domain, and type and creates an ESL. The action uses http:post() to make the call.

As we gain more experience with the subscriber pattern, we'll probably build in an action for signaling all subscribers for efficiency purposes, but there's nothing wrong with the way I've done it here from a functional standpoint. This is just what needs to be done. By using an ESL instead of short circuiting it in the engine we allow for other event networks to participate.

The Dispatcher ruleset contains another rule that let's students know when there isn't anyone on call. The sms_absent rule also respond to the schedule_inquiry explicit event, but only fires when the entity variable missed_call_count is greater than or equal to the number of subscribers (recall that it was incremented in the dispatch rule):

rule sms_absent {
  select when explicit schedule_inquiry
  if(subscribers.length() <= ent:missed_call_count) then 
    twilio:send_sms(event:attr("From"), 
                    ta_sms_num, 
                    "There are no TAs on call right now.");
  always {
    clear ent:missed_call_count;
  }
}

If it fires, an SMS is sent to the student saying that no TAs can respond. Whether or not it fires, the variable missed_call_count is cleared.

Notifying the TA

The TA has installed a TA ruleset that listens for schedule:inquiry events. If the TA has an "On Call" appointment on her calendar, then the explicit event on_call_request is raised with attributes named message and from. The event also includes an end attribute that contains the ending time of the appointment.

rule schedule_inquiry_oncall {
  select when schedule inquiry
  pre {
    thisappt = now(oncall_title, my_calendar);
  }
  if thisappt then noop();
  fired {
    raise explicit event on_call_request for "a16x142.dev" with 
      message = event:attr("message") and 
      from = event:attr("from") and
      end = thisappt => justtime(thisappt.pick("$.when[0].end")) 
                      | "later today";
  }
}

Why check the calendar here if it was checked in the dispatcher? Because schedule:inquiry is a pretty general event. The TA might have other apps installed that also respond to a schedule:inquiry event. For example, maybe she has a "study group" ruleset installed that negotiates meeting times for a study group. When a study group schedule:inquiry events comes in, we don't want the TA ruleset to respond. Of course, we check it in the dispatcher not only for efficiency purposes but to let students know when no TA is available

The on_call_request event is handled by the notify_ta rule. This rule processes some of the attributes and forms a message. The interesting thing, however, is that it doesn't SMS the TA. Instead, it raises a notification:status event. The TA must have an app installed in her personal event network to handle this event and notify her in some way. Nothing in this ruleset handles that event.

rule notify_ta {
  select when explicit on_call_request
  pre {
    person = event:attr("from");
    message = event:attr("message");
    end = event:attr("end");
    cookie = event:attr("cookie");
    msg = <<
#{my_name}, #{person} wants to meet in Cubicle 11. 
Your on call until #{end}. 
Respond with #{cookie} to take the appointment. 
'#{message}'
>>;
  }
  noop();
  always {
    raise notification event status with
      priority = "2" and
      application = "TA App" and
      subject = "On Call Request" and
      description = msg and
      _api = "sky" 
  }
}

Note that this rule doesn't take any action (noop()); we merely want the effect of raising an event in the postlude.

Responding

The system is set up to allow the TA to respond to the student's request to meet by merely sending an SMS back to the dispatcher. That's the purpose of the two digit "cookie" that gets sent in-band with the message. The TA responds with any message she likes as long as it starts with the same two digits and the Dispatch ruleset will route the message to the right person.

Whenever Twilio receives an SMS, it raises a twilio:sms event. As I mentioned earlier, there's a bit of semantic translation that occurs to determine what a twilio:sms event means. That is done by the sms rule in the Dispatcher ruleset:

rule sms {
  select when twilio sms
  pre {
    body = event:attr("Body");
    body_parts = body.extract(re/^(\\d\\d)\\s(.*)$/);
    cookie = body_parts[0];
    msg = body_parts[1]
  }
  if (cookie) then noop();
  fired {
    raise explicit event schedule_response for a16x141 
      with cookie = cookie 
       and msg = msg;  
  } else {
    raise explicit event schedule_inquiry for a16x141 
  }
}

The sms rule looks at the incoming message and if it starts with a two-digit sequence, assumes that it's a response from the TA and raises and explicit schedule_response event. Otherwise, it raises the explicit schedule_inquiry event. We saw earlier that the dispatch rule responds to the schedule_inquiry event.

The explicit schedule_response event is handled by the process_ack rule that processes the TA responses and sends an acknowledgment to the student:

rule process_ack {
  select when explicit schedule_response
  pre {
    phone_number = event:attr("From");
    ta_record = 
      subscribers.filter(
         function(r){
           phone_number.match(
                   ("/"+r{"phone"}+"/").as("regexp")
               )}
       ).head();
    ta_name = ta_record{"name"};
    ta_msg = event:attr("msg");
    msg = <<
#{ta_name} has been notified and will arrive 
shortly in Cubicle 11. He says '#{ta_msg}'
>>;
    student_num = ent:return_receipt{"x"+event:attr("cookie")};
  }
  if(student_num && ta_name) then 
       twilio:send_sms(student_num, ta_sms_num, msg);
  fired {
    clear ent:return_receipt{"x"+event:attr("cookie")};
  }
}

The rule uses the incoming phone number to filter the list of subscribers to retrieve the TA record in the subscriptions. That record is used to set the TA's name. We also use the in-band cookie to retrieve information about the student to whom we're responding. If the student number is found and that message came from a TA in the list, then we send an SMS back to the student and clear the return receipt.

Improvements

This federation could be improved in multiple ways:

  • Support subscription with an interface that allows TAs to come and go.
  • Integrate Foursquare data so that TAs who haven't checked in aren't contacted.

Conclusion

This blog post illustrates several important concepts.

  1. The demo shows how personal event networks can be federated by subscription to achieve a group purpose.
  2. The demo shows that apps can be installed to customize the owner's experience.
  3. The demo shows that apps can make use of shared services like the Simple Notification ruleset.
  4. The demo shows how apps installed in a personal event network can make use of personal data stored separately in the network

Through these actions personal event networks provide operating system like services for their owners, giving them a personal cloud that can be customized to work for them.