HunterX v7 Event Bus & Observability — Architecture & Reference

Status: Ratified (Sprint 006.85) Version: 1.0.0 Owner: HunterX Architecture Council


1. Purpose / Scope

This sprint builds the event-driven communication backbone and the observability platform every HunterX v7 subsystem uses. It delivers:

  1. Event Bus — publish/subscribe with routing, filtering, priorities, persistence, dead-letter queue, replay and event versioning.
  2. Event Catalog — canonical event types across 13 subsystems.
  3. Logging — structured JSON with correlation ids, context propagation, enrichment, sensitive-data masking and rotation.
  4. Metrics — counters, gauges and histograms covering the 12 canonical platform metrics.
  5. Tracing — distributed span hierarchy with context propagation.
  6. Telemetry — Memory, OpenTelemetry and Prometheus-compatible providers.
  7. Audit — typed audit events for the 7 audit kinds.
  8. Health — unified probes for the 10 platform components.
  9. Observability API — one internal service for events, metrics, tracing, health and telemetry.
  10. Dashboard data models — storage-agnostic shapes for future dashboards.

Hard constraints:

Scope: src/hunterx/domain/events/, src/hunterx/domain/ports/observability.py, src/hunterx/domain/entities/dashboard.py, src/hunterx/application/observability.py, src/hunterx/infrastructure/event_bus/, .../metrics/, .../tracing/, .../health/, .../logging/, .../telemetry/providers.py, src/hunterx/observability.py and the platform wiring in src/hunterx/platform/.

Out of scope: the Knowledge Graph, object store, cache and queue; specific dashboards UI; OTLP/prometheus wire implementations that require third-party dependencies.


2. Design Goals

  1. Every subsystem communicates through events. Engines, tools, plugins and schedulers publish and consume canonical events; they never call each other directly for cross-cutting concerns.
  2. One observability API. The ObservabilityService is the single entry point for publishing, subscribing, metrics, tracing, health and telemetry.
  3. Replayable history. Every published event is persisted and can be replayed, optionally filtered by type, so state can be reconstructed.
  4. Failures are quarantined, not silent. A failing subscriber isolates its error, other subscribers still run, and the event goes to the dead-letter queue for inspection and requeue.
  5. Versioned payloads. The catalog records the current payload version of every event type so producers and consumers can assert compatibility.

3. Architecture

flowchart LR
    subgraph Producers
        ME[Mission Engine]
        TE[Tool SDK]
        PM[Plugin Manager]
        KE[Knowledge Engine]
        AE[AI Engine]
    end
    subgraph Observability
        API[ObservabilityService]
        BUS[Event Bus]
        REG[Event Registry]
        STORE[Event Store + DLQ]
    end
    subgraph Consumers
        REPORT[Reporting]
        AUDIT[Audit Trail]
        SYS[System / Health]
    end

    ME --> API
    TE --> API
    PM --> API
    KE --> API
    AE --> API
    API --> BUS
    BUS --> REG
    BUS --> STORE
    BUS --> REPORT
    BUS --> AUDIT
    BUS --> SYS

    API --> MET[Metrics]
    API --> TRC[Tracing]
    API --> HLT[Health Registry]
    API --> TEL[Telemetry Provider]

4. Event Model

4.1 Envelope (domain/events/)

Every event is a frozen DomainEvent carrying the full metadata envelope:

Field Type Purpose
event_id str ULID, auto-generated
event_type str stable machine name, e.g. "mission.started"
occurred_at str UTC ISO-8601 timestamp
source / producer str producing component
payload dict JSON-serializable data
correlation_id str | None logical workflow correlation
causation_id str | None event that caused this one
mission_id str | None scoping mission
execution_id str | None scoping execution
consumer str | None target consumer
severity EventSeverity debug/info/notice/warning/error/critical
category EventCategory one of the 13 categories
payload_version int payload schema version

to_dict() serializes the full envelope; legacy construction (DomainEvent(event_type=..., payload=...)) remains unchanged.

4.2 Categories (EventCategory)

mission, execution, tool, plugin, database, knowledge, ai, workflow, security, reporting, system, configuration, user.

4.3 Priorities (EventPriority)

LOW (0) < NORMAL (1) < HIGH (2) < CRITICAL (3). Higher-priority subscribers are invoked first; equal priorities preserve subscription order.


5. Event Catalog & Registry

domain/events/spec.py defines EventSpec (event type, category, default severity, payload version, description, version history) and EventRegistry (register, get, require, list by category, unknown-type detection).

domain/events/catalog.py builds the canonical catalog of 52 event types across all 13 categories via build_registry(). Event types follow the <category>.<action> convention; consumers subscribe exactly ("mission.started") or by category prefix ("mission.*").

Event versioning is registry-driven: each spec records its current payload_version; EventSpec.supports(version) asserts whether a given payload version is compatible.


6. Event Bus

6.1 Port (domain/ports/observability.py)

ObservabilityEventBusPort extends the legacy messaging EventBusPort with:

Supporting ports: EventStorePort (append/mark/get/list/count/replay), DeadLetterQueuePort (push/list/count/requeue).

