diff --git a/autoresearch/prefill/supervisor.py b/autoresearch/prefill/supervisor.py index 0ca0342..afd9eb6 100644 --- a/autoresearch/prefill/supervisor.py +++ b/autoresearch/prefill/supervisor.py @@ -106,6 +106,13 @@ def _select_repair_target(current: dict, ledger: dict) -> str: str(item.get("obligation_id", "")): str(item.get("parent_id", "")) for item in ledger.get("obligations", []) } + cursor = parents.get(current_target, "") + visited = set() + while cursor and cursor not in visited: + if cursor in leaves: + return cursor + visited.add(cursor) + cursor = parents.get(cursor, "") def distance_from_current(obligation_id: str) -> int: distance = 0 @@ -982,6 +989,12 @@ def append_result(path: Path, row: dict) -> None: def run_iteration(args, iteration: int) -> dict: + from scripts.agent_gan_repl import ( + audit_ledger_semantic_duplicates, + load_proof_ledger, + save_proof_ledger, + ) + root = Path(__file__).resolve().parents[2] ar = Path(__file__).resolve().parent candidate_path = ar / "candidate.py" @@ -997,6 +1010,20 @@ def run_iteration(args, iteration: int) -> dict: current = _candidate_snapshot(current_module) previous_candidate = candidate_path.read_bytes() previous_state = _backup(state_path) + ledger_object = load_proof_ledger(ledger_path) + semantic_rejections = ( + audit_ledger_semantic_duplicates(ledger_object) + if ledger_object is not None else [] + ) + if ledger_object is not None and semantic_rejections: + save_proof_ledger(ledger_path, ledger_object) + for obligation_id, ancestor_id, score in semantic_rejections: + print( + "[autoresearch] phase=semantic-retro-reject " + f"id={obligation_id} duplicate_of={ancestor_id} " + f"score={score:.2f}", + flush=True, + ) previous_ledger = _backup(ledger_path) proposed = current diff --git a/tests/inference_engine/bench/test_autoresearch_supervisor.py b/tests/inference_engine/bench/test_autoresearch_supervisor.py index faaf00e..dd42827 100644 --- a/tests/inference_engine/bench/test_autoresearch_supervisor.py +++ b/tests/inference_engine/bench/test_autoresearch_supervisor.py @@ -335,6 +335,36 @@ def test_host_candidate_targets_deepest_current_branch_leaf(): assert candidate["prefill_compute_chunk_tokens"] == 256 +def test_host_candidate_rolls_back_to_nearest_valid_ancestor(): + rejected_target = "RH-C2-duplicate-child" + current = { + **_candidate(), + "target_obligation_id": rejected_target, + } + ledger = {"obligations": [ + { + "obligation_id": "RH-C1", + "statement": "Unrelated leaf.", + "status": "UNRESOLVED", + "parent_id": "", + }, + { + "obligation_id": "RH-C2-gap", + "statement": "Falsify the density-singularity implication.", + "status": "UNRESOLVED", + "parent_id": "", + }, + { + "obligation_id": rejected_target, + "statement": "Renamed density-singularity implication.", + "status": "REJECTED_DUPLICATE", + "parent_id": "RH-C2-gap", + }, + ]} + candidate = build_host_candidate(current, ledger) + assert candidate["target_obligation_id"] == "RH-C2-gap" + + def test_strategy_is_triggered_only_by_events(tmp_path): progress = { "kept": "True", @@ -546,6 +576,9 @@ def test_supervisor_preserves_runtime_and_cache_across_iterations(): / "supervisor.py" ).read_text() body = source[source.index("def run_iteration"):source.index("def main")] + assert body.index("audit_ledger_semantic_duplicates(") < body.index( + "previous_ledger = _backup", + ) assert body.index("check_runtime_health(") < body.index( "strategy_trigger_reason(", )