#000037 Phase 1.c: kernel_cost/llm_cost split + sweep_weights profile

Per §22 Finding 2: flat capital_cost=1.0 made every utility negative
in the dry-run sim. Split capital_cost into kernel_cost (~0.05 kernel
re-probe) + llm_cost (~1.0 LLM witness fan-out); ControllerBranch
now exposes effective_cost = kernel_cost + llm_cost when either is
positive, falling back to legacy capital_cost when both are zero.
Pre-1.c callers don't migrate. _utility() reads effective_cost so
split-cost and legacy-cost branches with the same total cost produce
byte-identical utility values.

Per §22 Finding 3 (partial): add sweep_weights() profile —
gamma_5f=1.5, lambda_capital_cost=0.25, nu_witness_divergence=0.5.
Sweep work willingly pays capital for falsification discovery and
treats high witness divergence as desirable signal (§13 step 11).
Registered in WEIGHT_PROFILES["sweep"]; folds into governance_policy
selection alongside safe / conservative / exploratory.

Tests: 6 new in tests/test_prometheus.py — effective_cost split path,
legacy fallback, _utility byte-identity across the two cost shapes,
kernel-only vs LLM-only ranking, sweep-vs-safe divergence on a
marginal branch (DEFERRED under safe, ACCEPT under sweep), and
WEIGHT_PROFILES registry now pins all four profiles.
This commit is contained in:
russell@unturf.com 2026-05-10 18:29:37 -04:00
parent 8da29fde69
commit 4b85a0af25
No known key found for this signature in database
2 changed files with 210 additions and 8 deletions

View file

