skip to navigation
skip to content

Terms 0.1.0b5

A smart knowledge store

Latest Version: 0.1.0b16

The Terms knowledge store


Terms is licensed under the GPLv3, and is hosted at github.

The Terms language


  • define new words (nouns, verbs, and names);
  • build facts out of your defined words;
  • build rules that combine given facts to produce new facts;
  • perform complex queries.



In any case, Terms is based on a first order theory, interpreted in a finite universe, so it might be implemented in Prolog; that’s why I specified “idiomatic”.

To try the examples given below, if you have installed Terms, you have to type “terms” in a terminal, and you will get a REPL where you can enter Terms constructs. To install Terms, follow the instuctions in the INSTALL.rst.

More examples can be found here and in the github repository.


The main building block of Terms constructs are words.

To start with, there are a few predefined words: word, verb, noun, number, thing, and exist.

New words are defined relating them to existing words.


As we shall see below, these relations are formally similar to the set theory relations “is an element of” and “is a subset of”.

In English, we express the first relation as “is of type”, and in Terms it is expressed as:

word1 is a word2.

So we would say that word1 is of type word2, defining word1 in terms of word2 (so word2 must have been defined before, or be predefined). The second relation is expressed in English as “is subtype of”, and in Terms:

a word1 is a word2.

So, we would say that word1 is a subtype of word2, also defining word1 in terms of word2. Among the predefined words, these relations are given:

word is a word.
verb is a word.
a verb is a word.
noun is a word.
a noun is a word.
thing is a noun.
a thing is a word.
exist is a verb.
a exist is a word.
number is a word.
a number is a word.


a person is a thing.
a man is a person.
a woman is a person.
john is a man.
sue is a woman.

These relations have consecuences, given by 2 implicit rules:



thing is a word.
person is a word.
person is a noun.
john is a word.
a man is a thing.
john is a thing.
sue is a person.




That can be read as: love is defined as a subtype of exist, and when used in facts it can take a subject of type person and an object labelled who also of type person.

The primitive verb is exist, that just defines a subj object of type thing. There are more predefined verbs, the use of which we shall see when we explain the treatment of time in Terms.



(love john, who sue).

The subj object is special: all verbs have it, and in facts it is not labelled with subj, it just takes the place of the subject right after the verb.

Verbs inherit the object types of their ancestors. The primitive exist verb only takes one object, subj, of type word, inherited by all the rest of the verbs. So, if we define a verb:

to adore is to love.

It will have a who object of type person. If adore had provided a new object, it would have been added to the inherited ones. A new verb can override an inherited object type to provide a subtype of the original object type (like we have done above with subj; subj is predefined to be of type word.)

Facts are words, “first class citizens”, and can be used wherever a word can be used. Facts are words of type exist, and also of type <verb>, were <verb> is the verb used to build the fact. So our facts are actually synctactic sugar for (love john, who sue) is a love.

The objects in a fact can be of any type (a word, a verb, a noun, a thing, a number). In addition, they can also be facts (type exist). So, if we define a verb like:


We can then build facts like:


And indeed:



We can build rules, that function producing new facts out of existing (or newly added) ones. A rule has 2 sets of facts, the conditions (given first) and the consecuences. The facts in each set of facts are separated by semicolons (conjunctions), and the symbol -> (implication) separates the conditions from the consecuences. A simple rule might be:

(love john, who sue)
(love sue, who john).


We can use variables in rules. They are logical variables, used only to match words, and with a scope limited to the rule were they are used. We build variables by capitalizing the name of the type of words that it can match, and appending any number of digits. So, for example, a variable Person1 would match any person, such as sue or john. With variables, we may build a rule like:

(love Person1, who Person2)
(love Person2, who Person1).

If we have this rule, and also that (love john, who sue), the system will conclude that (love sue, who john).


(want john, what Exists1)

With this, and (want john, what (love sue, who john))., the system would conclude that (love sue, who john).


(LoveVerb1 john, who Person1)
(LoveVerb1 Person1, who john).

In this case, LoveVerb1 would match both love and adore, so both (love john, who sue) and (adore john, who sue) would produce the conclusion that (love sue, who john) or (adore sue, who john).

For a more elaborate example we can define a new verb:


and a rule:

(be-allowed Person1, to LoveVerb1)
(LoveVerb1 Person1, who Person2).

