Skip to content

symfonic.capabilities.critic.signals

signals

Critic gate signals, ported from the agent middleware (CORE-403).

:func:compute_confidence and :func:detect_sensitive_hit are verbatim ports of symfonic.agent.middleware.confidence -- pure functions, copied rather than imported so this package depends on nothing outside itself and the kernel. :func:critic_gate_fires ports MetacognitiveMiddleware._gate_fires: fire on low confidence OR a sensitive hit. The selective smart gate, adopter gates, and fabrication/intent threading stay on the middleware; the port carries the core contract only.

Per-turn signals arrive through the resolved snapshot under :data:SIGNALS_ENTRY. No kernel stage publishes activation records yet; a composition root that wants firing behavior supplies the entry (tests supply fixtures). Absent, the critic degrades to cold-start neutral -- confidence 0.5, no hit -- so with the stock 0.6 threshold it reviews every turn rather than silently approving ungrounded ones.

activation_from_resolved

activation_from_resolved(resolved: Any) -> dict[str, Any]

The turn's activation record, or {} when no producer supplied one.

Source code in src/symfonic/capabilities/critic/signals.py
def activation_from_resolved(resolved: Any) -> dict[str, Any]:
    """The turn's activation record, or ``{}`` when no producer supplied one."""
    if resolved is None:
        return {}
    if isinstance(resolved, Mapping):
        entry = resolved.get(SIGNALS_ENTRY)
    else:
        get = getattr(resolved, "get", None)
        entry = get(SIGNALS_ENTRY) if callable(get) else None
    if entry is None:
        return {}
    value = getattr(entry, "value", entry)
    return dict(value) if isinstance(value, Mapping) else {}

compute_confidence

compute_confidence(activation_log: dict[str, Any]) -> float

Deterministic heuristic in [0.0, 1.0]. Returns 0.5 on empty input.

Formula

confidence = clamp01( 0.4 * seed_strength + 0.3 * path_coverage + 0.3 * edge_weight_factor )

Where

seed_strength = avg score of activated_nodes (>= 1.0 if seeded) path_coverage = min(1.0, len(inference_paths) / 3) edge_weight_factor = avg edge weight of traversed_edges / 5.0, capped at 1.0

Source code in src/symfonic/capabilities/critic/signals.py
def compute_confidence(activation_log: dict[str, Any]) -> float:
    """Deterministic heuristic in [0.0, 1.0]. Returns 0.5 on empty input.

    Formula:
        confidence = clamp01(
            0.4 * seed_strength
          + 0.3 * path_coverage
          + 0.3 * edge_weight_factor
        )

    Where:
        seed_strength  = avg score of activated_nodes (>= 1.0 if seeded)
        path_coverage  = min(1.0, len(inference_paths) / 3)
        edge_weight_factor = avg edge weight of traversed_edges / 5.0, capped at 1.0
    """
    if not activation_log:
        return 0.5

    activated_nodes = activation_log.get("activated_nodes") or []
    inference_paths = activation_log.get("inference_paths") or []
    traversed_edges = activation_log.get("traversed_edges") or []

    # seed_strength: average score of activated_nodes
    if activated_nodes:
        scores = [n.get("score", 0.0) for n in activated_nodes]
        seed_strength = sum(scores) / len(scores)
    else:
        return 0.5  # cold start — neutral

    # path_coverage
    path_coverage = min(1.0, len(inference_paths) / 3.0)

    # edge_weight_factor
    if traversed_edges:
        weights = [e.get("weight", 0.0) for e in traversed_edges]
        edge_weight_factor = min(1.0, (sum(weights) / len(weights)) / 5.0)
    else:
        edge_weight_factor = 0.0

    confidence = (
        0.4 * seed_strength
        + 0.3 * path_coverage
        + 0.3 * edge_weight_factor
    )
    return _clamp01(confidence)

critic_gate_fires

critic_gate_fires(confidence: float, sensitive_hit: str | None, threshold: float) -> bool

Fire on low confidence OR a sensitive hit (legacy gate, verbatim).

Source code in src/symfonic/capabilities/critic/signals.py
def critic_gate_fires(
    confidence: float, sensitive_hit: str | None, threshold: float
) -> bool:
    """Fire on low confidence OR a sensitive hit (legacy gate, verbatim)."""
    if confidence < threshold:
        return True
    return sensitive_hit is not None

detect_sensitive_hit

detect_sensitive_hit(activation_log: dict[str, Any], sensitive_tags: frozenset[str]) -> str | None

Return the first matching tag when any activated_node carries one of sensitive_tags in properties['tags'], else None.

Source code in src/symfonic/capabilities/critic/signals.py
def detect_sensitive_hit(
    activation_log: dict[str, Any],
    sensitive_tags: frozenset[str],
) -> str | None:
    """Return the first matching tag when any activated_node carries one of
    *sensitive_tags* in properties['tags'], else None."""
    if not sensitive_tags:
        return None

    activated_nodes = (activation_log or {}).get("activated_nodes") or []
    for node in activated_nodes:
        node_tags = node.get("tags") or node.get("properties", {}).get("tags") or []
        for tag in node_tags:
            if tag in sensitive_tags:
                return str(tag)
    return None