loading x elements...

This page explains how Elody works on a technical level. It mostly explains what Rules, Options and Tags are, and how they work.

In short:

  • When in a Scenario, Elody runs through a control loop until the problem is solved.
  • Rules are the basic building blocks of Elody. They tell Elody what Programs to execute.
  • Options are basically temporary Rules that are created at runtime, can encapsulate logic, and can be interacted with.
  • Tags are used to store data and to describe and communicate information in a uniform way.

For the technical documentation of the way Rules and Options are defined, see here.

Elody's basic control loop

When a Scenario is active, Elody keeps running through a control loop forever. This loop makes use of Rules and Options to identify and deal with problems as they arise. The loop looks like this:

  • Check what the current situation of the Scenario is. The situation is described by the Tags that have been created so far.
  • Identify all Rules and Options that would be appropriate to use based on the situation.
  • Pick the best one. This is usually based on the Rule's rating, but the Scenario Plan can explicitly overwrite the rating. This decision is logged in a Decision Process Message, which can be found in Developer Mode.
  • Execute the selected Rule or Option. This may cause Programs to be run, or cause other Events to be created.
  • Keep executing Events until you run out of things to do, then go back to step 1 and repeat.

Rules

Rules are defined by software contributors, and receive ratings by users.

Rules are the first step that decides what Elody will do. The most important work is usually done by Programs, and Options are usually used to make important decisions, but the Rules are the initial step that kicks off everything else.

A Rule is written in JSON format. After a Rule has been defined, you can inspect it in the browser, annotated with links to the appropriate point of the documentation, so that you can understand it more quickly. See here for an example.

The parts of a Rule

A Rule has three main parts. The Dependencies determine if the Rule is relevant at all, the Trigger tells Elody whether or not the Rule should be executed and selects arguments, and the Actions determine what happens when Elody executes the Rule.

  • The Dependencies

    The dependencies are a list of Symbols, each with a weight and optionally a string for further filtering. They determine whether or not a Rule is considered for execution at all.

    If all Symbols in a dependency have been signalled with '!set_signal_weight' Tags, then the Rule is considered for execution. However, it is first checked if the next part of the Rule, the trigger, also matches.

    The dependencies of a Rule act as a prefilter. Unlike the trigger, checking if a Rule's dependencies are fulfilled can be done quickly and does not slow down Elody.

    Some more details about the way this works

    If necessary, this can be made more complex: Dependencies can also have weights and filter strings, '!set_signal_weight' Tags can have weights and comments, and Rules can have a threshold.

    Each dependency of a Rule is considered active only if a '!set_signal_weight' Tag exists that matches the dependency. The dependency matches if the Symbols are the same and the filter string of the dependency is either not required, or is equal to the comment of the '!set_signal_weight' Tag.

    The weight of an active dependency is the product of the '!set_signal_weight' Tag's weight (defaults to 1) and the dependencies weight. It can be negative.

    If the sum of the weights of all active dependencies of a Rule is above a threshold, then the Rule passes

    (If this all sounds confusing, don't worry, it's pretty straight-forward in practice and you usually can just list the dependencies you need without worrying about weights.)

  • The Trigger

    The trigger of a Rule has two purposes: It confirms whether or not the Rule should be executed, and it finds arguments for the execution.

    The trigger is able to access any of the objects that have been created in the Scenario so far. It can look for patterns among those objects to check if the Rule is applicable.

    In addition to deciding whether or not the Rule should execute, the trigger can also capture objects to be used as arguments by the Rule's actions.

    The trigger can perform pattern matching. Most Rules use very simple patterns, but if necessary a trigger can become very complex and can use nested checks for the presence or absence of entire structures of Tags.

  • The Actions

    The actions of a Rule are executed only if the dependencies exist, the trigger finds a set of objects that match its requirements, and Elody determines that this Rule, out of all the other candidate Rules, should be executed next.

    Through its actions, a Rule can create Tags, run programs, create messages, create Options, and many things more.

A short example

