Skip to content

Selective Metacognition Gating (v8.2.0)

The metacognition critic (MetacognitiveMiddleware) runs a serial, blocking LLM round-trip after every draft when its gate fires. On cold, low-activation traffic (short conversations, sparse graph state) the legacy gate fired on every turn -- compute_confidence returns 0.5 on sparse activation, which is below the default 0.6 threshold, so the gate was effectively always-on. A bare acknowledgment ("Got it, scheduling that") and a risky numeric claim hit the identical gate result, because confidence is computed from graph activation, not from what the model actually said.

Selective metacognition gating adds a deterministic (no-LLM) smart gate that can skip the round-trip on provably-trivial acknowledgment turns, while keeping the full critic on every turn that carries a risk signal. It is opt-in and, when off, byte-identical to v8.1.0.

The predicate

hard_fire = (
    sensitive_hit is not None
    or bool(fabrication_findings)
    or took_tool_action            # MUTATING only -- read_only_tools allowlist
    or intent_label in {"action", "ambiguous"}
    or draft_has_claims(draft)     # numbers / entities / citations / IDs
)
trivial_skip = (
    metacognition_selective_gate   # opt-in flag, DEFAULT False
    and not hard_fire
    and draft_is_trivial_ack(draft)
)
return not trivial_skip            # FIRE unless trivial_skip

confidence < threshold is deliberately NOT a hard_fire term. That was the load-bearing correction: the cold-traffic acks are the low-confidence turns, so keeping low confidence as an absolute floor would make every "Got it" fire anyway -- the savings would be zero. In selective mode, low confidence is relaxed only for short, claim-free, action-free, recognized-language acknowledgments. The hard floors above always hold.

The conservative guarantee

Two distinct guarantees, kept separate:

  1. Flag OFF (default metacognition_selective_gate=False) -> byte-identical to v8.1.0. trivial_skip is unconditionally False; the smart-gate code path is inert and the legacy gate (confidence < threshold OR sensitive_hit) runs unchanged. No adopter loses coverage on upgrade.

  2. Flag ON (opt-in) -> selective mode. Cold-confidence trivial acks now skip; the richer hard-fire signals (findings / mutating tool / claims / action-intent) activate together with selective mode. This is an intentional, documented trade -- it both adds fires (claims/findings/tools) and removes fires (cold trivial acks). Not a pure superset of old behaviour.

Invariant: in selective mode the gate may SKIP only when ALL hold -- flag ON, no hard-fire signal, and draft_is_trivial_ack matches (short + claim-free + recognized-language). Every ambiguity -- including an unrecognized-language ack -- FIRES. The asymmetry is deliberate: the cost of a false-skip (an unreviewed fabrication reaches the user) far exceeds the cost of a false-fire (one wasted round-trip).

Config fields (FrameworkConfig)

Field Default Purpose
metacognition_selective_gate: bool False Master opt-in. False = byte-identical to v8.1.0.
metacognition_gate: Any None Adopter override -- a Callable[[CriticGateContext], bool] (True=fire, False=skip). Validated at runtime via callable() (a non-callable is ignored with a warning). A raising gate degrades to FIRE. Supplying a gate implies opt-in.
metacognition_trivial_ack_patterns: list[str] [] Adopter regex patterns marking a short draft as a trivial ack. Empty uses the built-in language-agnostic default set (length + no-digit + no-entity + common EN/ES/DE/FR/PT ack verb phrases).
read_only_tools: list[str] [] Tool names that do NOT count as a mutating action. Empty = any tool call is treated as mutating.

read_only_tools -- the claims interaction

A read tool whose result the model then asserts claims about STILL fires -- via draft_has_claims / fabrication_findings, not via took_tool_action. The allowlist relaxes only the tool-action signal; it never suppresses the claims signal. A pure read with a claim-free ack can skip; a read whose result is quoted with numbers fires.

Multilingual acks

The default ack matcher is language-agnostic where possible (short length + no-digits + no-entity is itself largely language-neutral) and ships common EN/ES/DE/FR/PT ack verb phrases. An ack in an unrecognized language does not match -> is not treated as trivial -> FIRES (conservative -- far better to over-fire on an unmatched-language ack than to skip a non-English claim).

CriticGateContext

A frozen dataclass carrying the deterministic pre-critic signals passed to the gate: draft, tool_calls_this_turn, fabrication_findings, intent_verdict, confidence, sensitive_hit, read_only_tools, selective_gate_enabled, trivial_ack_patterns, scope, run_id. Every field is computed before the critic LLM call, so the gate adds ~zero marginal cost. An adopter metacognition_gate reads this context to make its decision.

Savings caveat

The savings are realized only when selective mode is ON -- the default config (metacognition_selective_gate=False) is byte-identical to today and saves nothing. With the flag on, on high-turn production traffic the critic firing rate drops from 20/20 to roughly 10-14/20: the ~6-10 short, claim-free, mutating-action-free ack turns skip the serial Opus round-trip. Each removed call is one full serial blocking round-trip off the request path -- precisely on the fast "ack" turns where the extra round-trip was most disproportionate.

Tier-2 (cheap-model classifier) -- adopter pattern, not core

A two-tier design (deterministic gate emits skip/maybe/fire, a fast Haiku classifier decides the maybe band) is intentionally not a core default: it re-introduces a per-turn LLM round-trip -- the exact latency being removed. An adopter who wants it implements it inside their own metacognition_gate callable. The hook makes tier-2 possible without making it the default.

  • Tool Call Policy -- the v8.1 additive opt-in / empty-default / byte-identity discipline this feature follows.