@ -133,19 +133,44 @@ class ControllerBranch:
(even bet) so callers that haven't measured payoff don't need
to set it. Tests exercise the ``b_i <= 0`` safety guard via
this field.
**Cost-class split (Phase 1.c)** ``kernel_cost`` and
``llm_cost`` decompose what was previously a single
``capital_cost`` field. Per dry-run Finding #2, real sweep work
spans an order-of-magnitude cost spread (kernel-only re-probe
0.02-0.05, full LLM witness 1.0). Splitting them lets the
controller's utility formula reflect what the witness path
actually spends. Back-compat: when both new fields are 0 the
controller falls back to the legacy ``capital_cost``; existing
callers don't have to migrate. Tests pin both paths.
"""
branch_id: str
deltas: BatteryDeltas
witness_divergence: float
capital_cost: float
regression_penalty: float
security_risk: float
memory_invalidation: float
capital_cost: float = 0.0
regression_penalty: float = 0.0
security_risk: float = 0.0
memory_invalidation: float = 0.0
selfmodel_calibration_gain: float = 0.0
warrant_promotion_gain: float = 0.0
hard_vetoes: tuple[str, ...] = ()
payoff_b: float = 1.0
# Phase 1.c cost-class split. Defaults to 0 → back-compat
# mode (use legacy ``capital_cost``).
kernel_cost: float = 0.0
llm_cost: float = 0.0
@property
def effective_cost(self) -> float:
"""The cost value the controller spends in the utility formula.
Returns ``kernel_cost + llm_cost`` when either is positive;
otherwise returns the legacy ``capital_cost`` (back-compat
path for pre-Phase-1.c callers).
"""
split = self.kernel_cost + self.llm_cost
return split if split > 0.0 else self.capital_cost
@dataclass(frozen=True)
@ -317,10 +342,30 @@ def exploratory_weights() -> ControllerWeights:
)
def sweep_weights() -> ControllerWeights:
"""Sleep-sweep profile (Phase 1.c — dry-run Finding #3).
Tuned for Phase 3 sleep-sweep economics: sweep work deliberately
accepts capital cost in exchange for falsification discovery, so
``lambda_capital_cost`` drops to 0.25 (vs safe-default 1.0) and
``gamma_5f`` bumps to 1.5 (vs 1.25) to reward falsification-
rate improvement. Witness divergence weight drops to 0.5 (vs
1.5) because high divergence is *desirable* signal during sweep
it's what surfaces fixture candidates (§13 step 11).
"""
return replace(
ControllerWeights(),
gamma_5f=1.5,
lambda_capital_cost=0.25,
nu_witness_divergence=0.5,
)
WEIGHT_PROFILES: dict[str, ControllerWeights] = {
"safe": safe_weights(),
"conservative": conservative_weights(),
"exploratory": exploratory_weights(),
"sweep": sweep_weights(),
}
@ -371,7 +416,12 @@ def phase_1_trigger_check(
def _utility(branch: ControllerBranch, w: ControllerWeights) -> float:
"""§5 scalar utility ``U_i`` — all 11 weighted terms."""
"""§5 scalar utility ``U_i`` — all 11 weighted terms.
Uses :attr:`ControllerBranch.effective_cost` so the Phase 1.c
kernel/llm split and the legacy capital_cost path produce
byte-identical utility values when only one path is populated.
"""
d = branch.deltas
return (
w.alpha_5s * d.delta_5s
@ -380,7 +430,7 @@ def _utility(branch: ControllerBranch, w: ControllerWeights) -> float:
+ w.rho_5r * d.delta_5r
+ w.sigma_selfmodel_calibration * branch.selfmodel_calibration_gain
+ w.tau_warrant_promotion * branch.warrant_promotion_gain
- w.lambda_capital_cost * branch.capital_cost
- w.lambda_capital_cost * branch.effective_cost
- w.mu_regression_penalty * branch.regression_penalty
- w.nu_witness_divergence * branch.witness_divergence
- w.xi_security_risk * branch.security_risk
@ -887,6 +937,7 @@ __all__ = [
"safe_weights",
"conservative_weights",
"exploratory_weights",
"sweep_weights",
# Entry points
"controller_decide",
"phase_1_trigger_check",

View file

@ -555,11 +555,17 @@ def test_idempotent_byte_identical_output():
def test_weight_profiles_registry_complete():
"""All three named profiles present and structurally distinct."""
assert set(WEIGHT_PROFILES) == {"safe", "conservative", "exploratory"}
"""All four named profiles present and structurally distinct."""
assert set(WEIGHT_PROFILES) == {
"safe",
"conservative",
"exploratory",
"sweep",
}
safe = WEIGHT_PROFILES["safe"]
cons = WEIGHT_PROFILES["conservative"]
expl = WEIGHT_PROFILES["exploratory"]
swp = WEIGHT_PROFILES["sweep"]
# §15.2 deltas
assert cons.gamma_5f == 1.0
assert cons.lambda_capital_cost == 1.5
@ -570,6 +576,10 @@ def test_weight_profiles_registry_complete():
assert expl.nu_witness_divergence == 0.75
assert expl.lambda_capital_cost == 0.5
assert expl.eta_softmax_temperature == 1.5
# Sweep profile deltas (Phase 1.c — dry-run Finding #3)
assert swp.gamma_5f == 1.5
assert swp.lambda_capital_cost == 0.25
assert swp.nu_witness_divergence == 0.5
# Safe baseline
assert safe.gamma_5f == 1.25
@ -802,3 +812,144 @@ def test_module_level_constants_match_default_weights():
assert w.h_low == H_LOW
assert w.h_high == H_HIGH
assert w.kappa_memory == KAPPA_MEMORY
# ---------------------------------------------------------------------
# Phase 1.c — kernel_cost / llm_cost split on ControllerBranch
# ---------------------------------------------------------------------
def test_effective_cost_uses_split_when_provided():
"""When ``kernel_cost`` or ``llm_cost`` > 0, ``effective_cost``
returns their sum and IGNORES the legacy ``capital_cost`` field.
This is the Phase 1.c migration path."""
b = _b("a", capital_cost=1.0)
b_split = ControllerBranch(
branch_id="b",
deltas=BatteryDeltas(0, 0, 0, 0),
witness_divergence=0.0,
capital_cost=999.0, # SHOULD BE IGNORED
kernel_cost=0.05,
llm_cost=0.3,
)
# Legacy branch uses capital_cost directly:
assert b.effective_cost == 1.0
# Split branch sums kernel + llm, ignores legacy:
assert b_split.effective_cost == 0.35
def test_effective_cost_falls_back_when_split_zero():
"""When both new fields are 0, ``effective_cost`` reads
``capital_cost`` preserving pre-Phase-1.c behavior."""
b = ControllerBranch(
branch_id="legacy",
deltas=BatteryDeltas(0, 0, 0, 0),
witness_divergence=0.0,
capital_cost=0.42,
# kernel_cost + llm_cost both default to 0.0
)
assert b.effective_cost == 0.42
def test_utility_uses_effective_cost():
"""The utility formula reads effective_cost so a split-cost
branch and a legacy-cost branch with the same TOTAL cost produce
byte-identical utility values."""
legacy = ControllerBranch(
branch_id="legacy",
deltas=BatteryDeltas(0.1, 0.1, 0.1, 0.1),
witness_divergence=0.0,
capital_cost=0.5,
)
split = ControllerBranch(
branch_id="split",
deltas=BatteryDeltas(0.1, 0.1, 0.1, 0.1),
witness_divergence=0.0,
capital_cost=0.0,
kernel_cost=0.1,
llm_cost=0.4,
)
w = safe_weights()
from arborist.substrate.prometheus import _utility
assert _utility(legacy, w) == _utility(split, w)
def test_kernel_only_branch_cheaper_than_llm_branch():
"""Two branches with identical signal but different cost class
(kernel-only vs LLM-witness) produce different utilities,
favoring the kernel-only path."""
kernel_only = ControllerBranch(
branch_id="kernel",
deltas=BatteryDeltas(0, 0, 0.05, 0),
witness_divergence=0.0,
kernel_cost=0.05,
llm_cost=0.0,
payoff_b=10.0,
)
llm_only = ControllerBranch(
branch_id="llm",
deltas=BatteryDeltas(0, 0, 0.05, 0),
witness_divergence=0.0,
kernel_cost=0.0,
llm_cost=1.0,
payoff_b=10.0,
)
from arborist.substrate.prometheus import _utility
w = safe_weights()
assert _utility(kernel_only, w) > _utility(llm_only, w)
def test_split_cost_branches_route_through_controller():
"""End-to-end: a split-cost branch can be fed to controller_decide
and the decision uses effective_cost in the utility math."""
branches = (
ControllerBranch(
branch_id="kernel",
deltas=BatteryDeltas(0, 0, 0.10, 0),
witness_divergence=0.0,
kernel_cost=0.05,
llm_cost=0.0,
payoff_b=10.0,
),
ControllerBranch(
branch_id="llm",
deltas=BatteryDeltas(0, 0, 0.05, 0),
witness_divergence=0.0,
kernel_cost=0.0,
llm_cost=1.0,
payoff_b=1.0,
),
)
d = controller_decide(_inp(branches))
# The kernel branch is unambiguously the winner under safe
# weights: higher delta_5f, lower cost.
assert d.selected_branch_id == "kernel"
def test_sweep_profile_accepts_what_safe_profile_defers():
"""The Phase-1.c sweep profile drops lambda_capital_cost from
1.0 0.25 so the sweep is willing to pay capital for
falsification discovery. A marginal-cost branch that DEFERREDs
under safe weights ACCEPTs under sweep weights."""
from arborist.substrate.prometheus import sweep_weights
# Build a branch whose utility under safe weights is barely
# negative (DEFERRED via Kelly's p_i > 0.5 unmet); under sweep
# weights its utility flips positive.
# safe: U = 1.25 * 0.10 - 1.0 * 0.30 = -0.175 (< 0)
# sweep: U = 1.50 * 0.10 - 0.25 * 0.30 = +0.075 (> 0)
branches = (
ControllerBranch(
branch_id="marginal",
deltas=BatteryDeltas(0, 0, 0.10, 0),
witness_divergence=0.0,
capital_cost=0.30,
payoff_b=10.0,
),
)
safe_d = controller_decide(_inp(branches, weights=safe_weights()))
sweep_d = controller_decide(_inp(branches, weights=sweep_weights()))
# Safe-profile decision rejects the marginal branch.
assert safe_d.label == "REJECT"
# Sweep-profile decision accepts it.
assert sweep_d.label in ("ACCEPT", "MARGINAL")