Then, (be-allowed john, to adore) would allow him to adore but not to love.

We can use word variables, e.g. Word1, that will match any word or fact.


(be-allowed Person1, to LoveVerb1)


Integers are of type number. We don’t define numbers, we just use them. Any sequence of characters that can be cast as an integer type in Python are numbers in Terms, e.g.: 1.

Number variables are composed just with a capital letter and an integer, like N1, P3, or F122.

Pythonic conditions

In rules, we can add a section where we test conditions with Python, or where we produce new variables out of existing ones. This is primarily provided to test arithmetic conditions and to perform arithetic operations. This section is placed after the conditions, between the symbols <- and ->. The results of the tests are placed in a condition python variable, and if it evaluates to False, the rule is not fired.

To give an example, let’s imagine some new terms:

to aged is to exist, age a number.
a bar is a thing.
club-momentos is a bar.
to enters is to exist, where a bar.

Now, we can build a rule such as:

(aged Person1, age N1);
condition = N1 >= 18
(enters Person1, where Bar1).

If we have that:

(aged sue, age 17).
(aged john, age 19).

The system will (only) conclude that (enters john, where club-momentos).



Classical negation

Any fact can be negated by prepending ! to its verb:

(!aged sue, age 17).


Negation by failure


Some care must be taken with the count function. If a fact is entered that might match a pythonic count condition, it will never by itself trigger any rule. Rules are activated by facts matching normal conditions; and pythonic conditions can only allow or abort those activations. In other words, when a fact is added, it is tested against all normal conditions in all rules, and if it activates any rule, the pythonic conditions are tested. An example of this behaviour can be seen here. If you examine the ontology in the previous link, you will see that it is obviously wrong; that’s the reason I say that care must be taken. Counting happens in time, and it is not advisable to use it without activating time.



dbms = postgresql://terms:terms@localhost
dbname = mykb
time = normal
instant_duration = 60

If it is activated, several things happen.

The first is that the system starts tracking the present time: It has an integer register whose value represents the current time. This register is updated every config['instant_duration'] seconds. There are 3 possible values for the mode setting for time: If the setting is none, nothing is done with time. If the setting is normal, the current time of the system is incremented by 1 when it is updated. If the setting is real, the current time of the system is updated with Python’s import time; int(time.time()).

The second thing that happens is that, rather than defining verbs extending exist, we use 2 new verbs, occur and endure, both subtypes of exist. These new verbs have special number objects: occur has an at_ object, and endure a since_ and a till_ objects.

The third is that the system starts keeping 2 different factsets, one for the present and one for the past. All reasoning occurs in the present factset. When we add a fact made with these verbs, the system automatically adds to occur an at_ object and to endure a since_ object, both with the value of its “present” register. The till_ object of endure facts is left undefined. We never explicitly set those objects. Each time the time is updated, all occur facts are removed from the present and added to the past factset, and thus stop producing consecuences. Queries for occur facts go to the past factset if we specify an at_ object in the query, and to the present if an at_ object is not provided. The same goes for endure facts, substituting at_ with since_. We might say that the endure facts in the present factset are in present continuous tense.

The fourth thing that happens when we activate the temporal logic is that we can use a new predicate in the consecuances of our rules: finish. This verb is defined like this:


And when a rule with such a consecuence is activated, it grabs the provided what fact from the present factset, adds a till_ object to it with the present time as value, removes it from the present factset, and adds it to the past factset.

There is also the temporal verb exclusive-endure, subverb of endure. The peculiarity of exclusive-endure is that whenever a fact with such verb is added to the knowledge base, any previous present facts with the same subject and verb are finish ed.

A further verb, happen, derived from occur, has the singularity that, when a fact is added as a consecuence of other facts, and is built with a verb derived from happen, is fed through the pipeline back to the user adding the facts that are producing consecuences.


Queries are sets of facts separated by semicolons, with or without variables. If the query contains no variables, the answer will be true for presence of the asked facts or false for their absence. To find out whether a fact is negated we must query its negation.

If we include variables in the query, we will obtain all the variable substitutions that would produce a true query, in the form of a json list of mappings of strings.


