This specification defines a cognitive graph database model featuring chunks as collections of properties, and rules that operate on them in conjunction with highly scalable graph algorithms, and a simple notation for serializing graphs. The model is designed with the aim of facilitating machine learning for vocabularies and rules, and inspired by advances in the cognitive sciences on the organisation of the mammalian brain.

This document reflects implementation experience, but is still subject to change. Feedback is welcome through GitHub issues or on the public-cogai@w3.org mailing-list (with public archives).

Introduction

This specification introduces a simple notation and computational model for chunk graphs and rules. Chunks are collections of properties (name/value pairs) together with sub-symbolic parameters that mimic the characteristics of human memory, including the forgetting curve and stochastic recall. Chunk rules operate on chunk buffers that mimic the connections between the basal ganglia and cortical modules.

Architecture of the cognitive database model

At its heart, the model is based on [=graphs of chunks=] composed of a collection of [=chunks=], where each [=chunk=] represents a collection of basic familiar units that have been grouped together and stored in memory. To ease manipulation of procedural knowledge as declarative knowledge, [=chunks=] are used to model both declarative knowledge (i.e. data) as well as procedural knowledge (i.e. rules). See [[CHUNKS-INTRO]] for details.

The [=rule engine=] operates on a set of [=modules=], where each [=module=] has a [=graph of chunks=] and supports an extensible set of operations on chunks. Each module has a single [=module buffer=] that the [=rule engine=] can process, and that can hold one and only one [=chunk=] at a time.

This specification also defines a serialization format for graphs of chunks, used in examples throughout this specification.

The grammatical rules in this document are to be interpreted as described in [[[RFC5234]]] [[RFC5234]].

Conformance classes

Conformance to this specification is defined for four conformance classes:

Chunks document
A serialization of a [=graph of chunks=] as a file. A [=chunks document=] is conformant to this specification if it follows the grammar described in .
Authoring tool
An application that writes a [=chunks document=]. An [=authoring tool=] is conformant to this specification if it writes conforming [=chunks documents=].
Parser
A [=parser=] transforms a [=chunks document=] into another representation. A [=parser=] is conformant to this specification if it accepts any conforming [=chunks document=].
Rule engine
A processing application that operates on graphs of chunks and rules, organized following the cognitive agent architecture described in this specification. A [=rule engine=] is conformant to this specification if it follows the algorithms defined in .

Data types

This document uses the following restricted set of data types to describe [=chunks=]. See for a formal definition of their serialization.

A number represents a double-precision 64-bit format value as specified in the IEEE Standard for Binary Floating-Point Arithmetic [[IEEE-754-2019]]. It is serialized in base 10 using decimal digits, following the same grammar as numbers in JSON [[RFC8259]].

A boolean represents a logical entity having two values. It is serialized as either the literal name true, which gets interpreted as a truthy value, or the literal name false, which gets interpreted as a falsy value.

A date is a string that represents a date according to the [[Date and Time Profile]] of the [[ISO8601]] standard. A [=date=] value implicitly creates a read-only chunk whose type is iso8601 with properties that match actual date components.