Let us consider a simple Rule as an example. The purpose of this Rule is to perform a prediction on a time series, using a program that can work with CSV files.

The Rule has two dependencies: It looks for two Symbols. One that means "A prediction of a time series is wanted" and one that means "This file is a CSV file". Tags with these two Symbols are created by other Rules and Programs. The Rule is only used if both dependencies are found.

The Rule has two triggers: Firstly, it looks for a File that has a Tag with the aforementioned Symbol attached to it. Secondly, it also looks for a File that has a Tag attached to it that means "This file describes parameters that should be used for a prediction". The latter of the two is marked as an optional argument, since it is not strictly necessary. The trigger will be active even if this second file is not found, but if it is found then that file is available to the actions as an argument.

The Rule has one action: it executes a Program that can perform a prediction. It passes the above two Files to the program. If the File with the parameters could not be found, the Program is told to use default parameters instead.

This is the general format of a Rule. Examples of how this looks in code can be found in the tutorial.

Multiple versions of the same Rule

You can define multiple Versions of the same Rule.

When Elody searches for applicable Rules, if several versions of the same Rule pass the Dependencies checks and the filters set by the user, only the latest of them will be considered further.

For example, you may have a Rule that is fairly popular and has received a lot of positive feedback, so Elody uses it a lot. However, you want to make a small change to it and upload a new version. The rating of this new version is independent of the rating of the previous version. Elody will automatically use the latter version if its rating passes the safety parameters of the user, and will resort to the previous version otherwise.

It is done this way for security reasons: Without this, a malicious developer could upload a harmful rule as a new version of an established rule to piggy-back off its popularity. Since the new version has to gain feedback independently of the first, this can't happen.

(This website is still in Beta. This may be changed later depending on how well it works.)

Options, and how they differ from Rules

Options are similar to Rules, but not the same.

They serve multiple purposes, such as encapsulating planned actions so that they can be interacted with, priotitizing different actions relative to each other, and presenting a choice to the user.

Where Rules are constant and shared between Scenarios, Options are created within a Scenario and specific to it.

Options have triggers and actions, just like Rules do. They do not have dependencies, because they are always active.

Options can have a Display. This is used to render a Message to the user if Elody determines that the user should choose between several possible Options to execute. This is optional, as some Options are not intended to be presented to the user at all.

While Rules are only considered for execution if their dependencies match with !set_signal_weight Tags, Options are only considered if !set_option_confidence Tags are applied to them directly. In this way, the importance of an Option can be adjusted by other Rules, Options and Programs simply by assigning !set_option_confidence Tags to it.

The prioritization of Rules and Options is closely connected:

Most of the time, Rules are given priority. If however the quality/rating of the best available Rule is below some threshold (determined by parameters of the Scenario), Elody will look at the Options instead. If an Option's rating is high enough, it is executed right away. If no Option is rated quite high enough, Elody will instead present all Options with some minimum rating to the user, so that they can make the choice themselves.

Why does it work like this?

The intent is that Rules should almost always work indirectly:

Rather than executing a Program directly, a Rule should create an Option that will execute that Program later. As Rules take priority over Options, a number of different Options will be created before any Programs are executed at all. Since Options are objects that are available for inspection by a Rule's or Option's trigger, Rules and Options can react to the presence of existing Options.

This allows Rules to react to what other Rules were planning, interact with each other and suggest alternatives. The later pages of the developer tutorial give examples for such interactions.

Elody Decision Process messages

When you are in Developer Mode in a Scenario, you will see some Messages titled "Elody's decision process". One of these messages is created each time Elody chooses which Rule or Option to execute next.

When you are reading other people's Rules or writing your own and you find yourself confused, these should be your first stop for figuring things out.

Tags

If Rules and Options are the commands that tell Elody what to do, then Tags are the language those commands are written in.

Tags are a very flexible way to represent arbitrary information.

They can be used to describe objects, to define tasks, to mark the progress on tasks, and for many other usecases.

Rules are designed to work with Tags in an intuitive way. To understand how, let's look at an example.

An example

