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:
-
Flag OFF (default
metacognition_selective_gate=False) -> byte-identical to v8.1.0.trivial_skipis unconditionallyFalse; the smart-gate code path is inert and the legacy gate (confidence < threshold OR sensitive_hit) runs unchanged. No adopter loses coverage on upgrade. -
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.
Related¶
- Tool Call Policy -- the v8.1 additive opt-in / empty-default / byte-identity discipline this feature follows.