"""Meta-reasoning: challenge assumptions, detect contradictions, revisit nodes.""" from __future__ import annotations from typing import Any from fusionagi.schemas.atomic import AtomicSemanticUnit, AtomicUnitType from fusionagi.reasoning.tot import ThoughtNode, expand_node from fusionagi._logger import logger def challenge_assumptions( units: list[AtomicSemanticUnit], current_conclusion: str, ) -> list[str]: """ Identify and flag assumptions in units that support the conclusion. """ flagged: list[str] = [] conclusion_lower = current_conclusion.lower() for u in units: if u.type == AtomicUnitType.ASSUMPTION: flagged.append(u.content) elif "assume" in u.content.lower() or "assumption" in u.content.lower(): flagged.append(u.content) elif u.type == AtomicUnitType.CONSTRAINT: if any(w in conclusion_lower for w in ["must", "should", "require"]): flagged.append(f"Constraint may be assumed: {u.content[:100]}") logger.debug("Assumptions flagged", extra={"count": len(flagged)}) return flagged def detect_contradictions( units: list[AtomicSemanticUnit], ) -> list[tuple[str, str]]: """ Find conflicting units (heuristic: negation mismatch, same subject). """ neg_words = {"not", "no", "never", "none", "cannot", "shouldn't", "won't", "don't", "doesn't"} pairs: list[tuple[str, str]] = [] for i, a in enumerate(units): wa = set(a.content.lower().split()) for b in units[i + 1:]: wb = set(b.content.lower().split()) a_neg = bool(wa & neg_words) b_neg = bool(wb & neg_words) if a_neg != b_neg: overlap = len(wa & wb) / max(len(wa), 1) if overlap > 0.2: pairs.append((a.unit_id, b.unit_id)) logger.debug("Contradictions detected", extra={"count": len(pairs)}) return pairs def revisit_node( tree: ThoughtNode | None, node_id: str, new_evidence: str, ) -> ThoughtNode | None: """ Re-expand a node when new evidence arrives. Creates a new child with the new evidence. """ if tree is None: return None def find_node(n: ThoughtNode) -> ThoughtNode | None: if n.node_id == node_id: return n for c in n.children: found = find_node(c) if found: return found return None node = find_node(tree) if not node: return tree child = expand_node(node, new_evidence) child.metadata["revisit_evidence"] = new_evidence[:200] return tree