Miscelaneous technical notes.

  • I have shown several different kinds of variables, for things, for verbs, for numbers, for facts. But the logic behind Terms is first order, there is only one kind of individuals, and the proliferation of kinds of variables is just syntactic sugar. Person1 would be equivalent to something like “for all x, x is a person and x…”. LoveVerb1 would be equivalent to something like “for all x, a x is a love and x…”.
  • The Python section of the rules is exec``ed with a dict with the ``condition variable in locals and an empty dict as globals. We might add whatever we like as globals; for example, numpy.

The Terms Protocol


$ mkdir -p var/log
$ mkdir -p var/run
$ bin/kbdaemon start


A message from a client to the daemon, in this protocol, is a series of utf8 coded byte strings terminated by the string 'FINISH-TERMS'.


    • If the construct is a query, the response is a json string followed by the string 'END';
    • If the constructs are definitions, facts and/or rules, the response consists on the series of facts that derive as consecuences of the entered constructs, that are constructed with a verb that is to happen, terminated by the string 'END'.
  • If there is a lexicon: header, the response is a json string followed by the string 'END'. The contents of the json depend on a second header:
    • get-subwords returns a list of word names that are subword of the word whose name is given after the header.
    • get-words: returns a list of word names that are of the type of the word whose name is given after the header.
    • get-verb: return a representation of the objects that the verb named after the header has. For each object, there is a list with 3 items:
      • A string with the name of the label;
      • A string with the name of the type of the object;
      • A boolean that signals that the object must be a fact in itself.
  • If there is a compiler: header:
    • If there is an exec_globals: header, the string that follows is assumed to be an exec_global, and fed to the knowledge store as such.
    • If there is a terms: header, what follows are assumed to be Terms constructs, and we go back to the first bullet point in this series.

Installation and usage

Installation with setuptools on a virtualenv

You don’t need to use pythonbrew, but you must make sure you are using python 3.3.0 or above:

$ pythonbrew use 3.3.0

Make a virtualenv, and install setuptools:

$ pyvenv test-terms
$ cd test-terms/
$ . bin/activate
$ wget -O - | python

Install Terms (in this case, with PostgreSQL support):

$ easy_install Terms[PG]

Installation with buildout on a clean debian machine

I use this to develop Terms.

Start with a clean basic debian 7.1 virtual machine, only selecting the “standard system utilities” and “ssh server” software during installataion.

Some additional software, first to compile python-3.3:

$ wget
$ tar xzf Python-3.3.2.tgz
$ cd Python-3.3.2
$ ./configure
$ make
$ sudo make install

Install git, and an RDBMS:


Allow method “trust” to all local connections for PostgreSQL, and create a “terms” user:

$ sudo su - postgres
$ psql
postgres=# create role terms with superuser login;
postgres=# \q
$ logout

Get the buildout:

$ git clone

Make a python-3.3.2 virtualenv:

$ cd terms-project
$ pyvenv env
$ . env/bin/activate


$ vim config.cfg
$ python
$ bin/buildout

Now we initialize the knowledge store, and start the daemon:

$ bin/initterms -c etc/terms.cfg

Now, you can start the REPL and play with it:

$ bin/terms -c etc/terms.cfg
>> a man is a thing.
>> quit

Interfacing with Terms

Once installed, you should have a terms script, that provides a REPL.

If you just type terms in the command line, you will get a command line interpreter, bound to an in-memory sqlite database.


dbname = mykb
time = none

Then you must initialize the knowledge store:

$ initterms mykb

And now you can start the REPL:

$ terms mykb

In the configuration file you can put as many sections (e.g., [mykb]) as you like, one for each knowledge store.

Using PostgreSQL


$ easy_install Terms[PG]


dbms = postgresql://terms:terms@localhost
dbname = testkb
time = normal


eperez@calandria$ initterms mykb
eperez@calandria$ terms mykb
>> a person is a thing.
>> john is a person.
>> sue is a person.
>> (love john, who sue).
>> (love john, who sue)?
>> (love sue, who john)?
>> quit
eperez@calandria$ terms testing
>> (love john, who sue)?

Using the kbdaemon


dbms = postgresql://terms:terms@localhost
dbname = testkb
time = normal

Now you can start the daemon:

$ bin/kbdaemon start
kbdaemon started



There is a mailing list at google groups. You can also open an issue in the tracker. Or mail me <enriquepablo at google’s mail domain>.

File Type Py Version Uploaded on Size
Terms-0.1.0b5.tar.gz (md5) Source 2013-09-09 75KB