17 KiB
Custom Apps
Definition
Zulip defines a "custom app" to be a piece of code that runs in the Zulip ecosystem, but which is not part of the core Zulip codebase. Custom apps are mostly synonymous with "bots" and "integrations" in the Zulip ecosystem. We currently do not support any kind of browser plugin model.
Problem statement
Zulip wants to enable people in the world to author custom apps with the following goals in mind:
- Simple custom apps should be simple to write and deploy.
- Custom app authors should be able to easily distribute their work.
- Zulip should provide deployment support for mature, general-purpose bots, ideally either within organizations (a Zulip admin can vet their own custom apps and easily deploy them across upgrade cycles) or across organizations (custom apps get distributed with the Zulip tarball).
This document describes Zulip's current infrastructure, as well as laying out a roadmap for some future features.
A quick note on bots/integrations
As noted earlier, a custom app is just a generic term for what we often call bots or integrations. We recognize that bots and integrations can have different connotations. A bot typically spends most of its time responding to Zulip messages. An integration usually represents an app that interacts with some large third party system like an issue tracker. We will use both terms in this document in an informal sense, but from an architecture standpoint, we treat bots and integrations as essentially two shades of the same color. Many integrations are implemented as "bots." Likewise, any bot that does stuff outside of Zulip acts as an "integration."
Since the line between what a "bot" is and what an "integration" is can get very blurry, we try to be informal about "bots/integrations" and more formal about how "custom apps" actually function within the system.
Categories of custom apps
Stimulus/response and read/write
At the end of the day, most useful apps respond to some stimulus and produce a response. In the Zulip universe, Zulip can be the source of the stimulus, or the target of the response, or both. Along those lines, we divide custom apps into these three types:
-
A Zulip Reader uses activity on Zulip to stimulate an external response. An example here would be a follow-up bot that sees messages with the alert word "@todo" on a stream and then adds a task to a third party todo-list tool.
-
A Zulip Writer reacts to external stimuli and generates Zulip responses. An example here might be a build bot that gets triggered by an automated code build finishing and then writes "build finished" to a Zulip stream.
-
A Zulip Read/Writer reacts to a stimulus from Zulip by responding to Zulip. An example here would be a math bot that sees a message saying "compute 2+2" and responds with "2+2=4" on the same stream or back to the user in a PM.
The above three classifications represent kind of a Zulip-centric view of the universe, but we should put ourselves in the shoes of somebody "out in the world."
-
A World Reader is an app that gets some stimulus from the outside world and produces a response in Zulip. (So, a world reader is a Zulip writer.)
-
A World Writer is an app that gets some stimulus from Zulip and produces a response in the outside world. (So, a world writer is a Zulip reader.)
Some things are a little outside of the scope of this document. We could plausibly extend Zulip some day to host World Reader/Writer apps that don't even write Zulip messages but simply use Zulip as a kind of middleware platform.
More in the short term, we will have custom apps that may read/write from multiple sources. For example, a meeting bot may take input from both a cron job and a Zulip stream, and it may write to both a Zulip stream and a third party calendar tool. For the scope of this document, we won't spend a lot of time talking about how to build these types of apps, but we are aware that any solution needs to accommodate multiple sources and targets.
World Reader/Zulip Reader
Finally, we set the stage for how we talk about custom apps in terms of these two broad categories:
- A World Reader responds to stimuli from the outside world (and typically produces a response in Zulip).
- A Zulip Reader responds to stimuli from Zulip conversations (and typically produces a response in the outside world).
Again, we recognize that there can be overlap between those two categories for complex custom apps, but we mostly leave it as an exercise for the reader how to implement those apps.
Other classifications
We discussed one dimension for classifying custom apps, which is whether they are world-readers or Zulip-readers. Here we cover a few other classification schemes briefly:
- Generality Does the custom app have a specific use case or a general one? The spectrum here could run from a bot that Alice runs to update a text file on her laptop (specific) to a Twitter Bot that is optionally deployed on all Zulip realms (general).
- Authorship Who wrote the custom app? Was it written by contributors to the Zulip project?
- Maturity How well tested is the custom app? Is it just a prototype? Has it been sanctioned by an open source community? Has it been vetted by Zulip developers?
- Deployment Where does the custom app run? Does it run on Alice's laptop? Does it run on a Zulip server? Does it run as a plugin on third party infrastructure?
- Authorization What streams are the custom app allowed to read and write from? Which users can the custom app interact with?
- Identity How does the custom app identify itself on Zulip? How does it identify itself to the outside world?
- Third party We call the non-Zulip target or source of a custom app the "world." The "world" could be almost anything, ranging from an electronic device or text file to a large third-party system like Twitter or GitHub.
A lot of the classification schemes are interrelated. Here are some examples:
- For specific-purpose custom apps, authors may be happy to just deploy them on their own hardware. For general-use custom apps, authors may want to have them deployed on the Zulip server with super-user capabilities.
- As a custom app becomes more well-tested and well-vetted, the author will likely upgrade its deployment over time. At first the author may run the custom app on their laptop, then they may find dedicated hardware, and then finally they contribute the app to the Zulip project so that Zulip admins can deploy the app on Zulip servers.
- The nature of the third party will influence the deployment strategy. If I have a little home-grown gadget that can turn off the lights in my kitchen, I may run a custom app on my laptop that reads my PMs for "turn-off-the-light" messages. If I write a generic custom app that needs to update a third party corporate system based on Zulip events, I may want to deploy code to a public webserver or try to get my code to be part of the Zulip project itself.
World Reader
A World Reader custom app is an app that responds to stimuli from the world outside of Zulip. It typically functions as a Zulip Writer and posts some kind of message to a Zulip stream or user to alert people of world events. Here are some example stimuli:
- A Travis build finishes.
- Somebody tweets on Twitter.
- A hardware sensor notices a temperature increase.
- A pull request is submitted to GitHub.
- A cron job gets started on your laptop to send a reminder.
- Nagios detects a system anomaly.
Setting aside issues of how a custom app is constructed or deployed, you basically have to solve these problems:
- Detect events.
- Translate events into Zulip messages.
- Post the messages to Zulip.
Zulip integrations
Zulip actually supports a bunch of integrations out-of-the-box that perform as World Readers.
The three different integration models basically differ in where they perform the main functions of a World Reader.
Webhook integrations
In a webhook integration, the deployment model is usually this::
3rd party hardware:
- detect event
- send data to Zulip webhook
Zulip:
- support webhook endpoint
- translate event to messages
- internally post messages
One current limitation of our system is that we don't have a great way to deploy prototypes of webhook-based custom apps before Zulip has vetted the translation and added an official endpoint. Maybe we could set up some kind of webserver that can run translation code outside of Zulip and externally post the messages, and we could think about how to structure the code so that it is easy to eventually turn it into a Zulip-hosted integration.
Python scripts
In script integrations, the deployment model is usually this:
Custom app author's hardware:
- detect event by polling a third party system
- translate event in the script
- externally post messages
These type of integrations are typically easy to prototype, but they can be harder to deploy in production settings, since we rely on the authors to run their own scripts.
In some cases authors might want to at least move the translation/posting code to live on Zulip, by contributing that code to Zulip as a server-side integration. Then, there would still be the challenge of detecting events in the third party system, where maybe the user submits a patch to the third party as well.
Plugin integrations
In plugin integrations, the deployment model is usually this:
Third party system (driver):
- detect event
Third party system (plugin):
- further detect/triage event
- translate event
- externally post to Zulip
For third parties that have a plugin model, there are often other issues at play, like the plugins may need to be written in a non-Python language like Ruby. There are probably still some scenarios, however, where a lot of the logic for translation could be moved to a Zulip-side integration, and then we supply very thin client code for the plugin.
Zulip Reader
A Zulip Reader custom app gets stimuli from Zerver itself. Most Zulip Reader apps are packaged/advertised more as what people commonly call "bots" than as "integrations." (But sometimes what is currently a "bot" should really be deployed more like an "integration" in an ideal Zulip universe.)
Example custom Zulip Reader apps can be serious or whimsical.
Serious
- A user tags a message with an alert word like
@followup
or@ticket
. - A user needs help computing something, like a simple math expression or a timezone conversion.
- A World Reader custom app posts something to a Zulip stream that we want to cross-post to another external system.
- A user wants the custom app to query the outside world, like look up the weather or search Wikipedia.
- A bot collects RSVPs for an event.
- A bot conducts a user survey.
Whimsical
- A user wants to see a random quote of the day or a random cat fact.
- A user wants to tell the office telepresence robot to "turn left."
Setting aside whether a custom app is performing a serious or whimsical function, there are a few different types of Zulip Readers:
- Some readers will do simple local computations and post right back to Zulip.
- Some readers will do more expensive/web-related computations like searching Wikipedia, but then post right back to Zulip.
- Some readers will mutate the outside world in some way, like posting messages to third party APIs or controlling hardware.
- Some readers will do some combination of the prior bullets.
Deployment issues
Zulip currently provides only minimal deployment support for Zulip Reader custom apps:
- It ships with a few native server-side bots like the welcome bot and the notifications bot. (These are nice to have, but they are so tightly integrated into the Zulip core that they don't act as great examples for future app authors, and they are not easy to extend/customize.)
- Zulip does ship an API client that can conveniently read a
.zuliprc
file, poll for incoming messages/events, and post new messages to the Zulip server.
Local deployment
If you download the API client and write a bot that reads from Zulip, you face the following challenges if you deploy your code on your own devices:
- It can be difficult to keep the app running 24/7.
- You may have latency issues connecting to the server.
- If you want super-user permissions, you have to secure the API key.
- Without integration to the Zulip server, the app may spin needlessly during upgrades.
- If you've written a personal-use bot, it can be difficult to distribute code to your friends and have them be able to deploy it.
- If you've written a general-use bot, it may be difficult to persuade your admin to give you a superuser account.
We want to make it easier to deploy Zulip Readers on Zulip hardware. The following document talks about how we want to enable this from a code structuring standpoint:
This document, on the other hand, is more about designing the Zulip backend system to support eventual deployment of reader apps on the Zulip server.
Before we talk about server-side apps, we should consider an intermediate solution.
Non-Zulip dedicated hardware
There are some scenarios, mostly with general-purpose "serious" custom apps, where an app author might use the following development process:
- Create a prototype and deploy it locally.
- Publicize the app and deploy it on non-Zulip hardware.
- Contribute the app to the Zulip distribution, so that admins can run it Zulip-side.
To give a concrete example, let's say that I work for a company that is
building an issue tracker, and we want to offer Zulip support. I would
start by writing a Zulip Reader that scans for the alert word @ticket
on certain public Zulip streams, and part of that app would have logic
to post to my company's issue-tracking API.
Once I'm confident in my prototype, I will probably run it on dedicated company hardware that might already have tight physical security, 24/7 IT monitoring, etc.
But what if I don't have this kind of infrastructure available to me? Typically what I will do instead is rent time on some kind of hosting service. Some hosting platforms are basically just remote Unix systems, but others are more oriented toward hosting web apps.
Zulip's current roadmap assumes that authors will likely gravitate toward web-based solutions (even if it's just running a web server on their own Unix host in the cloud).
Zulip intends to offer support for "outgoing webhooks." The term "outgoing webhook" can be confusing, depending on your perspective, but it simply means that an HTTP request is outgoing from Zulip, so that it will hit a web endpoint that runs a third-party custom app.
Zulip will allow the custom app author, probably with the help of a Zulip admin, to configure Zulip to send a subset of Zulip messages to the author's web endpoint, and then the protocol for the custom app will to read the HTTP request and send some kind of HTTP response that optionally results in a message being written to Zulip. Meanwhile, the custom app can mutate the "world" as it sees fit.
Zulip-side support for reader apps
Even for app authors that have access to dedicated hardware, there would be several advantages to running Zulip Readers under the same umbrella as the core Zulip system.
- Your app will automatically inherit the uptime of the Zulip server itself (in terms of hardware availability).
- There will be no network latency between the app and the server.
- Securing apps to have superuser permissions will be less problematic.
- Keeping your app in sync with Zulip upgrades could become more automatic.
- Allowing multiple users in your realm to run their own copies of personal-use bots would be easier to administer.
The only problem with the above bullets is that we haven't built out any of that infrastructure yet.
We do have pending PR #1393, which addresses some of the issues that might come up.
In order to run apps inside the Zulip server, we basically need to solve the problems below. (One assumption is that we don't run apps truly in-process.)
- Contributions: We need a process for users to contribute code.
- Configuration/Discovery: We need Zulip to be able to find which apps are allowed to run for a particular deployment. (The admin may choose to run only a subset of contributed apps.)
- Queuing: We need to queue up events for readers, with some possible optimizations to scan for alert words during the in-process part of the call.
- Drivers: We need a generic driver that can pull events off of a queue and hand them off to our specific reader objects.
- Nannying: We need to launch readers with some kind of supervisord-like nannying.
- Pausing: We probably need a way to pause/stop readers without stopping the Zulip main processes. (At first this may just be part of solving the nanny problem.)
- Identity: We need to identify reader instances as specific Zulip users (non-owned bot, human-owned bot, or human).
- Superusers: We may need some readers to have users with special privileges like being auto-subscribed to all public streams.
- Read-only: We may need some readers at the other end of the spectrum to be highly locked down, e.g. enforce that they truly only have read access to Zulip messages.
- UI: We will want to provide some UI features that give admins and/or regular users visibility into which server-side apps are running.