Each Tag consists of a Symbol, and optionally a weight (number) and a comment (string). A Tag may stand on its own, or it may have any number of arguments. Since Tags can have other Tags as arguments, you can build a complex DAG (Directed Acyclic Graph) by combining Tags with each other.

The followng DAG is an excerpt of a real Tag structure that is used by the Symbol task_data_cleansing_and_analysis_for_pandas:

This graph is zoomable. Each Node in it represents one object, and most of the objects are Tags. Some objects are replaced with grey placeholders to reduce the size of the graph. Tags with the same Symbol are drawn with the same color. Tags have tooltips.

Understanding the example graph

The root of this DAG is a Tag called modifiable_file. This Tag represents a file, such as an excel sheet uploaded by a user, and keeps track of any modifications to that file as well as any analyses performed on it.

Some of the Tags occur multiple times, and each new occurrence is an update that overwrites the last. For example, there are multiple current_file Tags attached to the central modifiable_file Tag. Each time a modification was performed on the file, a new current_file Tag was created to associate the new file with all of the existing information. Another Tag that works the same way, where new versions overwrite old ones, is info_column_types, which attaches to a column and describes what datatypes that column has.

There are also other Tags that do not overwrite each other, like for example the column Tags. Each of these represents one column of the file.

Whether or not a Tag is meant to be overwritten, and any other question about the way the Tag should be used, should be explained by the description of the Symbol the Tag is based on. It is important to give clear description when defining a new Symbol, so that other developers know how they are mean to use it.

Both of these very different types of relationships can easily be modeled with Rules:

  • Say you are writing a program to visualize a column of a file.

    You need to know the type of the column because your analysis depends on that information.

    Rather than having to analyze the column in the file yourself, you can look at the existing Tags. This way you don't need to worry about edge cases yourself, because that's the responsibility of whoever created the Tags.

    The search performed by a Rule's trigger will pick the latest Tag by default, so to find the types of a column, just search for a Tag with Symbol info_column_types targeting the column you want:

    {
        "type" : "tag",
        "symbol" : "info_column_types",
        "arguments" : {
            0 : "myColumn"
        }
    }
  • Say you are writing a program that works only on text data.

    You want to know if there is any column that contains text.

    It's possible that the datatype of a column changes. Maybe a column used to be text, but then it was parsed and converted to numbers later.

    We now have to search for a column while we have to make sure that the latest info_column_types Tag on it marks it as a text.

    Fortunately, this is not hard to express either:

    {
        "type" : "tag",
        "symbol" : "column",
        "targeted_by" : [
            {
                "type" : "tag",
                "symbol" : "info_column_types",
                "search_postfilter" : {
                    "type" : "tag",
                    "comment_contains" : ",string,"
                }
            }
        ]
    }

    The 'search_postfilter' is only applied after the latest info_column_types Tag has been found, so it can be used to validate if the latest info_column_types Tag has a comment that contains ",string,". The description of the Symbol info_column_types explains the usage.

The developer tutorial gives many examples. Understanding this will get very intuitive with practice. By the way: If you are wondering how to delete a column Tag if they don't overwrite each other, that's what the !nullify Tag is for.

If you are the type of person who learns better from practical examples, you can also have a look at the Scenario Plan (developers only) Data Exploration demonstration with example file and tutorial information. It is the first practical example written for Elody. It is intentionally complex, and was designed as a stress test to ensure that the Rule logic we use is not missing anything. You can use Developer Mode to inspect what happens in the background and have a look at the Rules and Options we use for it. Keep in mind that this example was written as a stress-test: It is far more complex than most programs will be in practice.

Note

By the way, you can create graph representations of Tag structures like the one above using the Symbol !export_object. It can be used to extract a structure of files and Tags so that it can be imported again in another Scenario. This way you can use one Scenario to perform an analysis and create Tags, while a second Scenario makes use of those Tags later.

Note

Rules and Programs can both receive feedback from users. The rating of Rules is used by Elody for prioritization. The rating of Programs currently has no direct practical effect. This may change, as this platform is still under development.