feat(adr-0024): Phase 1 — wire inner-loop admissibility + determinism proof
Phase 1 of the post-ADR-0024 sequence: wire the inner-loop flag into live cognition paths and prove deterministic-when-wired in the same milestone. Changes: - RuntimeConfig: add inner_loop_admissibility + admissibility_threshold. - ChatRuntime: pass both into generate() on the chat hot path. - CLI: --inner-loop-admissibility / --admissibility-threshold flags. - vocab/manifold.py: document strict `>` tie-break as load-bearing for ADR-0024 rejected_attempts ordering (determinism by construction, not by accident). - tests/test_inner_loop_admissibility.py: three new determinism tests — identical rejected_attempts across 5 runs, identical trace hash across 5 runs (non-empty), and legacy hash equivalence when no rejections occur (flag on/off byte-identical). - tests/test_language_pack_cache.py: fix stale fixture (en-core-cog-070 -> en-core-cog-085 after pack growth). Suite: 995 passed, 0 failed, 2 skipped. Acceptance criteria met: - wired through RuntimeConfig + CLI + ChatRuntime + generate() - deterministic rejected_attempts sequence (verified by repetition) - deterministic trace hash under inner_loop=True - legacy ADR-0023 trace hashes preserved when no rejections - nearest_next determinism is by construction (sequenced iteration + strict > tie-break), now documented Next: Phase 2 — corpus-observation eval on existing v1 corpus with the four-condition matrix (boundary-only, null control, inner-loop t=0.0, inner-loop t>0) and exhaustion_rate + latency metrics.
This commit is contained in:
parent
f0dbe9a57c
commit
7fccf368fb
6 changed files with 110 additions and 1 deletions
|
|
@ -182,6 +182,8 @@ class ChatRuntime:
|
|||
use_salience=config.use_salience,
|
||||
salience_top_k=config.salience_top_k,
|
||||
inhibition_threshold=config.inhibition_threshold,
|
||||
inner_loop_admissibility=config.inner_loop_admissibility,
|
||||
admissibility_threshold=config.admissibility_threshold,
|
||||
)
|
||||
else:
|
||||
resolved_config = config
|
||||
|
|
@ -396,6 +398,8 @@ class ChatRuntime:
|
|||
use_salience=self.config.use_salience,
|
||||
salience_top_k=self.config.salience_top_k,
|
||||
inhibition_threshold=self.config.inhibition_threshold,
|
||||
inner_loop_admissibility=self.config.inner_loop_admissibility,
|
||||
admissibility_threshold=self.config.admissibility_threshold,
|
||||
)
|
||||
|
||||
# --- Articulation fidelity: replace bare S-P-O join with intent-aware surface ---
|
||||
|
|
|
|||
13
core/cli.py
13
core/cli.py
|
|
@ -134,6 +134,8 @@ def _runtime_config_from_args(args: argparse.Namespace):
|
|||
use_salience=not args.no_salience,
|
||||
salience_top_k=args.salience_top_k,
|
||||
inhibition_threshold=args.inhibition_threshold,
|
||||
inner_loop_admissibility=getattr(args, "inner_loop_admissibility", False),
|
||||
admissibility_threshold=getattr(args, "admissibility_threshold", 0.0),
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -684,6 +686,17 @@ def _add_runtime_policy_args(parser: argparse.ArgumentParser) -> None:
|
|||
parser.add_argument("--vault-reproject-interval", type=int, default=20, help="vault null-cone reprojection cadence; default: 20 stores")
|
||||
parser.add_argument("--salience-top-k", type=int, default=16, help="salience candidate budget; default: 16")
|
||||
parser.add_argument("--inhibition-threshold", type=float, default=0.3, help="attention inhibition threshold; default: 0.3")
|
||||
parser.add_argument(
|
||||
"--inner-loop-admissibility",
|
||||
action="store_true",
|
||||
help="enable ADR-0024 per-rotor inner-loop admissibility (re-select on rejection)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--admissibility-threshold",
|
||||
type=float,
|
||||
default=0.0,
|
||||
help="inner-loop admissibility score threshold; default: 0.0",
|
||||
)
|
||||
parser.add_argument("--no-salience", action="store_true", help="disable salience attention and use full-manifold generation")
|
||||
parser.add_argument(
|
||||
"--allow-cross-language-generation",
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ class RuntimeConfig:
|
|||
use_salience: bool = True
|
||||
salience_top_k: int = 16
|
||||
inhibition_threshold: float = 0.3
|
||||
inner_loop_admissibility: bool = False
|
||||
admissibility_threshold: float = 0.0
|
||||
|
||||
|
||||
DEFAULT_CONFIG = RuntimeConfig()
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ from generate.admissibility import (
|
|||
AdmissibilityVerdict,
|
||||
RegionSource,
|
||||
)
|
||||
from generate.result import GenerationResult
|
||||
from generate.stream import generate
|
||||
|
||||
|
||||
|
|
@ -241,5 +242,91 @@ class TestCanonicalOmitsEmptyRejectedAttempts:
|
|||
assert canonical["rejected_attempts"] == [[1, "x", -0.25]]
|
||||
|
||||
|
||||
class TestInnerLoopDeterminism:
|
||||
"""Phase 1 acceptance criterion (ADR-0024 follow-up).
|
||||
|
||||
The inner-loop re-selection introduces a new ordering-sensitive
|
||||
path: candidates excluded by rejection feed back into the next
|
||||
``vocab.nearest`` call. Determinism here relies on:
|
||||
|
||||
* ``vocab.nearest`` using a strict ``>`` tie-break over a
|
||||
sequenced iteration (load-bearing comment in
|
||||
``vocab/manifold.py``);
|
||||
* ``step_exclude`` being used only for set membership, never
|
||||
iterated;
|
||||
* ``rejected_attempts`` being appended in loop order.
|
||||
|
||||
These tests pin determinism *by repetition* — same inputs, same
|
||||
output across N runs, with non-empty rejection sequences in scope.
|
||||
"""
|
||||
|
||||
def _run(self) -> GenerationResult:
|
||||
vocab = _ControllableVocab(
|
||||
words=["seed", "alpha", "beta", "gamma"],
|
||||
preference=[1, 2, 3],
|
||||
versor_signs=[+1.0, -1.0, -1.0, +1.0],
|
||||
)
|
||||
return generate(
|
||||
_initial_state(vocab),
|
||||
vocab,
|
||||
_IdentityPersona(),
|
||||
max_tokens=1,
|
||||
region=_positive_blade_region((1, 2, 3)),
|
||||
inner_loop_admissibility=True,
|
||||
)
|
||||
|
||||
def test_repeated_runs_produce_identical_rejected_attempts(self) -> None:
|
||||
results = [self._run() for _ in range(5)]
|
||||
baseline = results[0].admissibility_trace[0].rejected_attempts
|
||||
# Two rejections precede the admitted selection.
|
||||
assert len(baseline) == 2
|
||||
for result in results[1:]:
|
||||
step = result.admissibility_trace[0]
|
||||
assert step.rejected_attempts == baseline
|
||||
assert step.selected_word == "gamma"
|
||||
|
||||
def test_repeated_runs_produce_identical_trace_hash(self) -> None:
|
||||
from core.cognition.trace import hash_admissibility_trace
|
||||
|
||||
hashes = {
|
||||
hash_admissibility_trace(self._run().admissibility_trace)
|
||||
for _ in range(5)
|
||||
}
|
||||
assert len(hashes) == 1
|
||||
only_hash = next(iter(hashes))
|
||||
assert only_hash != "" # non-empty trace ⇒ non-empty hash
|
||||
|
||||
def test_inner_loop_off_preserves_legacy_trace_hash(self) -> None:
|
||||
"""No rejections ⇒ canonical omits the new key ⇒ hash is byte-
|
||||
identical to what an ADR-0023 turn would have produced."""
|
||||
from core.cognition.trace import hash_admissibility_trace
|
||||
|
||||
vocab_off = _ControllableVocab(
|
||||
words=["seed", "alpha", "beta"],
|
||||
preference=[1, 2],
|
||||
versor_signs=[+1.0, +1.0, +1.0],
|
||||
)
|
||||
result_off = generate(
|
||||
_initial_state(vocab_off),
|
||||
vocab_off,
|
||||
_IdentityPersona(),
|
||||
max_tokens=1,
|
||||
region=_positive_blade_region((1, 2)),
|
||||
inner_loop_admissibility=False,
|
||||
)
|
||||
result_on = generate(
|
||||
_initial_state(vocab_off),
|
||||
vocab_off,
|
||||
_IdentityPersona(),
|
||||
max_tokens=1,
|
||||
region=_positive_blade_region((1, 2)),
|
||||
inner_loop_admissibility=True,
|
||||
)
|
||||
# No rejections in either run ⇒ traces hash identically.
|
||||
h_off = hash_admissibility_trace(result_off.admissibility_trace)
|
||||
h_on = hash_admissibility_trace(result_on.admissibility_trace)
|
||||
assert h_off == h_on
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
pytest.main([__file__, "-v"])
|
||||
|
|
|
|||
|
|
@ -37,4 +37,4 @@ def test_load_pack_entries_returns_new_list_from_cached_tuple() -> None:
|
|||
entries_a.pop()
|
||||
|
||||
assert len(entries_a) == len(entries_b) - 1
|
||||
assert entries_b[-1].entry_id == "en-core-cog-070"
|
||||
assert entries_b[-1].entry_id == "en-core-cog-085"
|
||||
|
|
|
|||
|
|
@ -262,6 +262,9 @@ class VocabManifold:
|
|||
|
||||
best_score = -np.inf
|
||||
best_idx = -1
|
||||
# Strict `>` is load-bearing: on ties, the first candidate in iteration
|
||||
# order wins. ADR-0024 inner-loop re-selection relies on this for
|
||||
# deterministic rejected_attempts ordering across runs.
|
||||
for i in candidates:
|
||||
if i in blocked:
|
||||
continue
|
||||
|
|
|
|||
Loading…
Reference in a new issue