Whether or not to prepend iso8601 and its related properties with @ (see issue #2).

A string literal is an arbitrary set of characters. It is serialized enclosed in double quotes, following the same grammar as strings in JSON [[RFC8259]].

A name is a string that can include letters, digits, period, hyphen, underscore and slash characters, and that cannot be interpreted as a [=number=], a [=boolean=]. Additionally, depending on the context under which it is used, a [=name=] may start with one of the name operators (see [[[#name-operators]]]).

Chunks and graphs

A chunk is a named typed collection of [=properties=]. A [=chunk=] is used to model both declarative knowledge and procedural knowledge as a collection of basic familiar units that have been grouped together and stored in memory.

A [=chunk=] has a [=type=] and an optional [=identifier=].

Chunk type

A chunk type is a [=name=] that documents the nature of a chunk. The [=type=] is used to group and index chunks. [=Rules=] typically apply to chunks of a given [=type=].

As a special case, the [=type=] may be formed by a single asterisk (*), which is used to describe a [=condition=] or [=action=] that matches any chunk [=type=].

Chunk identifier

The chunk identifier is a [=name=] that uniquely identifies a chunk within the graph it is defined in.

The chunk [=identifier=] is optional. If missing, it will be automatically assigned when the chunk is added to a chunk module.

Chunk properties

A chunk property is a [=name=]/[=value=] pair that describes a chunk across the particular dimension identified by the property name.

A value is either an [=atomic value=] or an ordered list of [=atomic values=] (values are comma-separated in serialized form).

An atomic value is either:

A property [=value=] |a| equals property [=value=] |b| when the following algorithm returns true:

Chunk context

A [=chunk=] may be scoped to a context, which identifies the specific situation under which the [=chunk=] should be considered to be true. This mechanism allows [=chunks=] to describe things that are only true in hypothetical situations.

[=Contexts=] can be used to express situations that involve the use of statements about statements, including beliefs, stories, reported speech, examples in lessons, abductive reasoning and even search query patterns. They are also useful for episodic memory when one wants to describe facts that are true in a given situation, for instance an episode when a person visited a restaurant for lunch, sat by the window, and had soup for starters followed by mushroom risotto for the main course. A sequence of episodes can then be modelled as relationships between contexts.

A [=chunk=] is associated with a specific [=context=] through an [=@context=] [=property=].

A [=chunk=] that is not explicitly associated with a [=context=] (i.e. in the absence of an [=@context=] property) belongs to the default context.

As illustrated in the previous example, [=contexts=] can be chained, e.g. to describe the beliefs of someone in a fictional story or movie, and to indicate when a context is part of several other contexts, thus creating a tree of [=contexts=].

Practically speaking, [=contexts=] make it possible to filter out non relevant [=chunks=] in [=conditions=] and [=actions=]. Two [=chunks=] may only [=match=] when they belong to the same context. For instance, a [=chunk=] that belongs to the context tom-belief-1 can only [=match=] [=chunks=] that also belong to that context, and de facto cannot match [=chunks=] that belong to the [=default context=]. In particular, a [=chunk=] that belongs to the [=default context=] can only [=match=] [=chunks=] that also belong to the [=default context=].

Is there a need for a limited form of automatic inheritance, e.g. when matching chunks in a context for a fictional account, should this also match chunks in the parent context? This would allow general knowledge to apply by default within fictional accounts.

Links between chunks

In this document, a link is a directed and labeled connection between two [=chunks=]. A [=link=] is automatically created whenever a chunk property [=value=] is a [=name=] that references an existing chunk [=identifier=].

The subject of the [=link=] identifies the [=chunk=] at the origin of the connection. The object of the [=link=] identifies the [=chunk=] targeted by the connection. The label of the [=link=] is the property [=name=].

When a [=chunk=] links to another [=chunk=], this implicitly creates a third [=chunk=] whose [=type=] is the name of the [=property=] that creates the [=link=], and that has two [=properties=]:

Graph of chunks

A graph of chunks is simply a collection of [=chunks=]. The vertices of the graph are the [=chunks=]. The edges of the graph are the [=links=] between the chunks.

Since [=links=] are directed, a [=graph of chunks=] is a directed graph.

Rules and modules

Rules

A rule is a [=chunk=] whose [=type=] is rule and that has:

A [=rule=] represents a unit of procedural knowledge. Rules consist of [=conditions=] and [=actions=].

Conditions

A condition is a [=chunk=] that describes the premises that must hold true for a [=rule=] to apply. A [=condition=] identifies which [=module=] it relates to through an [=@module=] property, defaulting to the goal module. A [=condition=] holds true when the [=chunk=] in the related [=module buffer=] is a [=matching chunk=] for the [=condition=].

Actions

An action is a [=chunk=] that can directly update [=module buffers=], or can do so indirectly, e.g. by sending messages to the [=module=] to invoke graph algorithms, such as graph queries and updates, or to carry out operations, e.g. instructing a robot to move its arm. When the algorithm or operation is complete, a response can be sent back to update the module's buffer. This in turn can trigger further rules as needed. An [=action=] identifies which [=module=] it relates to through an [=@module=] property, defaulting to the goal module.

In many cases, the actual operation that an [=action=] will carry out will appear as an [=@do=] property. Built-in operations are always supported (see ). Other actions may be supported. See section [[[#scripting-api]]] for a means for applications to define additional actions, e.g. as a way to invoke external actions for operating a robot arm or camera.

Matching chunks

A [=chunk=] |A| matches [=chunk=] |B| if the conditions below are all met:

  • Context. One of the following conditions holds true:
    • Neither |A| nor |B| have [=@context=] properties.
    • Both |A| and |B| have [=@context=] properties and |A|'s [=@context=] property [=value=] [=equals=] |B|'s [=@context=] property [=value=].
  • Identifier. One of the following conditions holds true:
    • |A| has an [=@id=] property whose [=value=] is an [=atomic value=] that [=equals=] |B|'s [=identifier=].
    • |A| does not have an [=@id=] property.
  • Inheritance. One of the following conditions holds true:
    • |A| has an [=@kindof=] property whose [=value=] |V| is an [=atomic value=], and |B|'s [=type=] is a subclass of |V|, meaning that |B|'s [=type=] is |V| or there exists a chain of kindof links between |V| and |B|'s [=type=].
    • |A| does not have an [=@kindof=] property.
  • Properties. For each [=property=] |p| in |A| whose [=name=] does not start with @, either of the following holds true:
    • |p|'s [=value=] is ! and there is no [=property=] in |B| with |p|'s [=name=].
    • |p|'s [=value=] is not ! and there exits a [=property=] in |B| with the same [=name=] and [=equal=] [=value=] as |p|.
  • Type. One of the following conditions holds true:
    • |A| has an [=@type=] property whose [=value=] is an [=atomic value=] that [=equals=] |B|'s [=type=].
    • |A| does not have an [=@type=] property, and |A|'s [=type=] is *.
    • |A| does not have an [=@type=] property, and |A|'s [=type=] and |B|'s [=type=] are identical.

@-properties for conditions and actions

The [=reserved names=] defined in this section may be used as [=property=] names in [=conditions=] and [=actions=] to control their behavior.

The @compile property

This used in a rule action to compile a set of chunks in declarative memory into a set of rules in procedural memory. The process starts with the chunk that cites the chunks used for the conditions and actions, and then applies to those chunks. Note that @compile may cite a list of IDs for chunks to be compiled.

The @context property

When used in a regular [=chunk=], identifies a chunk's [=context=]. When used in a [=condition=] or in an [=action=], [=matches=] a [=chunk=]'s [=context=].

The @do property

Specifies the graph algorithm or operation to execute. See for a list of common operations that are supported across modules.

The @for property

Iterates over a set of items in a comma separated list. The [=@from=] and [=@to=] properties may be used to restrict the iteration range.

The @from property

Specifies the zero-based starting index of an [=@for=] iteration. Value must be an integer.

The @id property

This is used in rule actions together with a value specify the chunk ID, e.g. to get a chunk with a given ID, or to add, update or replace an existing chunk with that ID.

The @index property

Used as part of an iteration over the values in a comma separated list with [=@for=].

The @kindof property

[=Matches=] a [=chunk=]'s [=type=] when that [=type=] is linked to the value of the [=@kindof=] property through a chain of kindof links. The property should be used in conjunction with a * type to match subclasses of a given class in a taxonomy.

The @map property and chunk type

This is used with [=@compile=] and [=@uncompile=] to reference a chunk of type [=@map=] that defines a map for property names and their meanings. See also the [=@unmap=] property.

The @module property

References the [=module=] a [=condition=] or [=action=] relates to. Value must be the [=module name=] of the targeted [=module=]. In the absence of an [=@module=] property, [=conditions=] and [=actions=] apply to the goal module.

The @more property

Queries the [=boolean=] flag set to true by the [=rule engine=] on the current [=chunk=] in [=@for=] and [=@do properties=] iterations when there are remaining [=chunks=] to iterate over.

The @pop property

An [=action=] property that removes the last [=atomic value=] from a [=value=]. If the [=value=] to process is already an [=atomic value=], the underlying property is removed.

If a [=@to=] property is also present, the removed [=atomic value=] is assigned to the [=property=] identified by the [=@to=] property. In the absence of a [=@to=] property, the removed [=atomic value=] is discarded.

The @priority property

The @priority property lets actions set the [=priority=] of a [=chunk=] when they add it to the queue for a module buffer (see [=module buffer=]).

The @push property

An [=action=] property that pushes an [=atomic value=] to the end of the [=value=] of the property identified by a companion [=@to=] property. If the targeted property does not exist yet, it is created.

In the absence of a [=@to=] property, this operation has no effect.

The @shift property

An [=action=] property that removes the first [=atomic value=] from a [=value=]. If the [=value=] to process is already an [=atomic value=], the underlying property is removed.

If a [=@to=] property is also present, the removed [=atomic value=] is assigned to the [=property=] identified by the [=@to=] property. In the absence of a [=@to=] property, the removed [=atomic value=] is discarded.

The @status property

Queries the [=module buffer/status=] of a [=module buffer=]. The [=rule engine=] sets the status of a [=module buffer=] with the outcome of the [=rule=]'s execution. Most operations are asynchronous, except [=@do clear=], [=@do update=] and [=@do queue=].

The @tag property

This property provides a means for rule conditions to test a module buffer for the result from a preceding action. Use @tag in the action to pass an identifier to the subsequent asynchronous response where it can be accessed via @tag in a rule condition.

The @to property

Companion [=action=] property used in [=@do properties=], [=@for=], [=@pop=], [=@push=], [=@shift=], [=@unshift=] operations.

Meaning and value constraints depend on the operation. See individual operations for details. For instance, when used in a [=@for=] operation, the property specifies the zero-based ending index of the iteration. Value must be an integer. When used in a [=@do properties=] operation, the property specifies the name of the [=module buffer=] onto which to write the current [=chunk=].

The @type property

[=Matches=] a [=chunk=]'s [=type=], or binds a variable to the [=chunk=]'s [=type=].

The @unmap property

This MUST reference a chunk of type [=@map=] that defines a map for property names and their meanings, performing the inverse of the mapping specified by the [=@map=] property.

The @unshift property

An [=action=] property that pushes an [=atomic value=] to the beginning of the [=value=] of the property identified by a companion [=@to=] property. If the targeted property does not exist yet, it is created.

In the absence of a [=@to=] property, this operation has no effect.

The @uncompile property

This used in a rule action to copy a set of rules in procedural memory to a set of chunks in declarative memory into a set of rules in procedural memory. The process starts with the chunk that cites the chunks used for the conditions and actions, and then applies to those chunks. Note that @uncompile may cite a list of IDs for chunks to be uncompiled.

Is there a need for a means to map chunk IDs for use with [=@compile=] and [=@uncompile=]? A potential solution would be to introduce @map-id by analogy to [=@map=].

Modules

A module is a [=graph of chunks=] associated with one and only one [=module buffer=]. A [=module=] has a module name that follows the [=name=] data type, and that is typically used to target the [=module=] in [=@module=] properties.

A [=module=] supports [=built-in operations=], and may support additional operations defined by the application when the [=module=] is initialized.

A [=module=] represents a cognitive database on which the [=rule engine=] may operate. It may be viewed as a region in the cerebral cortex, where the [=module buffer=] corresponds to the bundle of nerve fibres connecting to that region.

The [=rule engine=] automatically creates a module named goal, which will therefore always exist in a rule execution context.

The [=@module=] property allows [=conditions=] and [=actions=] to reference the [=module name=] of the [=module=] they relate to. In the absence of an [=@module=] property, [=conditions=] and [=actions=] apply to the goal module.

Module buffers

A module buffer is a container for at most one [=chunk=]. The mammalian brain is richly connected locally, and weakly remotely. A [=module buffer=] mimics the constrained communication capacity of the mammalian brains for such long range communication.

The [=rule engine=] operates on a module's [=graph of chunks=] through its [=module buffer=].

A [=module buffer=] has a status, (accessible via [=@status=]), whose value is initially [=status/okay=], and which reflects the outcome of the last [=action=] held and performed by the [=module buffer=]. Values can be:

pending
The operation is still pending.
okay
The operation completed successfully.

Switch to ok? This seems more common in technologies (e.g. HTTP) than okay.

forbidden
The operation was not allowed.
nomatch
The operation [=failed=] because there was no [=matching chunk=] for the [=action=] in the targeted [=module=].
failed
The operation failed.

This is analogous to the hypertext transfer protocol (HTTP) and allows rule engines to work with remote cognitive databases. To relate particular request and response pairs, use [=@tag=] in the action to pass an identifier to the subsequent asynchronous response where it can be accessed via [=@tag=] in a rule condition.

A [=module buffer=] has a queue, which is a set of [=chunks=], initially empty. Each chunk in the [=queue=] has a priority, represented by an integer from 1 to 10, with 10 the highest priority. The default [=priority=] is 5. [=Chunks=] are ordered by descending [=priority=] in a [=queue=]. When [=priorities=] match, [=chunks=] are ordered by insertion order (first in, first out).

The [=@priority=] property lets [=actions=] set the [=priority=] of a [=chunk=] when they add it to a [=queue=].

A [=module buffer=] is automatically cleared when the [=actions=] associated with the [=rule=] it contained did not update the contents of targeted [=module buffers=]. This pops the [=queue=] if it is not already empty.

Built-in operations

The [=@do=] property lets an [=action=] specify the graph algorithm or operation to execute. The default operation is to update the [=module buffer=], similar to calling [=@do update=].

All [=modules=] support the built-in operations defined in this section.

All [=modules=] also support the [=@for=] property to iterate over a set of items in a comma separated list. This has the effect of loading the [=module buffer=] with the first item in the list. The index range can optionally be specified with [=@from=] and [=@to=], where the first item in the list has index 0.

The @do clear operation

Clears the [=module buffer=] and pops the [=queue=].

The @do delete operation

Removes [=matching chunks=] from the [=graph of chunks=].

The @do get operation

Looks for a [=matching chunk=] in the module's [=graph of chunks=] and puts a copy of it in the [=module buffer=] if found. Modifying the [=properties=] of a [=chunk=] copied from a [=graph of chunks=] (e.g. through a [=@do update=] operation) will not alter the underlying [=graph of chunks=]. To save an updated [=chunk=], a [=@do put=] or [=@do patch=] command needs to be issued.

The @do next operation

Loads the next [=matching chunk=] to the targeted [=module buffer=] in an implementation dependent order.

The @do patch operation

If the [=chunk=] in the targeted [=module buffer=] has the same [=identifier=] as a [=chunk=] in the underlying [=graph of chunks=], patches the [=chunk=] in the [=graph of chunks=] with the [=properties=] that appear in the [=module buffer=], excluding [=properties=] prefixed with an @ character.

What is the expected behavior when the action has an @id property? It would seem reasonable for the action to synchronously update the [=identifier=] for the chunk in the module buffer prior to applying that chunk to patch the module's [=graph of chunks=].

The @do properties operation

Initiates an iteration over the [=properties=] of the [=matching chunk=] that do not begin with @. Each [=property=] is mapped to a new [=chunk=] with the same [=type=] as the [=action=]. The action's properties are copied over, and name and value properties are used to pass the [=property=] name and value respectively. The [=@more=] property is given the value true unless this is the final [=chunk=] in the iteration, in which case [=@more=] is given the value false. By default, the iteration is written to the same [=module buffer=] as designated by the [=action=] that initiated it. However, you can designate a different [=module buffer=] with the [=@to=] property. By setting additional properties in the initiating action, you can ensure that the rules used to process the property name and value are distinct from other such iterations.

The @do put operation

Saves the contents of the [=module buffer=] as a [=chunk=] to the module's [=graph of chunks=]. If the [=action=] has an [=@id=] property, this operation will overwrite the [=chunk=] with the same [=identifier=] or will create a new [=chunk=] with the given [=identifier=] if it does not exist already. This operation will also create a new [=chunk=] in the absence of an [=@id=] property.

If a chunk was loaded with @do get, then updated with @do update, would a call to @do patch create a new chunk if @id is not set? Or would it rather update the chunk in the graph?

The @do queue operation

Pushes a [=chunk=] to the [=queue=] for the [=module buffer=]. If a [=@priority=] property is set to an integer value between 1 and 10, the [=priority=] of the [=chunk=] in the [=queue=] is set to that value.

The @do update operation

Directly updates the [=module buffer=] if the chunk [=type=] for the [=action=] is the same as the [=chunk=] currently held in the [=module buffer=]. The operation updates the properties given in the [=action=], leaving aside properties prefixed with an @ character, and leaving other existing properties unchanged. If the chunk [=type=] for the action is not the same as the [=chunk=] currently held in the [=module buffer=], a new [=chunk=] is created with the properties given in the [=action=], excluding properties prefixed with an @ character. This is the default action when an [=action=] has neither an [=@do=] property nor an [=@for=] property.

How can one update the properties prefixed with an @ character in a [=chunk=] such as [=@context=], [=@subject=] or [=@object=]? A potential solution is to support the [=@map=] property as already supported for [=@compile=] and [=@uncompile=], along with an [=@unmap=] property. This would involve an asynchronous operation.

Name operators

Operators defined in this section may be used on their own as [=names=] or in front of [=names=] to alter their meaning.

The variable operator ?

The variable operator ? may be prepended to a [=name=] when used as a [=property=] value to turn the [=name=] into a variable which represents a symbolic name to a [=value=]. A [=variable=] gets bound to a [=value=] in a [=condition=]. The [=value=] can then be referenced in [=actions=] using the [=variable=]'s name. Effectively, [=variables=] allow applications to copy information from rule [=conditions=] to rule [=actions=].

[=Variables=] are scoped to the [=rule=] where they appear.

[=Variables=] are [=bound=] to a [=value=] the first time they appear in a [=condition=]. Subsequent occurrences of the same [=variable=] in a [=condition=] reference their [=value=].

[=Variables=] may represent any type of [=value=]. In particular, a [=variable=] that [=matches=] a property whose value is a list of [=atomic values=] gets bound to the list of [=atomic values=].

A [=condition=] that contains a [=property=] with a list of [=variables=] [=matches=] a list of [=atomic values=] that has the same length. The [=condition=] does not [=match=] when the lengths of the lists differ.

The wild card operator *

The wild card operator * may be used on its own in lieu of a [=name=] in one of the following cases:

The negation operator !

The negation operator ! may be prepended to [=names=] to negate the outcome of their evaluation. The [=negation operator=] may be used in one of the following cases:

The [=negation operator=] cannot be prepended to a [=variable=] that has not yet been [=bound=]. Similarly, the [=negation operator=] cannot be used on its own as an [=atomic value=] in a list. More generally, the [=negation operator=] cannot be used elsewhere than in the cases detailed above.

A second [=negation operator=] prepended to a [=name=] that starts with a [=negation operator=] cancels the effect of the first [=negation operator=].

The reserved name operator @

The reserved name operator @ may be prepended to a [=name=] to denote a reserved name with specific meaning. Most [=reserved names=] are to be used as [=property=] names, typically in [=conditions=] and [=actions=] to control their behavior (see [[[#properties-for-conditions-and-actions]]]). Some of them may be used as chunk [=types=] to denote a chunk with specific meaning (see [[[#mapping-to-rdf]]]).

Rule engine execution

The rule engine is invoked whenever any of the module buffers are changed, including when [=queue=] is popped. It then searches for rules whose conditions match the current state of the buffers. If a rule has multiple conditions, they must all match the current state of the buffers. If there are multiple matching rules, a stochastic process is applied to select which one to execute. This process is based upon the activation of the rule's root chunk, i.e. the chunk with [=@condition=] and [=@action=]. The higher the activation, the more likely the rule will be selected. See Section [[[#mimicking-human-memory]]].

Executing a rule is a process in which the sequence of actions are applied in the same order that they appear in the rule. Actions are for the most part asynchronous with a few exceptions, see Section [[[#rules]]]. Implementations may index module buffers to speed the process of matching rule conditions to the buffers. One such approach involves a discrimination network akin to Charles Forgy's [[Rete Match Algorithm]].

Need to define requirements for how the rule engine supports reinforcement learning.

Chunks documents

Parsing a chunks document

Chunk documents are parsed one chunk at a time in document order, and asserted to the [=graph of chunks=] for the given module, see Section [[[#scripting-api]]]. If multiple chunk definitions share the same [=identifier=] in a set of chunks, the last definition overrides former definitions.

Chunks grammar

A [=chunks document=] MUST follow the grammar defined below.

Railroad diagrams

This section presents an informative view of the tokens that the grammar defines, in the form of railroad diagrams. These diagrams are provided solely to make it easier to get an intuitive grasp of the syntax of each token.

Mapping to RDF

Linked Data [[LINKED-DATA]], at the basis of RDF [[RDF11-CONCEPTS]], is a way to create a network of standards-based machine interpretable data across different documents and Web sites. It allows an application to start at one piece of Linked Data, and follow embedded links to other pieces of Linked Data that are hosted on different sites across the Web.

[=Names=] used in [=chunks=] are local to the [=graph of chunks=] in which they appear. For [=names=] to be usable as linked data, there needs to be a way to associate them to global identifiers. [=Reserved names=] defined in this section may be used to create such a mapping.

In turn, this mechanism can be used to map a [=graph of chunks=] to an RDF graph and vice versa.

Algorithms to serialize/deserialize a [=graph of chunks=] to an RDF graph and vice versa are out of scope of this document but may be specified in future revisions of it.

The @rdfmap type

The [=@rdfmap=] chunk [=type=] identifies a chunk that defines a mapping between [=names=] and IRIs [[IRI]]. Each [=property=] it defines whose [=name=] does not start with one of the name operators (see [[[#name-operators]]]) creates a mapping between this [=name=] and the property [=value=], interpreted as an IRI.

The [=@rdfmap=] keyword plays the same role in [=chunks=] as the @context keyword in JSON-LD. See the notion of Context in JSON-LD for details [[JSON-LD]]. The term [=context=] and the [=@context=] keyword have a different meaning in [=chunks=] where they identify the specific situation under which a [=chunk=] should be considered to be true. A distinct [=@rdfmap=] keyword is used here to avoid any confusion.

If multiple [=@rdfmap=] chunks create a mapping for the same [=name=], the last definition in overrides previous ones.

An [=@rdfmap=] chunk may also contain a [=@base=] property to create a default IRI namespace for [=names=] and [=@prefix=] properties to define prefixes for compact IRIs.

The @base property

The [=@base=] [=property=] defines a default IRI namespace for [=names=] that are not explicitly declared in an [=@rdfmap=].

There can be only one default IRI namespace for a given [=graph of chunks=]. If [=@base=] is used in multiple [=@rdfmap=] chunks, the last definition overrides previous ones.

The @prefix property

The [=@prefix=] [=property=] can be used in an [=@rdfmap=] chunk to reference a [=chunk=] that defines IRI prefixes, which in turn allow the use of compact IRIs in the [=@rdfmap=] chunk.

The actual [=chunk=] that defines prefixes can have any [=type=]. Each [=property=] it defines whose [=name=] does not start with one of the name operators (see [[[#name-operators]]]) creates a prefix between the property [=name=] and the property [=value=], interpreted as an IRI.

As with [=@base=], in case of conflicts, the definition that gets referenced last overrides former definitions.

Data Models

This section describes potential ways for using [=chunks=] to declare the properties and values expected for a given chunk [=type=]. Here is an example for a chunk type for a bottle:

The first chunk declares that chunks of [=type=] bottle have two properties: level and capped. The following chunks provide metadata for those [=properties=]. You can declare the data type for the property along with its initial value. You can provide the minimum and maximum value for numeric property values. This approach is useful for properties whose meaning and data model is the same regardless of the chunk [=type=] they are used in.

Here is a further example that introduces additional data types, along with the means to describe properties specific to a given chunk [=type=],

The values for properties can either be the name of a property, e.g. position or a chunk ID for a chunk of [=type=] property, that names the property in question and describes its data model.

For properties whose value is either a name or a sequence of names, you can use min and max to specify constraints on the length of the sequence, e.g. min 1; max 1 to require a single value for the associated property.

Validation and Richer Queries

Data models can be used to check that a [=graph of chunks=] conforms to the constraints defined by the data model. In principle, a new [=@do=] action could be defined to invoke such checks. Such a command is not standardised in this specification, but could be readily implemented as an application defined action, see section [[[#scripting-api]]].

The built-in @ actions provide standard ways to query and update chunk graphs. Rich queries could generate chunk graphs as a result of their execution. Such queries can be expressed as a set of chunks and executed using application defined graph algorithms implemented using the [[[#scripting-api]]]. The built-in @ actions can then be used to access the graphs generated by richer queries.

Mimicking Human Memory

Human memory focuses on what is useful based upon past experience. Memories fade over time unless they are used. Chunks support sub-symbolic parameters that mimic characteristics of human memory. The chunk activation is boosted each time the chunk is updated or recalled. It then decays over time like a leaky capacitor replicating the forgetting curve for human memory.

Chunk recall is stochastic and influenced by the chunk's activation. Chunk buffers can only hold a single chunk. If matching chunks have the same activation, they are equally likely to be selected and loaded into the module's chunk buffer, otherwise the stronger the activation, the more likely a matching chunk will be selected. Chunks mimic the spacing effect in that the boost to a given chunk is progressively reduced if it occurs within a short time since the chunk was last boosted.

Chunks further support spreading activation each time a chunk is updated or recalled. This boosts related chunks that are referenced from chunk property values in a wave that weakens as it spreads out. The greater the fan-out from a given chunk, the weaker the boost given to the linked chunks. You can think of this as a fixed amount of energy that is divided across the links and partially absorbed by each chunk it reaches. The wave stops when it falls below a threshold level.

Need to explain how reinforcement learning boosts chunk rules based upon the number of steps needed to accomplish a task, along with how this supports machine learning for rule sets.

Scripting API

This section describes the scripting API exposed by the original JavaScript library for chunks and rules, see [[CHUNKS-DEMOS]]. The starting point is to create a graph from a text string containing the source of the chunks that make up the graph. The following code creates two graphs: one for facts from "facts.chk", and another for rules from "rules.chk".

	let facts, rules;
	fetch("facts.chk")
	.then((response) => response.text())
	.then(function (source) {
		facts = new ChunkGraph(source);
		fetch("rules.chk")
		.then((response) => response.text())
		.then(function (source) {
			rules = new ChunkGraph(source);
		});
	});

Here are some operations you perform on a graph:

new ChunkGraph(source)
Create a new graph from a text string containing the chunks and links.
graph.chunks[id]
Find a chunk given its id.
graph.types[type]
Find the list of chunks with a given type.
graph.forall(kind, handler, context)
Apply a function to all chunks whose type has the kindof relationship to the given kind. This applies recursively to chains of kindof relationships. The handler is a function that is passed the chunk and the context.
graph.get(type, values)
Recall a chunk with a given type, and matching values as denoted by a JavaScript object with a set of named properties. Note that this is stochastic and returns the 'best' chunk when there are multiple matches.
graph.put(type, values, id)
Remember (i.e. create and store) a chunk with a given type, and matching values as denoted by a JavaScript object with a set of named properties. The chunk id will be assigned automatically if not supplied.
graph.delete(type, values, id)
Remove the chunk with a given ID if provided, otherwise forget the set of chunks with the given type and matching values.
graph.parse(source)
Parse source as chunks and add to this graph.
graph.add(chunk)
Adds a chunk or link to the graph, see below for ways to create chunks and links. If the chunk is currently part of another graph, it will be removed from that graph before being added to this one.
graph.remove(chunk)
Remove a chunk or link from the graph.

Here are some operations you perform on a chunk:

new Chunk(type, id)
Create a new chunk for a given type and id. The id is optional and will be assigned automatically when the chunk is added to a graph if not supplied.
new Link(subject, predicate, object)
Create a new Link as a subclass of chunk where the chunk type is given by the predicate. The chunk id will be assigned automatically when the Link is added to a graph.
chunk.id
Access the chunk's id.
chunk.type
Access the chunk's type.
chunk.properties[name]
Access a chunk property value given the property's name.
chunk.setValue(name, value)
Overwrite the value of a named property
chunk.addValue(name, value)
Add a value for named property. An array is used only if the property value has multiple values.
chunk.removeValue(name)
Remove a value from the named property - this is the inverse of addValue.
chunk.hasValue(name, value)
Returns true or false according to whether the named property contains the given value, i.e. the property is either that value or it is a list, one of whose items is that value. If the property is undefined for this chunk, then the return value is false.
chunk.toString()
Returns a pretty printed version of the chunk.

The following describes the API for rule engines:

new RuleEngine()
Create a new rule engine.
engine.addModule(name, graph[, backend])

Registers a new local module with its name, graph and an optional backend for graph algorithms.

The backend is declared as an object whose property values are functions that implement the algorithm identified by the property's name. The algorithm's name can then used with @do in rule actions for this module. The action is passed a single argument that is an object whose property values are the bindings for the variables identified by the object's property names.

The backend functions can be used to override the default actions for recall, remember and update. Note that "rules" and "goals" are required modules. The rules module is used to hold procedural knowledge as a set of rules. By default, the "goals" module is initialised to an empty graph. A separate method is envisaged for adding remote modules.

engine.getModule(name)
Return the module created by calling engine.addModule.
engine.start(rules, facts[, initial_goal])
A convenience function to set the rules and facts modules, along with an optional initial chunk, that is provided as a chunk, i.e. as source text. Note: rules is a graph containing the rules that define procedural knowledge, and facts is a graph containing a set of chunks that define declarative knowledge. The goal is not used until you call engine.next().
engine.next()
Find and execute the next matching rule.
engine.setGoal(source)
Parse the source for a chunk and load it into the goal module's buffer.
engine.setBuffer(name, source)
Parse the source for a chunk and add it to the named module's graph, then load it into the module's buffer.
engine.setBuffer(name, chunk)
Load the chunk into the named module's buffer.
engine.getBuffer(name)
Return the chunk in the named module's buffer.
engine.addListener(listener)
Register a listener function that will be called when any of the module buffers are updated. The listener is passed a single argument identifying the module by name.