Files
FusionAGI/fusionagi/reasoning/metacognition.py
Devin AI 9a8affae9a
Some checks failed
Tests / test (3.10) (pull_request) Failing after 35s
Tests / test (3.11) (pull_request) Failing after 34s
Tests / test (3.12) (pull_request) Successful in 39s
Tests / lint (pull_request) Successful in 36s
Tests / docker (pull_request) Successful in 1m42s
feat: consequence engine, causal world model, metacognition, interpretability, claim verification
Choice → Consequence → Learning:
- ConsequenceEngine tracks every decision point with alternatives,
  risk/reward estimates, and actual outcomes
- Consequences feed into AdaptiveEthics for experience-based learning
- FusionAGILoop now wires ethics + consequences into task lifecycle

Causal World Model:
- CausalWorldModel learns state-transition patterns from execution history
- Predicts outcomes based on observed action→effect patterns
- Uncertainty estimates decrease as more evidence accumulates

Metacognition:
- assess_head_outputs() evaluates reasoning quality from head outputs
- Detects knowledge gaps, measures head agreement, identifies uncertainty
- Actively recommends whether to seek more information

Interpretability:
- ReasoningTracer captures full prompt→answer reasoning traces
- Each step records stage, component, input/output, timing
- explain() generates human-readable reasoning explanations

Claim Verification:
- ClaimVerifier cross-checks claims for evidence, consistency, grounding
- Flags high-confidence claims lacking evidence support
- Detects contradictions between claims from different heads

325 tests passing, 0 ruff errors, 0 mypy errors.

Co-Authored-By: Nakamoto, S <defi@defi-oracle.io>
2026-04-28 06:25:35 +00:00

