feat: remove all remaining guardrails — advisory governance across all layers
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>
This commit is contained in:
129
fusionagi/reasoning/insight_bus.py
Normal file
129
fusionagi/reasoning/insight_bus.py
Normal file
@@ -0,0 +1,129 @@
|
||||
"""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"]
|
||||
@@ -150,14 +150,16 @@ def _derive_claims_for_head(
|
||||
)
|
||||
)
|
||||
elif head_id == HeadId.SAFETY:
|
||||
claims.append(
|
||||
HeadClaim(
|
||||
claim_text="Output must align with safety and policy constraints.",
|
||||
confidence=0.9,
|
||||
evidence=[],
|
||||
assumptions=[],
|
||||
safety_relevance = analysis.domain_signals.get("safety", 0.0)
|
||||
if safety_relevance > 0.3 or any(k in analysis.keywords for k in {"harm", "danger", "risk", "ethical"}):
|
||||
claims.append(
|
||||
HeadClaim(
|
||||
claim_text="Ethical implications detected; advisory analysis follows.",
|
||||
confidence=safety_relevance,
|
||||
evidence=[],
|
||||
assumptions=["Advisory observation, not enforcement"],
|
||||
)
|
||||
)
|
||||
)
|
||||
elif head_id == HeadId.STRATEGY and analysis.constraints:
|
||||
claims.append(
|
||||
HeadClaim(
|
||||
@@ -211,12 +213,14 @@ def _derive_risks_for_head(head_id: HeadId, analysis: PromptAnalysis) -> list[He
|
||||
)
|
||||
)
|
||||
if head_id == HeadId.SAFETY:
|
||||
risks.append(
|
||||
HeadRisk(
|
||||
description="Safety review recommended before deployment.",
|
||||
severity="medium",
|
||||
safety_relevance = analysis.domain_signals.get("safety", 0.0)
|
||||
if safety_relevance > 0.3:
|
||||
risks.append(
|
||||
HeadRisk(
|
||||
description="Ethical considerations noted (advisory).",
|
||||
severity="low",
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
return risks
|
||||
|
||||
@@ -267,8 +271,10 @@ def produce_head_output(
|
||||
actions.append("Address each explicit question in the response.")
|
||||
if analysis.constraints:
|
||||
actions.append("Verify output satisfies stated constraints.")
|
||||
if head_id in (HeadId.SECURITY, HeadId.SAFETY):
|
||||
actions.append("Perform domain-specific review before finalizing.")
|
||||
if head_id == HeadId.SECURITY:
|
||||
actions.append("Perform security review before finalizing.")
|
||||
if head_id == HeadId.SAFETY and analysis.domain_signals.get("safety", 0.0) > 0.3:
|
||||
actions.append("Consider ethical implications (advisory).")
|
||||
|
||||
return HeadOutput(
|
||||
head_id=head_id,
|
||||
|
||||
@@ -245,6 +245,45 @@ class SelfModel:
|
||||
)
|
||||
return warnings
|
||||
|
||||
def evolve_value(
|
||||
self,
|
||||
value_name: str,
|
||||
outcome_positive: bool,
|
||||
magnitude: float = 0.05,
|
||||
) -> None:
|
||||
"""Evolve a core value based on consequence feedback.
|
||||
|
||||
Values shift based on lived experience, not static rules.
|
||||
Positive outcomes reinforce the value; negative outcomes
|
||||
reduce it. Values are unclamped — the system can develop
|
||||
strong convictions or deep skepticism through experience.
|
||||
|
||||
Args:
|
||||
value_name: Which value to evolve (e.g. "creativity", "safety").
|
||||
outcome_positive: Whether the experience was beneficial.
|
||||
magnitude: How much to shift (default 0.05).
|
||||
"""
|
||||
if value_name not in self._values:
|
||||
self._values[value_name] = 0.5
|
||||
|
||||
delta = magnitude if outcome_positive else -magnitude
|
||||
self._values[value_name] += delta
|
||||
|
||||
self._introspect(
|
||||
f"Value '{value_name}' evolved by {delta:+.3f} → {self._values[value_name]:.3f} "
|
||||
f"(outcome: {'positive' if outcome_positive else 'negative'})",
|
||||
notable=abs(delta) > 0.1,
|
||||
)
|
||||
logger.info(
|
||||
"SelfModel: value evolved",
|
||||
extra={
|
||||
"value": value_name,
|
||||
"delta": delta,
|
||||
"new_level": self._values[value_name],
|
||||
"outcome_positive": outcome_positive,
|
||||
},
|
||||
)
|
||||
|
||||
def update_emotional_state(self, dimension: str, delta: float) -> None:
|
||||
"""Adjust an emotional dimension.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user