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 valuestate.transition.detected— FSM engine found a phase changenarrative.propagation.spike— Narrative engine detected rapid belief spreadtrust.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.