Files

130 lines
4.0 KiB
Python
Raw Permalink Normal View History

"""Cross-head insight bus — shared learning channel between heads.
Heads can publish observations (insights) to the bus, and other heads
can subscribe to learn from them. This enables the Safety head to
learn from Logic's contradiction detections, Research's source quality
assessments, and so on breaking the head-isolation barrier.
Usage:
bus = InsightBus()
bus.publish("logic", Insight(source="logic", message="Contradiction found", ...))
recent = bus.get_insights(subscriber="safety", limit=10)
"""
from __future__ import annotations
import time
from dataclasses import dataclass, field
from typing import Any
from fusionagi._logger import logger
@dataclass
class Insight:
"""A single observation published by a head."""
source: str
message: str
domain: str = ""
confidence: float = 0.5
metadata: dict[str, Any] = field(default_factory=dict)
timestamp: float = field(default_factory=time.monotonic)
class InsightBus:
"""Shared bus for cross-head learning.
Heads publish observations; other heads consume them to enrich
their own reasoning. The bus maintains a rolling window of
insights and supports filtered retrieval.
Args:
max_insights: Maximum insights retained (oldest dropped first).
"""
def __init__(self, max_insights: int = 1000) -> None:
self._insights: list[Insight] = []
self._max = max_insights
self._subscribers: dict[str, list[str]] = {}
def publish(self, publisher: str, insight: Insight) -> None:
"""Publish an insight from a head.
Args:
publisher: Head ID of the publisher.
insight: The observation to share.
"""
self._insights.append(insight)
if len(self._insights) > self._max:
self._insights = self._insights[-self._max:]
logger.debug(
"InsightBus: insight published",
extra={
"publisher": publisher,
"domain": insight.domain,
"message": insight.message[:80],
},
)
def subscribe(self, subscriber: str, domains: list[str] | None = None) -> None:
"""Register a head's interest in certain domains.
Args:
subscriber: Head ID subscribing.
domains: Domains of interest (None = all).
"""
self._subscribers[subscriber] = domains or []
def get_insights(
self,
subscriber: str | None = None,
domain: str | None = None,
limit: int = 20,
since: float | None = None,
) -> list[Insight]:
"""Retrieve recent insights, optionally filtered.
Args:
subscriber: If given, filter by subscriber's registered domains.
domain: Explicit domain filter.
limit: Max results.
since: Only insights after this timestamp.
Returns:
List of matching insights, most recent first.
"""
results = self._insights
if since is not None:
results = [i for i in results if i.timestamp >= since]
if domain:
results = [i for i in results if i.domain == domain]
elif subscriber and subscriber in self._subscribers:
domains = self._subscribers[subscriber]
if domains:
results = [i for i in results if i.domain in domains]
return list(reversed(results[-limit:]))
def get_summary(self) -> dict[str, Any]:
"""Return bus statistics."""
by_source: dict[str, int] = {}
by_domain: dict[str, int] = {}
for i in self._insights:
by_source[i.source] = by_source.get(i.source, 0) + 1
if i.domain:
by_domain[i.domain] = by_domain.get(i.domain, 0) + 1
return {
"total_insights": len(self._insights),
"subscribers": list(self._subscribers.keys()),
"by_source": by_source,
"by_domain": by_domain,
}
__all__ = ["Insight", "InsightBus"]