Series 1 — Part 6 of 8

Engines in the behavioral AI platform do not call each other. They publish events and subscribe to topics through an in-process event bus. This article explains the pattern, shows the implementation, and covers why it prevents circular dependencies.

Why Engines Must Not Call Each Other

If the causal-graph engine imports and calls the bayesian-confidence engine directly, you have a compile-time dependency that cannot be changed at runtime. The registry cannot activate or deactivate it. The control plane cannot reorder it. And the moment confidence wants to call causal-graph back, you have a circular import that crashes the process.

Events decouple this entirely. bayesian-confidence emits confidence.updated. causal-graph subscribes to it. Neither knows the other exists.

The Event Bus Implementation

// core/event-bus.js
const EventEmitter = require('events');

class BehavioralEventBus extends EventEmitter {
  constructor() {
    super();
    this.setMaxListeners(50); // 34 engines + control plane + governance
    this._log = [];
  }

  emit(topic, payload) {
    this._log.push({ topic, payload, ts: Date.now() });
    return super.emit(topic, payload);
  }

  getLog() { return [...this._log]; }
  clearLog() { this._log = []; }
}

module.exports = new BehavioralEventBus(); // singleton

Topic Naming Convention

Topics follow a domain.entity.event pattern:

  • confidence.updated — Bayesian engine published a new confidence value
  • state.transition.detected — FSM engine found a phase change
  • narrative.propagation.spike — Narrative engine detected rapid belief spread
  • trust.deficit.flagged — Trust graph engine found a significant deficit

Async Events and Engine Ordering

Because the control plane runs engines in dependency-sorted batches, subscribed events from a prior batch are always available to engines in the current batch. The event log is passed into each engine's score() call as context.events, so engines can read prior-batch outputs even if they did not directly subscribe.