Event Backbone Architecture¶
CONCEPT:AU-KG.query.vendor-agnostic-traversal — Vendor-Agnostic Event Backbone
Overview¶
The event backbone provides protocol-based pub/sub event streaming for
graph mutations, task queues, telemetry, and evolution triggers. It follows
the same pattern as GraphBackend — abstract protocol with an in-memory
default and optional distributed backends.
Architecture¶
graph TD
subgraph Producers
IGE["IntelligenceGraphEngine"]
TM["TaskManager"]
AHE["AHE Harness"]
end
subgraph EventBackend["Event Backbone (Protocol)"]
MEB["MemoryEventBackend<br/>(in-process, zero-dep)"]
KEB["RedpandaEventBackend<br/>(distributed, persistent, confluent-kafka)"]
end
subgraph Topics
T1["kg.mutations"]
T2["kg.tasks"]
T3["kg.staging"]
T4["kg.telemetry"]
T5["kg.evolution"]
end
subgraph Consumers
OBS["Post-commit mutation observers"]
TELE["Telemetry Pipeline"]
EVOL["Evolution Engine"]
end
IGE -->|publish| T1
TM -->|publish| T2
AHE -->|publish| T5
T1 -->|subscribe| OBS
T4 -->|subscribe| TELE
T5 -->|subscribe| EVOL
style MEB fill:#d5e8d4,stroke:#82b366
style KEB fill:#dae8fe,stroke:#6c8ebf
Topic Taxonomy¶
| Topic | Purpose | Retention | Cleanup Policy |
|---|---|---|---|
kg.mutations |
Graph CRUD events (add/update/remove node/edge) | 7 days | compact + remove |
kg.tasks |
WorkItem notification and completion events | 3 days | remove |
kg.staging |
Staged graph payloads awaiting write | 1 day | remove |
kg.telemetry |
Agent traces, latency, error rates | 1 day | remove |
kg.evolution |
Self-improvement triggers, AHE cycle events | 7 days | compact + remove |
Event Schema¶
All events are JSON-serialized with the following base structure:
{
"action": "add_node",
"data": {
"id": "agent-001",
"properties": {"type": "Agent", "name": "Research Agent"}
},
"timestamp": 1716912000.0,
"source": "IntelligenceGraphEngine"
}
Mutation Actions¶
| Action | Data Fields | Description |
|---|---|---|
add_node |
id, properties |
Add or update a node |
add_edge |
source, target, properties |
Add or update an edge |
remove_node |
id |
Remove a node and its edges |
remove_edge |
source, target |
Remove a specific edge |
Backend Selection¶
from agent_utilities.knowledge_graph.core.event_backend import create_event_backend
# In-memory (default, zero config, single-process)
backend = create_event_backend("memory")
# Redpanda (production, multi-process)
backend = create_event_backend("redpanda", bootstrap_servers="redpanda:9092")
Environment Variables¶
| Variable | Default | Description |
|---|---|---|
EVENT_BACKEND |
redpanda |
Backend type (memory, redpanda) — falls back to memory unless KAFKA_ENABLED=true |
KAFKA_ENABLED |
false |
Gate that enables the distributed backend; otherwise MemoryEventBackend is used |
REDPANDA_BROKERS |
localhost:9092 |
Broker addresses (Kafka/Redpanda) |
REDPANDA_CONSUMER_GROUP |
agent-utilities |
Default consumer group |
REDPANDA_SECURITY_PROTOCOL |
PLAINTEXT |
Security protocol |
Authority and mirror consistency¶
The Rust engine is the sole graph authority. Writes commit as governed
ChangeEnvelope operations; optional mirrors receive the same acknowledged
mutations through the durable per-mirror FanOutBackend outbox. Pub/sub events
are post-commit notifications and never form an alternate replication path.
sequenceDiagram
participant ENG as Engine authority (Rust)
participant FOB as FanOutBackend
participant OB as Durable mirror outbox
participant MIR as Mirror (persistent backend)
FOB->>ENG: ApplyChangeEnvelope
ENG-->>FOB: durable acknowledgement
FOB->>OB: append mutation per mirror
OB->>MIR: ordered replay
MIR-->>OB: advance durable cursor
Note over FOB,MIR: Periodic reconcile repairs drift from authority
Failure Modes¶
| Failure Mode | Mitigation |
|---|---|
| Redpanda unavailable | Auto-fallback to MemoryEventBackend for notifications |
| Mirror unavailable | Durable outbox retains its unapplied ordered tail |
| Authority↔mirror drift | Explicit authority-to-mirror reconciliation repairs drift |
| Duplicate delivery | Governed mutation identity and mirror cursors keep replay idempotent |
Docker Deployment¶
# Start Kafka (KRaft mode, no Zookeeper) — also runs Redpanda-compatible brokers
docker compose -f docker/kafka-kraft.compose.yml up -d
# Verify topics
docker exec agent-utilities-kafka \
/opt/kafka/bin/kafka-topics.sh --list --bootstrap-server localhost:9092
Dependencies¶
Ingest Task Queue Scale-Out (CONCEPT:AU-KG.backend.selectable-queue-backend / KG-2.56 / AU-KG.ingest.decoupled-kg-ingest-consumer)¶
Separate from the pub/sub event backbone above, the durable ingest task
queue (the queue submit_task writes and the ingest workers drain) is
selectable, fail-loud, and — with Kafka — horizontally scalable.
Three queue modes¶
| Mode | Selected by | Scope | Workers |
|---|---|---|---|
sqlite |
default (nothing set) | one host (per-host kg_task_queue.db) |
in-process daemon threads on the flock host |
postgres |
auto when STATE_DB_URI is set, or TASK_QUEUE_BACKEND=postgres |
fleet (one shared queue, FOR UPDATE SKIP LOCKED claims — AU-KG.ingest.cross-host-safe-kg) |
in-process threads on every participating host |
kafka |
TASK_QUEUE_BACKEND=kafka |
fleet (keyed kg_tasks topic) |
the kg-ingest consumer group: the host engine's pool plus any number of decoupled kg-ingest-worker processes |
Selection contract (KG-2.55):
TASK_QUEUE_BACKENDunset → auto:postgreswhenSTATE_DB_URIis set, elsesqlite. Auto stays graceful — an unreachable Postgres degrades to the per-host SQLite queue with a warning.TASK_QUEUE_BACKEND=kafka|postgresset explicitly → fail-loud: an unreachable broker/state store raisesTaskQueueUnavailableat startup with the endpoint and fall-back instructions. Never a silent degrade.
Partition-key hierarchy (KG-2.56)¶
Producers key every kg_tasks message; Kafka guarantees ordering per key
without serializing unrelated work. Ingestion producers derive an opaque
partition_ref from the privacy-normalized target and tenant; the notification
does not contain the target definition. For generic queue envelopes, first
match wins:
tenant:<id>— ambientActorContext.tenant_id(multi-tenant ordering);work:<opaque-ref>— durable ingestion partition reference;corpus:<repo>— portable corpus identifiers on non-ingestion envelopes;type:<task_type>— coarsest bucket for everything else.
kg_tasks is ensured idempotently at startup with KG_TASKS_PARTITIONS
partitions (default 6). Grow-only: raising the flag adds partitions; an
existing topic is never shrunk. Partition count bounds the consumer group's
maximum parallelism.
Ordering & idempotency guarantees (AU-KG.ingest.decoupled-kg-ingest-consumer)¶
- At-least-once delivery. Offsets are committed only after a task completes (or is durably marked failed); worker crashes redeliver.
- Idempotent claims.
job_idaddresses a deterministic ingestionWorkItem. Every consumer must win nativeClaimWorkItem; lease epoch and fencing token reject duplicate or stale execution. There is no graph-field claim fallback or advisory-lock authority. - Per-key ordering only. There is no global order and no cross-partition
priority lane (the graph-polling modes'
priority=highfast path does not apply in Kafka mode). - Lease recovery. Uncommitted offsets redeliver automatically; native
ClaimWorkItematomically reclaims expired leases when the item is eligible.
Worker deployment shape¶
# Host engine (unchanged): in Kafka mode its worker pool simply joins kg-ingest.
TASK_QUEUE_BACKEND=kafka KAFKA_BOOTSTRAP_SERVERS=kafka:9092 graph-os-daemon
# Scale out: N decoupled workers, any host, NO KG host role required.
# Each is an engine *client* (existing Rust daemon over TLS + OS-5.14 HMAC secret).
GRAPH_SERVICE_ENDPOINTS=tls://engine.example.test:9100 \
ENGINE_TLS_PROFILE_REF=vault://platform/engine/tls-profile \
GRAPH_SERVICE_AUTH_SECRET="${GRAPH_SERVICE_AUTH_SECRET_FROM_SUPERVISOR}" \
KAFKA_BOOTSTRAP_SERVERS=kafka:9092 \
kg-ingest-worker # or: python -m agent_utilities.knowledge_graph.ingest_worker
Per-host concurrency autosizes with the shared CPU/memory sizer
(compute_ingest_worker_count: ~36% of cores, ~3 GB RAM per worker, floor 2);
override with --workers / KG_INGESTION_WORKERS.
Backpressure & lag visibility¶
The leader host's maintenance scheduler samples the queue every pass and publishes to the AU-OS.observability.no-op-without-metrics gateway Prometheus registry:
agent_utilities_kg_ingest_queue_depth{backend}— pending tasks in the selected backend (uniform across sqlite/postgres/kafka);agent_utilities_kg_ingest_consumer_lag{topic,group}— totalkg-ingestgroup lag onkg_tasks(Kafka mode).
The batch orchestrator's deferral and the maintenance bulk-defer gate read the
same uniform number via engine.ingest_queue_depth(), which counts each
non-terminal ingestion WorkItem exactly once, so backpressure does not double
count a Kafka notification and its authoritative work record.