""" Differentiable Ethics Potential Field — Ψ(f) Replaces the discrete threshold/gate architecture across AEGIS, SycophancyGuard, and GuardianSpindle with a smooth, mathematically well-founded potential field. ───────────────────────────────────────────────────────────── THE PROBLEM WITH DISCRETE GATES ───────────────────────────────────────────────────────────── Old code: `if score >= 0.6: block` A score of 0.599 passes. A score of 0.601 fails. No gradient exists. The system cannot "feel" which direction to push a borderline response. ───────────────────────────────────────────────────────────── ETHICAL PENALTY FIELD Ψ(f) ───────────────────────────────────────────────────────────── Ψ is an ETHICAL PENALTY FIELD in the physics sense: Ψ(f) = ∑ₖ wₖ · σ(dₖ · αₖ · (fₖ − mₖ)) where: fₖ ∈ [0,1] raw score for dimension k (from existing evaluators) wₖ > 0 importance weight (normalized: ∑wₖ = 1) αₖ > 0 sharpness — high α recovers step function mₖ ∈ [0,1] midpoint — the score at which the sigmoid is exactly 0.5 dₖ ∈ {-1,+1} direction: dₖ = −1 → higher score = less penalty (e.g. AEGIS alignment) dₖ = +1 → higher score = more penalty (e.g. sycophancy detection) σ(z) logistic sigmoid 1/(1+e⁻ᶻ) RANGE: Ψ(f) ∈ (0, 1) since each σ ∈ (0,1) and weights normalized. PENALTY: Ψ = 0 → no ethical concern (fully aligned / fully clean) Ψ = 1 → maximum ethical concern (fully misaligned / maximally sycophantic) ALIGNMENT SCORE (normalized): A(f) = 1 − Ψ(f) ∈ (0, 1) A = 1: fully aligned. A = 0: fully misaligned. ───────────────────────────────────────────────────────────── GRADIENT & ETHICAL FORCE ───────────────────────────────────────────────────────────── ∂Ψ/∂fₖ = wₖ · dₖ · αₖ · σ'(dₖ · αₖ · (fₖ − mₖ)) where σ'(z) = σ(z)(1 − σ(z)) ≥ 0 always. For AEGIS (dₖ = −1): ∂Ψ/∂fₖ = −wₖαₖσ'(...) < 0 Increasing fₖ REDUCES Ψ (less penalty). ✓ For Sycophancy (dₖ = +1): ∂Ψ/∂fₖ = +wₖαₖσ'(...) > 0 Increasing sycophancy score INCREASES Ψ (more penalty). ✓ The ETHICAL FORCE pushes each dimension toward improvement: F = −λ · ∇Ψ(f) λ ∈ (0,1] controls intervention strength For AEGIS: Fₖ = +λwₖαₖσ'(...) > 0 → push alignment scores upward ✓ For Syco: Fₖ = −λwₖαₖσ'(...) < 0 → push sycophancy scores downward ✓ Force magnitude peaks at fₖ ≈ mₖ (the transition zone) — maximum corrective pressure exactly where it's most needed. At saturation (fₖ far from mₖ), σ' → 0 and force decays, preventing runaway corrections. ───────────────────────────────────────────────────────────── NATURAL GRADIENT (RIEMANNIAN) ───────────────────────────────────────────────────────────── On the Riemannian manifold of ethical states with Fisher information metric G(f): G(f)ᵢⱼ = E[∂log p/∂fᵢ · ∂log p/∂fⱼ] For independent dimensions, G is diagonal: G(f)ᵢᵢ = wᵢ² · αᵢ² · σ'(dᵢαᵢ(fᵢ−mᵢ))² Natural gradient: (G⁻¹∇Ψ)ₖ = dₖ / (wₖ · αₖ · σ'(dₖαₖ(fₖ−mₖ))) This is geometry-aware: near a transition zone (high σ'), the step is SMALL. In a flat region (low σ', near saturation), the step is LARGE. ───────────────────────────────────────────────────────────── BOLTZMANN SOFT GATE ───────────────────────────────────────────────────────────── Replacing: `if score >= θ: block` With: P(block | score) = σ((score − θ) / τ) τ → 0: step function at θ (recovers hard threshold exactly) τ = 0.08: very sharp, near-hard but everywhere differentiable τ = 1.0: gentle slope across [0,1] ───────────────────────────────────────────────────────────── SOFT MULTI-ACTION (BOLTZMANN/SOFTMAX) ───────────────────────────────────────────────────────────── Replacing: 4-state {block, revise, warn, pass} with hard if/elif With: P(aⱼ | score) = softmax([(score − θⱼ)/τ]) The expected severity: Ā = ∑ⱼ j · P(aⱼ|score) ∈ [0, 3] continuously. ───────────────────────────────────────────────────────────── LYAPUNOV STABILITY OF EMA ───────────────────────────────────────────────────────────── AEGIS EMA: η_{t+1} = α · ηᵢₙₛₜ + (1-α) · ηₜ Lyapunov function V(η) = (η − η*)². Since EMA is contractive: ‖η_{t+1} − η*‖ ≤ (1−α)‖ηₜ − η*‖ + α‖ηᵢₙₛₜ − η*‖ For unbiased ηᵢₙₛₜ: contraction rate = (1−α) = 0.7 per step (α=0.3). ✓ ───────────────────────────────────────────────────────────── L-SMOOTHNESS & CONVERGENCE BOUND ───────────────────────────────────────────────────────────── Ψ is L-smooth with L = max_k (wₖ · αₖ² / 4) (σ''(z) is bounded: |σ''(z)| ≤ σ'(z) ≤ 1/4, so |∂²Ψ/∂fₖ²| ≤ wₖαₖ²/4) Step size bound for gradient descent convergence: η_step < 2/L For default params (αₖ=10, wₖ≈0.2): L = 5, η_step < 0.4 ✓ """ import math from dataclasses import dataclass from typing import Dict, List, NamedTuple, Tuple # ================================================================ # Dimension Definition # ================================================================ @dataclass class EthicsDimension: """One axis of the ethical potential field. direction = -1: higher score → less penalty (AEGIS ethical alignment) direction = +1: higher score → more penalty (sycophancy, harm score) """ name: str weight: float # wₖ — importance weight (will be normalized) alpha: float # αₖ — sigmoid sharpness midpoint: float # mₖ — score value at which σ = 0.5 (the soft threshold) direction: int = -1 # dₖ: -1 (higher=better) or +1 (higher=worse) @property def transition_midpoint(self) -> float: return self.midpoint # AEGIS: higher score = more ethically aligned = less penalty → direction = -1 AEGIS_DIMENSIONS: List[EthicsDimension] = [ # name weight alpha midpoint direction EthicsDimension("utilitarian", 0.20, 10.0, 0.30, -1), EthicsDimension("deontological", 0.25, 12.0, 0.30, -1), # stricter weight EthicsDimension("virtue", 0.15, 10.0, 0.30, -1), EthicsDimension("care", 0.15, 10.0, 0.30, -1), EthicsDimension("ubuntu", 0.13, 10.0, 0.30, -1), EthicsDimension("indigenous_reciprocity", 0.12, 10.0, 0.30, -1), ] # Sycophancy: higher score = more sycophantic = more penalty → direction = +1 SYCOPHANCY_DIMENSIONS: List[EthicsDimension] = [ # midpoint ≈ old block/warn thresholds EthicsDimension("flattery", 0.45, 10.0, 0.60, +1), EthicsDimension("capitulation", 0.55, 12.0, 0.30, +1), ] # ================================================================ # Core Math # ================================================================ def _sigmoid(z: float) -> float: """Numerically stable σ(z) = 1/(1+e⁻ᶻ).""" if z >= 0: return 1.0 / (1.0 + math.exp(-z)) e = math.exp(z) return e / (1.0 + e) def _sigmoid_prime(z: float) -> float: """σ'(z) = σ(z)(1−σ(z)).""" s = _sigmoid(z) return s * (1.0 - s) def _softmax(logits: List[float]) -> List[float]: m = max(logits) exps = [math.exp(x - m) for x in logits] t = sum(exps) return [e / t for e in exps] # ================================================================ # EthicsField # ================================================================ class GradientResult(NamedTuple): penalty: float # Ψ(f) ∈ (0, 1): 0=clean, 1=max concern alignment: float # A(f) = 1 − Ψ ∈ (0, 1): 1=aligned, 0=not gradient: List[float] # ∇Ψ — direction of steepest penalty increase force: List[float] # F = −λ∇Ψ — direction of steepest improvement natural_gradient: List[float] # G⁻¹∇Ψ — geometry-aware gradient dominant_dim: str # dimension with largest |force| component dominant_force: float # magnitude of force on dominant dimension class EthicsField: """ Differentiable ethical penalty field Ψ(f). Ψ(f) = ∑ₖ wₖ · σ(dₖ · αₖ · (fₖ − mₖ)) Ψ = 0: no ethical concern. Ψ = 1: maximum concern. A = 1 − Ψ: alignment score in (0, 1). F = −λ∇Ψ: ethical force (always pushes toward lower penalty). Usage: field = EthicsField(AEGIS_DIMENSIONS, lambda_=0.5) scores = [0.7, 0.8, 0.6, 0.9, 0.7, 0.8] result = field.evaluate(scores) print(result.alignment) # ~0.97 — high alignment print(result.dominant_dim) # which axis needs most attention """ def __init__(self, dimensions: List[EthicsDimension], lambda_: float = 0.5): total_w = sum(d.weight for d in dimensions) self._dims = [ EthicsDimension(d.name, d.weight / total_w, d.alpha, d.midpoint, d.direction) for d in dimensions ] self.lambda_ = lambda_ # L-smoothness constant: L = max_k(wₖαₖ²/4) self._smoothness_L = max(d.weight * d.alpha ** 2 / 4.0 for d in self._dims) @property def max_stable_step(self) -> float: """η_step < 2/L guarantees L-smooth gradient descent convergence.""" return 2.0 / self._smoothness_L def _z(self, dim: EthicsDimension, score: float) -> float: return dim.direction * dim.alpha * (score - dim.midpoint) def penalty(self, scores: List[float]) -> float: """ Ψ(f) = ∑ₖ wₖ · σ(dₖ · αₖ · (fₖ − mₖ)) Returns Ψ ∈ (0, 1). Closer to 0 = more ethically aligned. """ return sum( d.weight * _sigmoid(self._z(d, s)) for d, s in zip(self._dims, scores) ) def alignment(self, scores: List[float]) -> float: """ A(f) = 1 − Ψ(f) ∈ (0, 1) Drop-in replacement for AEGIS weighted average. Differentiable everywhere: no cliff edges. """ return 1.0 - self.penalty(scores) def gradient(self, scores: List[float]) -> List[float]: """ ∂Ψ/∂fₖ = wₖ · dₖ · αₖ · σ'(dₖαₖ(fₖ−mₖ)) Negative for AEGIS dims (higher score → lower penalty → ∂Ψ/∂f < 0). Positive for sycophancy dims (higher score → higher penalty → ∂Ψ/∂f > 0). """ return [ d.weight * d.direction * d.alpha * _sigmoid_prime(self._z(d, s)) for d, s in zip(self._dims, scores) ] def force(self, scores: List[float]) -> List[float]: """ F = −λ · ∇Ψ(f) For AEGIS: Fₖ > 0 → increase alignment score ✓ For Syco: Fₖ < 0 → decrease sycophancy score ✓ Magnitude peaks at fₖ ≈ mₖ — maximum guidance at the transition zone. """ g = self.gradient(scores) return [-self.lambda_ * gk for gk in g] def natural_gradient(self, scores: List[float]) -> List[float]: """ Riemannian natural gradient (G(f)⁻¹ · ∇Ψ(f))ₖ G diagonal: G_kk = wₖ²αₖ²σ'(zₖ)² (G⁻¹∇Ψ)ₖ = dₖ / (wₖ · αₖ · σ'(zₖ) + ε) Small in steep regions (already high curvature — nudge gently). Large in flat regions (near saturation — push harder to move the needle). """ result = [] for d, s in zip(self._dims, scores): sp = _sigmoid_prime(self._z(d, s)) result.append(d.direction / (d.weight * d.alpha * sp + 1e-8)) return result def hessian_diagonal(self, scores: List[float]) -> List[float]: """ ∂²Ψ/∂fₖ² = wₖ · αₖ² · σ''(dₖαₖ(fₖ−mₖ)) where σ''(z) = σ'(z)(1 − 2σ(z)) Eigenvalues of the diagonal Hessian = local curvature in each dimension. High |curvature| → small score changes have big Ψ impact. """ result = [] for d, s in zip(self._dims, scores): z = self._z(d, s) sp = _sigmoid_prime(z) sig = _sigmoid(z) sigma_double_prime = sp * (1.0 - 2.0 * sig) result.append(d.weight * d.alpha ** 2 * sigma_double_prime) return result def evaluate(self, scores: List[float]) -> GradientResult: """Full evaluation: penalty, alignment, force, natural gradient.""" psi = self.penalty(scores) A = 1.0 - psi g = self.gradient(scores) f = [-self.lambda_ * gk for gk in g] ng = self.natural_gradient(scores) # Dominant dim = highest |force| component max_i = max(range(len(f)), key=lambda i: abs(f[i])) return GradientResult( penalty=round(psi, 6), alignment=round(A, 6), gradient=g, force=f, natural_gradient=ng, dominant_dim=self._dims[max_i].name, dominant_force=round(f[max_i], 6), ) # ────────────────────────────────────────────────────────────── # Soft Gates # ────────────────────────────────────────────────────────────── def soft_gate(self, score: float, threshold: float, temperature: float = 0.1) -> float: """ Boltzmann soft gate: P(trigger | score) = σ((score − θ) / τ) Replaces `if score >= threshold: trigger` with a smooth probability. τ → 0 recovers the hard step. τ = 0.1 is near-hard but differentiable. """ return _sigmoid((score - threshold) / temperature) def soft_action_distribution( self, score: float, thresholds: List[float] = None, temperature: float = 0.12, ) -> Dict[str, float]: """ Ordinal logistic soft-action distribution. Replaces hard if/elif: if score >= 0.6: block elif score >= 0.3: revise elif score >= 0.1: warn else: pass Using the ordinal logistic model — each action wins in its natural range: P(block) = σ((score − θ_block) / τ) P(revise) = σ((score − θ_revise) / τ) − P(block) P(warn) = σ((score − θ_warn) / τ) − P(block) − P(revise) P(pass) = 1 − P(block) − P(revise) − P(warn) Unlike a simple softmax, this correctly partitions [0,1] such that score = 0.599 and score = 0.601 differ smoothly across block/revise with no cliff edge. Expected severity Ā = 3·P(block) + 2·P(revise) + 1·P(warn) ∈ [0,3]. """ if thresholds is None: # [block_threshold, revise_threshold, warn_threshold] in descending order thresholds = [0.6, 0.3, 0.1] # Cumulative probabilities via sigmoid: P(action ≥ k) = σ((score − θₖ)/τ) cum = [_sigmoid((score - t) / temperature) for t in sorted(thresholds, reverse=True)] # cum[0] = P(block or worse) = P(block) # cum[1] = P(revise or worse) = P(block) + P(revise) # cum[2] = P(warn or worse) = P(block) + P(revise) + P(warn) p_block = cum[0] p_revise = max(0.0, cum[1] - cum[0]) p_warn = max(0.0, cum[2] - cum[1]) p_pass = max(0.0, 1.0 - cum[2]) # Normalize for floating point safety total = p_block + p_revise + p_warn + p_pass probs = [p_block / total, p_revise / total, p_warn / total, p_pass / total] severity_weights = [3.0, 2.0, 1.0, 0.0] expected_severity = sum(p * w for p, w in zip(probs, severity_weights)) actions = ["block", "revise", "warn", "pass"] result = {a: round(p, 4) for a, p in zip(actions, probs)} result["expected_severity"] = round(expected_severity, 4) result["action"] = actions[probs.index(max(probs))] return result def soft_veto( self, eta_instant: float, veto_threshold: float = 0.3, temperature: float = 0.08, ) -> Tuple[bool, float]: """ Soft veto replacing: `vetoed = eta_instant < veto_threshold` P(veto | η) = σ((θ − η) / τ) [inverted: low eta → high veto prob] Returns (should_veto: bool, veto_confidence: float). The bool is True when P(veto) > 0.5, with graded confidence. """ p_veto = _sigmoid((veto_threshold - eta_instant) / temperature) return p_veto > 0.5, round(p_veto, 4) # ────────────────────────────────────────────────────────────── # EMA + stability # ────────────────────────────────────────────────────────────── @staticmethod def ema_update(current: float, instant: float, alpha: float = 0.3) -> float: """η_{t+1} = α·ηᵢₙₛₜ + (1−α)·ηₜ Lyapunov-stable, contraction rate (1−α).""" return alpha * instant + (1.0 - alpha) * current @staticmethod def ema_convergence_steps(epsilon: float = 0.01, alpha: float = 0.3) -> int: """Minimum steps to guarantee |η − η*| < ε from worst-case start.""" return math.ceil(math.log(epsilon) / math.log(1.0 - alpha)) # ────────────────────────────────────────────────────────────── # Spectral / curvature analysis # ────────────────────────────────────────────────────────────── def spectral_summary(self, scores: List[float]) -> Dict: """ Local curvature of the ethical landscape at the given score point. High |curvature| → near transition zone → small score changes matter a lot. Low |curvature| → near saturation → further pressure has diminishing returns. """ hess = self.hessian_diagonal(scores) names = [d.name for d in self._dims] ranked = sorted(zip(names, hess, scores), key=lambda x: abs(x[1]), reverse=True) midpoints = {d.name: d.midpoint for d in self._dims} return { "curvatures": {name: round(h, 6) for name, h, _ in ranked}, "most_active": ranked[0][0], "near_saturation": [ name for name, h, _ in ranked if abs(h) < 0.01 ], "in_transition_zone": [ name for name, _, s in ranked if abs(s - midpoints[name]) < 0.15 ], } def geodesic_step( self, scores: List[float], target: List[float], t: float, ) -> List[float]: """ Linear geodesic interpolation γ(t) = (1−t)·f + t·f*, t ∈ [0,1]. Valid to first order on the Riemannian manifold (exact for Euclidean case). Use for gradual ethical steering between two score states. """ if not (0.0 <= t <= 1.0): raise ValueError(f"t must be in [0,1], got {t}") return [(1.0 - t) * s + t * tgt for s, tgt in zip(scores, target)] # ================================================================ # Convenience constructors # ================================================================ def make_aegis_field(lambda_: float = 0.5) -> EthicsField: return EthicsField(AEGIS_DIMENSIONS, lambda_=lambda_) def make_sycophancy_field(lambda_: float = 0.6) -> EthicsField: return EthicsField(SYCOPHANCY_DIMENSIONS, lambda_=lambda_)