CoCo

Combined deduCtiOn and abduCtiOn Reasoner

CoCo is a dual-process inspired cognitive architecture built in Rust. It integrates a rule-based expert system and a timeline-based planner to invoke deductive and abductive reasoning in dynamic environments.


Features

  • Dual-process Reasoning: Combines a forward-chaining deductive engine with abductive inference to explain observed evidence.
  • Dynamic Knowledge Base: Classes and objects evolve at runtime. Properties update continuously and a full time-series history is retained.
  • CLIPS Expert System: Leverages the battle-tested CLIPS rule engine via seamless Rust bindings for expressive, pattern-based rule evaluation.
  • Real-time Notifications: Clients subscribe to reasoner events over WebSocket and receive inferences as they fire.

The knowledge base is organized around classes and objects. Classes define the structure of things the reasoner knows about. They carry static properties (fixed at creation time) and dynamic properties (updated as the environment evolves), and can inherit from other classes to form a hierarchy. Objects are instances of one or more classes; their properties can be updated at any time, and a full history of their values is recorded as time-series data that can be queried over any time range.

Reasoning is driven by rules. Each rule encodes a piece of domain knowledge: given certain conditions on the current state of objects and their data, the reasoner fires the appropriate conclusions, either deducing new facts or hypothesising explanations for observed evidence. Rules can be added dynamically, allowing the knowledge base to grow and adapt without restarting the system.

Clients can interact with CoCo either through its REST API or by opening a WebSocket connection to receive real-time notifications whenever the reasoner produces new inferences. The full API reference is available in the API tab.

Rules

Rules are the core of CoCo's reasoning engine. They are written in the CLIPS language as defrule constructs and submitted to the knowledge base at any time, without restarting the system.

How facts are structured

Every class in the knowledge base is mapped to a CLIPS deftemplate with an id slot. Each property of that class gets its own companion deftemplate named ClassName_propertyName.

  • Static properties have an id and a value slot. Their fact is asserted once at object creation and updated in-place when PATCH /objects/{id} is called.
  • Dynamic properties add a time slot (Unix timestamp) alongside id and value. Every call to POST /objects/{id}/data updates the live fact and appends a new entry to the persistent time-series history.

So for a class Sensor with a static property location (string) and a dynamic property temperature (float), the CLIPS working memory contains:

(Sensor             (id sensor1))
(Sensor_location    (id sensor1) (value "room-a"))
(Sensor_temperature (id sensor1) (value 22.5) (time 1712345678))
Writing rules

A rule matches on one or more fact patterns in the left-hand side (LHS) and executes actions in the right-hand side (RHS) when all conditions are satisfied. CoCo provides a built-in function add-data that rules can call to push new dynamic values back into the knowledge base, triggering further inference chains.

Examples

Fire whenever any object of class Person exists:

(defrule greet
  (Person (id ?id))
  =>
  (printout t "Hello " ?id "!" crlf))

Trigger an alert and record a sentinel value when a thermometer reading exceeds 35 °C:

(defrule temperature-alert
  (ThermometerMonitor_temperature (id ?id) (value ?temp&:(> ?temp 35)))
  =>
  (add-data ?id (create$ temperature) (create$ 99.9)))

Cross-class rule — correlate two objects and assert a derived fact when conditions on both match:

(defrule cross-check
  (RoomSensor_humidity (id ?room) (value ?h&:(> ?h 80)))
  (HVAC (id ?hvac))
  (HVAC_zone (id ?hvac) (value ?room))
  =>
  (assert (high-humidity-alert (room ?room) (hvac ?hvac))))

Rules are submitted as plain JSON to POST /rules, with a name and a content field containing the defrule string. Rules can be inspected individually via GET /rules/{name}.