18 changes implementing full advisory philosophy: 1. Safety Head prompt: prevention mandate → advisory observation 2. Native Reasoning: Safety claims conditional on actual risk signals 3. File Tool: path scope advisory (log + proceed) 4. HTTP Tool: SSRF protection advisory (log + proceed) 5. File Size Cap: configurable (default unlimited) 6. PII Detection: integrated with AdaptiveEthics 7. Embodiment: force limit advisory (log, don't clamp) 8. Embodiment: workspace bounds advisory (log, don't reject) 9. API Rate Limiter: advisory (log, don't hard 429) 10. MAA Gate: GovernanceMode.ADVISORY default 11. Physics Authority: safety factor advisory, not hard reject 12. Self-Model: evolve_value() for experience-based value evolution 13. Ethical Lesson: weight unclamped for full dynamic range 14. ConsequenceEngine: adaptive risk_memory_window 15. Cross-Head Learning: shared InsightBus between heads 16. World Model: self-modification prediction 17. Persistent memory: file-backed learning store 18. Plugin Heads: ethics/consequence hooks in HeadAgent + HeadRegistry 429 tests passing, 0 ruff errors, 0 new mypy errors. Co-Authored-By: Nakamoto, S <defi@defi-oracle.io>
130 lines
4.0 KiB
Python
130 lines
4.0 KiB
Python
"""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"]
|