6.2 Implementation (infrastructure/event_bus/)

InMemoryEventBus is the shipped adapter. Behaviors:

6.3 Event Lifecycle

sequenceDiagram
    participant P as Producer
    participant B as Event Bus
    participant S as Subscribers
    participant ST as Event Store
    participant D as Dead Letter Queue

    P->>B: publish(event)
    B->>ST: append(event) [received]
    B->>S: dispatch (priority order, filters applied)
    alt handler succeeds
        S-->>B: ok
    else handler fails
        S-->>B: error (isolated)
        B->>D: push(event, error)
    end
    B->>ST: mark(event, delivered)

7. Logging Framework

infrastructure/logging/__init__.py:


8. Metrics

infrastructure/metrics/__init__.py provides InMemoryMetrics with three kinds — counters, gauges and histograms — plus tag dimensions and a Prometheus-compatible text renderer.

The metrics reference (12 canonical metrics):

Metric Kind Notes
execution_duration_seconds histogram tool execution duration
tool_runtime_seconds histogram per-tool runtime
queue_size gauge pending jobs
mission_duration_seconds histogram mission wall time
error_rate gauge share of errored operations
success_rate gauge share of successful operations
failure_rate gauge share of failed operations
retry_count counter retried executions
memory_usage_bytes gauge process memory
cpu_usage_percent gauge process CPU
database_latency_seconds histogram DB round-trip latency
cache_hit_ratio gauge cache effectiveness

render_prometheus() normalizes names for the exposition format (hunterx_<name>) so the same collector can be scraped.


9. Tracing

infrastructure/tracing/__init__.py provides InMemoryTracer:

Spans form a hierarchy via parent_span_id; a thread-local context propagates the trace/span id across components on the same thread, enabling execution timelines and mission timelines by filtering a trace’s attributes.


10. Telemetry Providers

infrastructure/telemetry/providers.py provides three providers behind TelemetryProviderPort:

Provider Export
MemoryTelemetryProvider default; Prometheus text of current metrics
PrometheusTelemetryProvider Prometheus text exposition
OpenTelemetryTelemetryProvider OTLP when opentelemetry extras are installed; degrades to in-memory otherwise

build_provider(kind) selects by name (memory | prometheus | otel / opentelemetry); unknown kinds fall back to memory.

The pre-existing MemoryTelemetry (TelemetryPort) is preserved unchanged for backward compatibility.


11. Audit Events

domain/events/audit.py provides AuditEventFactory with the seven audit kinds: authentication, authorization, configuration changes, mission lifecycle, tool execution, plugin lifecycle and database changes. Audit events ride the same bus as every other event and are therefore persisted by the event store.


12. Health Monitoring

infrastructure/health/__init__.py provides HealthProbe, HealthRegistry.register_callable and check_all() (returns {component: {status, detail}}) plus a dashboard-friendly summary().

The platform registers ten canonical probes: core_engine, mission_engine, database, tool_sdk, plugin_manager, knowledge_engine, ai_engine, cache, queue, scheduler. Probes degrade gracefully: a probe exception reports status down rather than raising.


13. Observability API

application/observability.py exposes ObservabilityService — the single internal API:

src/hunterx/observability.py is the facade re-exporting the public surface for subsystems.


14. Dashboard Data Models

domain/entities/dashboard.py defines storage-agnostic shapes — DashboardQuery, MetricSeries, DashboardPanel, DashboardModel — and four reference definitions (overview, missions, health, events) via dashboard_definitions(). Future dashboards hydrate these models at runtime.


15. Platform Wiring

src/hunterx/platform/ wires the observability stack:

The pre-existing event_bus (EventBusPort) still resolves so all legacy wiring and tests remain valid.


16. Developer Guide

Publishing an event:

from hunterx.domain.events import DomainEvent
from hunterx.domain.events.enums import EventCategory, EventSeverity

event = DomainEvent(
    event_type="mission.started",
    payload={"mission_id": "m1"},
    category=EventCategory.MISSION,
    severity=EventSeverity.INFO,
)
platform.observability.publish(event)

Subscribing with a filter and priority:

from hunterx.domain.events.enums import EventPriority

platform.observability.subscribe(
    "mission.*",
    lambda e: handle(e),
    priority=EventPriority.HIGH,
    filter=lambda e: e.payload.get("mission_id") == "m1",
)

Recording a metric:

platform.observability.duration("database_latency_seconds", 0.012, tags={"op": "select"})

Tracing an operation:

platform.observability.start_span("mission.run", attributes={"mission_id": "m1"})
try:
    ...
finally:
    platform.observability.end_span()

Checking health and exporting telemetry:

report = platform.observability.check_health()
text = platform.observability.export_telemetry()  # Prometheus text

Auditing an event:

from hunterx.domain.events.audit import AuditEventFactory
platform.observability.publish(AuditEventFactory.authentication("alice", succeeded=True))

Replaying a category:

for event in platform.observability.replay(event_type="mission.*"):
    print(event.event_type, event.payload)

17. Verification


18. References