The Event Bus Architecture: Why VANI Services Never Call Each Other Directly
VANI prohibits direct service-to-service calls — all inter-service communication must go through the Cognitive Interconnect (Event Bus), with publishers unaware of subscribers and subscribers unaware of publishers.
- Reference
- V017
- Project
- VANI
- Status
- Implemented — Phase 0 (Event System)
- Evidence
- high
- Source
- 017-event-bus-architecture.md
Context
The Event Bus (called the "Cognitive Interconnect") was one of the 13 Phase 0 subsystems. ADR-007 formalized its use as the mandatory integration mechanism for Phase 1 services. The rejection of direct service calls is documented in both the event bus architecture spec and ADR-007.
The Decision
From docs/architecture/EVENT_BUS.md:
"Publishers never communicate directly with subscribers. Subscribers never know the publisher."
From ADR-007:
"Services must not invoke another service directly, depend on another service's implementation, or create private event buses."
From Phase 1 Architecture (docs/phases/phase1/ARCHITECTURE.md):
"Direct service-to-service communication is prohibited."
What the Event Bus Provides
The Cognitive Interconnect consists of six internal components:
- Event Models — Immutable event structures with required fields (Event ID, Event Type, Version, Timestamp, Producer, Payload, Metadata)
- Subscription Registry — Tracks which services subscribe to which event types
- Event Router — Determines which subscribers receive each event (routing policy, topic matching, priority resolution)
- Event Dispatcher — Delivers events to subscribers (retry policy, delivery acknowledgements, dead letter queue forwarding)
- Middleware Pipeline — Validation, schema checking, producer identity verification
- Dead Letter Queue — Handles events that cannot be delivered
The Event Lifecycle
Event Created
↓
Published
↓
Event Envelope Created
↓
Middleware Pipeline (validation, schema check)
↓
Validated
↓
Queued
↓
Routed (Event Router determines subscribers)
↓
Dispatched (Event Dispatcher delivers)
↓
Delivered
↓
Processed
↓
CompletedIf delivery fails: Retry Policy → Dead Letter Queue → Diagnostic Event.
The separation of Router and Dispatcher is explicit:
"Routing never performs delivery. Delivery never performs routing. Services never implement routing logic."
Event Priorities
Events define priorities: Critical → High → Normal → Low → Background.
Why Not Direct Calls?
From ADR-007, the rejected alternatives:
Rejected: Direct service method calls
- "Introduces tight coupling between services. Services cannot be replaced, reordered, or modified without cascading changes to other services."
- "Violates FOUNDATION.md Architectural Law 3: Domain Ownership."
Rejected: Shared global state
- "Violates FOUNDATION.md Architectural Law 6: No Global Mutable State."
- "Makes concurrent access unpredictable."
Evidence
docs/architecture/EVENT_BUS.md— Event Bus architecture (420 lines, 6,980 bytes)docs/adr/ADR-007-event-capability.md— Event-driven integration decisiondocs/phases/phase1/ARCHITECTURE.md— "Direct service-to-service communication is prohibited" (line 267)src/runtime/events/— Event System source directory- Phase 0 Event System listed in IMPLEMENTATION_REPORT.md as completed
Diagnosis
Established: The Event Bus is implemented (Phase 0 subsystem). Direct service calls are prohibited in Phase 1 architecture and ADR-007. The six-component internal structure is documented in the architecture spec.
Established: The Dead Letter Queue exists as an explicit failure handling mechanism — events that cannot be delivered do not silently disappear.
Established: Event priority levels are defined: Critical, High, Normal, Low, Background.
Observation: The requirement for immutable event payloads ("immutable events" in Event Bus design principles) aligns with the broader FOUNDATION.md Law 4: "Immutable Shared State." Events are snapshots of what happened, not mutable objects.
Established: The event structure requires a Producer field — every event's source is tracked. This supports observability and debugging.
What I Learned
The Dead Letter Queue is architecturally significant: it means events cannot silently fail. In a system with no direct calls (everything is async event delivery), silent failures would be catastrophic — you'd have no idea that a service never received a critical event. The DLQ ensures failures are observable and recoverable.
The separation of Router (which subscribers receive the event?) from Dispatcher (deliver the event to them) is a good example of single-responsibility applied at the infrastructure level. Most event bus implementations mix these, which makes routing policy changes require modifying the delivery code.
Broader Principle
Event-driven communication is not appropriate for every system. For VANI's continuous runtime OS model (many subsystems running simultaneously, autonomous cognitive cycles, no request-response), it is appropriate because:
- Cognitive subsystems should not know about each other's implementation
- Autonomous operations (scheduler jobs, consolidation tasks) don't have a "caller" to return to
- The same event can trigger actions in multiple subsystems simultaneously
For a system that is primarily request-response (web API, CLI tool), event-driven communication adds complexity without commensurate benefit.
Technical References
docs/architecture/EVENT_BUS.md— Full event bus architecturedocs/adr/ADR-007-event-capability.md— Formal ADRdocs/phases/phase1/ARCHITECTURE.md— Section 8 (Communication Model)src/runtime/events/— Event System source
Failure sequence
On the record- Attempt
- Failure
- Diagnosis
- **Established:** The Event Bus is implemented (Phase 0 subsystem). Direct service calls are prohibited in Phase 1 architecture and ADR-007. The six-component internal structure is documented in the architecture spec. **Established:** The Dead Letter Queue exists as an explicit failure handling mechanism — events that cannot be delivered do not silently disappear. **Established:** Event priority levels are defined: Critical, High, Normal, Low, Background. **Observation:** The requirement for immutable event payloads ("immutable events" in Event Bus design principles) aligns with the broader FOUNDATION.md Law 4: "Immutable Shared State." Events are snapshots of what happened, not mutable objects. **Established:** The event structure requires a Producer field — every event's source is tracked. This supports observability and debugging.
- Lesson
- The Dead Letter Queue is architecturally significant: it means events cannot silently fail. In a system with no direct calls (everything is async event delivery), silent failures would be catastrophic — you'd have no idea that a service never received a critical event. The DLQ ensures failures are observable and recoverable. The separation of Router (which subscribers receive the event?) from Dispatcher (deliver the event to them) is a good example of single-responsibility applied at the infrastructure level. Most event bus implementations mix these, which makes routing policy changes require modifying the delivery code.
- Next iteration