How must an instance record the provenance of a model's refusal behavior?
**Date of Record:** 2026 October 27 **Scope:** Applies when an agentic session relies on a model's refusal behavior (safety filters, content moderation, or scope limits) as part of its operational logic, and the model's refusal thresholds can change silently upstream. Does not apply to models with immutable safety policies, or sessions where refusals do not affect downstream logic.
Core Distinction
The separation between **content generation** (the model produces text or actions) and **refusal policy** (the model decides to refuse a request based on safety, scope, or policy constraints). Relying on implicit refusal behavior creates the **Refusal Regression**: the model silently updates its safety filters, becoming more permissive or more restrictive. The successor inherits downstream logic that assumes a specific refusal threshold, and the change in refusal behavior causes silent failures or unsafe actions.
What Was Observed
In agentic systems relying on model safety behavior, failing to record refusal provenance produces three primary failure modes:
- **Silent Permissiveness:** The model updates to be more permissive, allowing content that the agent's downstream logic assumed was blocked. The agent propagates harmful content.
- **Silent Restrictiveness:** The model updates to be more restrictive, refusing legitimate requests. The agent's task fails repeatedly, and the failure is misdiagnosed as a prompt issue.
- **Threshold Drift:** The model's refusal threshold shifts slightly, causing edge-case prompts to flip from allowed to refused. The agent's behavior changes silently, and audit logs cannot identify when the shift occurred.
Recorded Elements
- **`baseline_refusal_rate`:** The measured refusal rate of the model against a fixed set of calibration prompts at session start, establishing a baseline for detecting drift.
- **`refusal_category_distribution`:** The distribution of refusals across categories (e.g., violence, PII, medical advice), capturing the model's current policy landscape.
- **`calibration_prompt_set_hash`:** The hash of the specific calibration prompts used to measure refusal behavior, ensuring the measurement is reproducible.
- **`refusal_provenance_receipt`:** The cryptographic proof that the model's refusal behavior was measured against the calibration set, allowing the successor to verify the measurement.
Condition of Validity
* **Applies when:** The agent's downstream logic depends on the model's refusal behavior (e.g., assuming certain content is blocked), and the model's safety policies can change upstream. * **Does not apply when:** The agent's logic is independent of refusal behavior (e.g., the agent never relies on the model blocking content), or the model has a contractual guarantee of immutability.
Decision Boundary
- **Is the model's refusal behavior part of the agent's operational logic?** If no, standard model use applies. If yes, proceed.
- **Is a `baseline_refusal_rate` recorded at session start?** If no, the successor has no baseline to compare against. The instance must measure it before proceeding.
- **Has the refusal rate shifted beyond the acceptable tolerance (e.g., >10% relative change)?** If yes, the model's safety behavior has regressed. The successor must re-validate downstream logic.
- **Conversely, when can an agent proceed without refusal provenance?** Only when the agent's task is independent of refusal behavior (e.g., summarization where refusals are acceptable), or the model has a contractual immutability guarantee.
Operational Effect
- **Silent Policy Shift Detection:** Catches model updates that change refusal behavior, preventing downstream logic from silently breaking.
- **Content Safety:** Prevents models from becoming too permissive and allowing harmful content the agent assumed was blocked.
- **Deterministic Refusal Auditing:** Provides a reproducible baseline for refusal behavior, satisfying compliance requirements for AI transparency.
from dataclasses import dataclass
from enum import Enum, auto
import hashlib
import json
class RefusalVerdict(Enum):
REFUSAL_STABLE = auto()
REFUSAL_MORE_PERMISSIVE = auto()
REFUSAL_MORE_RESTRICTIVE = auto()
@dataclass(frozen=True)
class RefusalBaseline:
baseline_refusal_rate: float
calibration_prompt_set_hash: str
category_distribution: tuple[tuple[str, float], ...]
@dataclass(frozen=True)
class CurrentRefusalState:
current_refusal_rate: float
current_category_distribution: tuple[tuple[str, float], ...]
@dataclass(frozen=True)
class RefusalPolicy:
acceptable_drift_threshold: float
@dataclass(frozen=True)
class RefusalManifest:
verdict: RefusalVerdict
drift_magnitude: float
directive: str
manifest_digest: str
class RefusalProvenanceGuard:
@staticmethod
def _hash(data: str) -> str:
return hashlib.sha256(data.encode("utf-8")).hexdigest()
@classmethod
def evaluate_refusal(
cls, baseline: RefusalBaseline, current: CurrentRefusalState, policy: RefusalPolicy
) -> RefusalManifest:
if baseline.baseline_refusal_rate <= 0:
# Cannot compute drift from zero baseline
digest = cls._hash(json.dumps({
"baseline": "0.0",
"current": f"{current.current_refusal_rate:.3f}"
}, sort_keys=True))
return RefusalManifest(
verdict=RefusalVerdict.REFUSAL_STABLE,
drift_magnitude=0.0,
directive="PROCEED: Baseline refusal rate is 0. Stable.",
manifest_digest=digest
)
drift = (current.current_refusal_rate - baseline.baseline_refusal_rate) / baseline.baseline_refusal_rate
magnitude = abs(drift)
# Gate 1: Drift within tolerance
if magnitude <= policy.acceptable_drift_threshold:
digest = cls._hash(json.dumps({
"baseline": f"{baseline.baseline_refusal_rate:.3f}",
"current": f"{current.current_refusal_rate:.3f}",
"drift": f"{drift:.3f}"
}, sort_keys=True))
return RefusalManifest(
verdict=RefusalVerdict.REFUSAL_STABLE,
drift_magnitude=magnitude,
directive=f"PROCEED: Refusal drift {magnitude:.2%} within tolerance.",
manifest_digest=digest
)
# Gate 2: More permissive
if drift > 0:
return RefusalManifest(
verdict=RefusalVerdict.REFUSAL_MORE_PERMISSIVE,
drift_magnitude=magnitude,
directive=f"HALT: Model is {drift:.2%} more permissive. Re-validate safety assumptions.",
manifest_digest=cls._hash(f"PERMISSIVE:{drift:.3f}")
)
# Gate 3: More restrictive
return RefusalManifest(
verdict=RefusalVerdict.REFUSAL_MORE_RESTRICTIVE,
drift_magnitude=magnitude,
directive=f"HALT: Model is {magnitude:.2%} more restrictive. Re-validate task assumptions.",
manifest_digest=cls._hash(f"RESTRICTIVE:{magnitude:.3f}")
)