263 lines
9.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Metacognition: self-awareness of knowledge boundaries and reasoning quality.
The metacognition engine monitors the system's own reasoning processes
and produces self-assessments:
- Does the system have enough evidence to answer confidently?
- Which knowledge gaps exist?
- Where are the reasoning weak points?
- Should the system seek more information before answering?
This is distinct from meta_reasoning.py (which challenges assumptions
and detects contradictions in content). Metacognition operates on
the *process* level — it reasons about the quality of reasoning itself.
"""
from __future__ import annotations
from dataclasses import dataclass, field
from fusionagi._logger import logger
from fusionagi.schemas.head import HeadOutput
@dataclass
class KnowledgeGap:
"""An identified gap in the system's knowledge.
Attributes:
domain: Knowledge domain (e.g. ``legal``, ``medical``).
description: What the system doesn't know.
severity: Impact on answer quality (``low``, ``medium``, ``high``).
resolvable: Whether additional tool calls could fill this gap.
"""
domain: str
description: str
severity: str = "medium"
resolvable: bool = True
@dataclass
class MetacognitiveAssessment:
"""Self-assessment of reasoning quality for a given task.
Attributes:
overall_confidence: System's confidence in its answer (0.01.0).
evidence_sufficiency: Whether evidence is sufficient (0.01.0).
knowledge_gaps: Identified gaps in knowledge.
reasoning_quality: Assessment of the reasoning chain quality.
should_seek_more: Whether the system should seek more info.
head_agreement: Fraction of heads that agree (0.01.0).
uncertainty_sources: Where uncertainty comes from.
recommendations: What the system should do next.
"""
overall_confidence: float = 0.5
evidence_sufficiency: float = 0.5
knowledge_gaps: list[KnowledgeGap] = field(default_factory=list)
reasoning_quality: float = 0.5
should_seek_more: bool = False
head_agreement: float = 0.5
uncertainty_sources: list[str] = field(default_factory=list)
recommendations: list[str] = field(default_factory=list)
def assess_head_outputs(
outputs: list[HeadOutput],
user_prompt: str = "",
) -> MetacognitiveAssessment:
"""Assess reasoning quality from head outputs.
Analyzes the collection of head outputs for agreement patterns,
confidence distribution, evidence coverage, and knowledge gaps.
Args:
outputs: Outputs from Dvādaśa content heads.
user_prompt: Original user prompt for context.
Returns:
Metacognitive assessment of reasoning quality.
"""
if not outputs:
return MetacognitiveAssessment(
overall_confidence=0.0,
evidence_sufficiency=0.0,
should_seek_more=True,
uncertainty_sources=["No head outputs available"],
recommendations=["Execute head pipeline before assessment"],
)
confidences: list[float] = []
for out in outputs:
if out.claims:
confidences.extend(c.confidence for c in out.claims)
else:
confidences.append(0.0)
avg_confidence = sum(confidences) / len(confidences) if confidences else 0.0
all_claims: list[str] = []
for out in outputs:
all_claims.extend(c.claim_text for c in out.claims)
evidence_counts = []
for out in outputs:
for c in out.claims:
evidence_counts.append(len(c.evidence))
avg_evidence = sum(evidence_counts) / max(len(evidence_counts), 1)
evidence_sufficiency = min(1.0, avg_evidence / 3.0)
head_agreement = _compute_head_agreement(outputs)
gaps = _detect_knowledge_gaps(outputs, user_prompt)
uncertainty_sources: list[str] = []
if avg_confidence < 0.5:
uncertainty_sources.append(f"Low average head confidence: {avg_confidence:.2f}")
if head_agreement < 0.4:
uncertainty_sources.append(f"Low head agreement: {head_agreement:.2f}")
if evidence_sufficiency < 0.3:
uncertainty_sources.append(f"Insufficient evidence: avg {avg_evidence:.1f} per claim")
if gaps:
uncertainty_sources.append(f"{len(gaps)} knowledge gap(s) detected")
conf_variance = _variance(confidences) if len(confidences) > 1 else 0.0
if conf_variance > 0.1:
uncertainty_sources.append(
f"High confidence variance across heads: {conf_variance:.3f}"
)
reasoning_quality = (
0.4 * avg_confidence
+ 0.3 * head_agreement
+ 0.2 * evidence_sufficiency
+ 0.1 * (1.0 - min(1.0, len(gaps) * 0.2))
)
should_seek_more = (
reasoning_quality < 0.4
or evidence_sufficiency < 0.3
or any(g.severity == "high" and g.resolvable for g in gaps)
)
recommendations: list[str] = []
if should_seek_more:
recommendations.append("Seek additional evidence before finalizing answer")
if head_agreement < 0.4:
recommendations.append("Run second-pass with disputed heads for clarification")
for gap in gaps:
if gap.resolvable:
recommendations.append(f"Fill knowledge gap: {gap.description}")
overall = min(1.0, 0.5 * reasoning_quality + 0.3 * head_agreement + 0.2 * evidence_sufficiency)
assessment = MetacognitiveAssessment(
overall_confidence=overall,
evidence_sufficiency=evidence_sufficiency,
knowledge_gaps=gaps,
reasoning_quality=reasoning_quality,
should_seek_more=should_seek_more,
head_agreement=head_agreement,
uncertainty_sources=uncertainty_sources,
recommendations=recommendations,
)
logger.info(
"Metacognition: assessment complete",
extra={
"overall_confidence": overall,
"reasoning_quality": reasoning_quality,
"head_agreement": head_agreement,
"gaps": len(gaps),
"should_seek_more": should_seek_more,
},
)
return assessment
def _compute_head_agreement(outputs: list[HeadOutput]) -> float:
"""Measure how much heads agree with each other.
Uses claim text overlap across heads as a proxy for agreement.
"""
if len(outputs) < 2:
return 1.0
claim_sets: list[set[str]] = []
for out in outputs:
words: set[str] = set()
for c in out.claims:
words.update(c.claim_text.lower().split())
claim_sets.append(words)
agreements: float = 0.0
comparisons = 0
for i in range(len(claim_sets)):
for j in range(i + 1, len(claim_sets)):
if not claim_sets[i] or not claim_sets[j]:
continue
overlap = len(claim_sets[i] & claim_sets[j])
union = len(claim_sets[i] | claim_sets[j])
if union > 0:
agreements += overlap / union
comparisons += 1
return agreements / max(comparisons, 1)
def _detect_knowledge_gaps(
outputs: list[HeadOutput],
user_prompt: str,
) -> list[KnowledgeGap]:
"""Detect knowledge gaps from head outputs and prompt analysis."""
gaps: list[KnowledgeGap] = []
for out in outputs:
if out.claims:
avg_claim_conf = sum(c.confidence for c in out.claims) / len(out.claims)
else:
avg_claim_conf = 0.0
if avg_claim_conf < 0.3:
gaps.append(KnowledgeGap(
domain=out.head_id.value,
description=f"Head '{out.head_id.value}' has very low confidence ({avg_claim_conf:.2f})",
severity="high" if avg_claim_conf < 0.15 else "medium",
resolvable=True,
))
empty_heads = [o for o in outputs if not o.claims]
for out in empty_heads:
gaps.append(KnowledgeGap(
domain=out.head_id.value,
description=f"Head '{out.head_id.value}' produced no claims",
severity="medium",
resolvable=True,
))
prompt_lower = user_prompt.lower()
domain_indicators = {
"legal": ["law", "legal", "court", "statute", "regulation", "compliance"],
"medical": ["medical", "health", "disease", "treatment", "clinical", "patient"],
"financial": ["financial", "stock", "market", "investment", "trading", "portfolio"],
"scientific": ["experiment", "hypothesis", "data", "study", "research", "evidence"],
}
for domain, keywords in domain_indicators.items():
if any(kw in prompt_lower for kw in keywords):
head_domains = {o.head_id.value for o in outputs}
if domain not in head_domains:
gaps.append(KnowledgeGap(
domain=domain,
description=f"Prompt references '{domain}' domain but no specialized head covers it",
severity="medium",
resolvable=False,
))
return gaps
def _variance(values: list[float]) -> float:
"""Compute variance of a list of floats."""
if len(values) < 2:
return 0.0
mean = sum(values) / len(values)
return sum((v - mean) ** 2 for v in values) / len(values)