diff --git a/autoresearch/prefill/prepare.py b/autoresearch/prefill/prepare.py index b273d60..90427d4 100644 --- a/autoresearch/prefill/prepare.py +++ b/autoresearch/prefill/prepare.py @@ -46,7 +46,7 @@ def evaluate(report: dict, candidate) -> dict: ), "recursive_protocol": ( critic.get("critic_protocol") - == "recursive_proof_decomposition_v2" + == "goal_anchored_recursive_gan_v3" ), "no_fallback": int(delta.get("fallbacks", 0)) == 0, "no_job_failure": int(delta.get("remote_job_failures", 0)) == 0, diff --git a/autoresearch/prefill/program.md b/autoresearch/prefill/program.md index 82e10bd..55c2a6f 100644 --- a/autoresearch/prefill/program.md +++ b/autoresearch/prefill/program.md @@ -17,7 +17,7 @@ Minimize `metric_cold_critic_prefill_s`. Lower is better. - Every compute segment must remain at or below 300 seconds. - Critic must receive the complete Generator response. - `critic_omitted_tokens` must equal zero. -- Protocol must be `recursive_proof_decomposition_v2`. +- Protocol must be `goal_anchored_recursive_gan_v3`. - Snapshot mode must remain `final_only`. - No fallback, local Primary Prefill, failed remote job, sampling, summary, or semantic simplification is allowed. diff --git a/docs/ops/distributed-prefill-kv-network.md b/docs/ops/distributed-prefill-kv-network.md index 61b4287..87b499d 100644 --- a/docs/ops/distributed-prefill-kv-network.md +++ b/docs/ops/distributed-prefill-kv-network.md @@ -349,13 +349,20 @@ and semantic fallback are forbidden. A global Critic score is valid only when `critic_omitted_tokens=0`. Long Prefill operations emit a heartbeat every 30 seconds; on the 16GB allens worker, full-context Critic Prefill may take 15–25 minutes. -The Critic uses `recursive_proof_decomposition_v2`: numeric scores and blanket +The interactive Critic uses `goal_anchored_recursive_gan_v3`: numeric scores and blanket approval are forbidden. It ignores prizes, money, prestige, style, and other proof-irrelevant facts. It attacks the central stopping claim, recursively decomposes it into proof obligations, records arguments/counterarguments and dependencies for every node, and loops until every leaf is either explicitly derived or a precisely stated open lemma. It then reports the smallest unresolved frontier and next adversarial step. +The first substantive REPL input becomes an immutable research goal. `/continue` +feeds the complete previous Generator and Critic outputs into the next +Generator turn; `/new ` is the only way to switch topics. Inputs beginning +with `generator>`, `critic>`, `prompt>`, `[metrics]`, `[allens]`, errors, or +tracebacks are rejected so Terminal output cannot contaminate the research +objective. Every Critic turn emits an explicit `Goal Alignment` decision and +restores the anchored proof frontier when drift is detected. The worker reserves estimated final-snapshot capacity before model compute, prevents adaptive shrink from consuming active reservations, then atomically publishes and leases the final snapshot before adding optional intermediate diff --git a/scripts/agent_gan_inference_demo.py b/scripts/agent_gan_inference_demo.py index b4f19df..d370223 100644 --- a/scripts/agent_gan_inference_demo.py +++ b/scripts/agent_gan_inference_demo.py @@ -40,14 +40,19 @@ def _output_metadata(text: str) -> dict: } -def build_critic_context(tokenizer, text: str) -> tuple[str, dict]: +def build_critic_context( + tokenizer, + text: str, + *, + protocol: str = "recursive_proof_decomposition_v2", +) -> tuple[str, dict]: full_ids = tokenizer.encode(text, add_special_tokens=False) return text, { "generator_full_tokens": len(full_ids), "critic_context_tokens": len(full_ids), "critic_omitted_tokens": 0, "review_scope": "full", - "critic_protocol": "recursive_proof_decomposition_v2", + "critic_protocol": protocol, } diff --git a/scripts/agent_gan_repl.py b/scripts/agent_gan_repl.py index cb03f65..82a1ffd 100644 --- a/scripts/agent_gan_repl.py +++ b/scripts/agent_gan_repl.py @@ -5,6 +5,7 @@ import argparse import hashlib import json +import re import signal import threading import time @@ -46,7 +47,37 @@ def _telemetry_request(url: str, **kwargs): return None -def build_generator_messages(prompt: str) -> list[dict[str, str]]: +_RUNTIME_ARTIFACT = re.compile( + r"^\s*(?:generator>|critic>|prompt>|\[(?:metrics|allens|error|" + r"telemetry-warning|protected|supervisor)\]|Traceback\b)", + re.IGNORECASE, +) + + +def is_runtime_artifact_prompt(text: str) -> bool: + lines = [line for line in text.splitlines() if line.strip()] + return bool(lines) and bool(_RUNTIME_ARTIFACT.match(lines[0])) + + +def build_generator_messages( + goal: str, + *, + steering: str = "", + previous_generator: str = "", + previous_critic: str = "", +) -> list[dict[str, str]]: + feedback = "" + if previous_generator or previous_critic: + feedback = ( + "\n\nComplete previous Generator response:\n" + f"{previous_generator}\n\nComplete previous Critic correction:\n" + f"{previous_critic}\n\nApply the Critic's Next Adversarial Step " + "while remaining anchored to the immutable goal." + ) + steering_text = ( + f"\n\nCurrent human steering (subordinate to the goal):\n{steering}" + if steering else "" + ) return [ { "role": "system", @@ -59,14 +90,21 @@ def build_generator_messages(prompt: str) -> list[dict[str, str]]: "Distinguish unknown from impossible." ), }, - {"role": "user", "content": prompt}, + { + "role": "user", + "content": ( + f"IMMUTABLE RESEARCH GOAL:\n{goal}" + f"{feedback}{steering_text}" + ), + }, ] def build_critic_messages( - prompt: str, + goal: str, generator_response: str, *, + steering: str = "", stop_reason: str, complete: bool, ) -> list[dict[str, str]]: @@ -94,12 +132,17 @@ def build_critic_messages( "Leaf Obligation Ledger; Smallest Unresolved Frontier; Next " "Adversarial Step. Do not sample, summarize, simplify, or use a " "fallback review." + " Begin with `Goal Alignment: ALIGNED` or `Goal Alignment: " + "DRIFTED`. If drifted, discard the off-topic branch and restore " + "the proof-obligation frontier for the immutable goal." ), }, { "role": "user", "content": ( - f"Task:\n{prompt}\n\nComplete response:\n{generator_response}\n\n" + f"IMMUTABLE RESEARCH GOAL:\n{goal}\n\n" + f"Current steering:\n{steering or '(none)'}\n\n" + f"Complete response:\n{generator_response}\n\n" f"Completion: {stop_reason}; complete={complete}" ), }, @@ -257,11 +300,16 @@ def get_stats(): return stats print( - "Kakeya Agent GAN REPL ready. Type a prompt; /quit exits.\n" + "Kakeya Agent GAN REPL ready. First prompt sets the immutable goal.\n" + "Use /continue to apply Critic feedback, /new to reset, " + "and /quit to exit.\n" "Each turn runs allens Prefill → Primary hot Generator → " "allens Prefill → Primary hot Critic.", flush=True, ) + research_goal = "" + previous_generator = "" + previous_critic = "" with Client(args.address) as client: while True: try: @@ -274,6 +322,33 @@ def get_stats(): if prompt.lower() in {"/quit", "/exit"}: print("[bye]") break + if prompt.lower().startswith("/new"): + new_goal = prompt[4:].strip() + research_goal = new_goal + previous_generator = "" + previous_critic = "" + if not research_goal: + print("[goal] cleared; enter a new research goal", flush=True) + continue + prompt = research_goal + print(f"[goal] reset: {research_goal}", flush=True) + elif prompt.lower() == "/continue": + if not research_goal: + print("[goal-error] no active research goal", flush=True) + continue + prompt = "" + elif is_runtime_artifact_prompt(prompt): + print( + "[input-rejected] runtime output cannot become a research " + "prompt; use /continue or /new ", + flush=True, + ) + continue + elif not research_goal: + research_goal = prompt + print(f"[goal] anchored: {research_goal}", flush=True) + prompt = "" + steering = prompt run_nonce = uuid.uuid4().hex telemetry_state["degraded"] = False run = _telemetry_request( @@ -288,13 +363,22 @@ def get_stats(): "agents": ["generator", "critic"], "rounds": 1, "output_tokens": args.output_tokens, + "goal_anchor": hashlib.sha256( + research_goal.encode(), + ).hexdigest(), + "feedback_applied": bool(previous_critic), }, }, ) remote_run = run is not None run_id = run["id"] if remote_run else f"local_{run_nonce[:16]}" try: - generator_messages = build_generator_messages(prompt) + generator_messages = build_generator_messages( + research_goal, + steering=steering, + previous_generator=previous_generator, + previous_critic=previous_critic, + ) generator_ids = tokenizer.apply_chat_template( generator_messages, add_generation_prompt=True, @@ -348,6 +432,7 @@ def get_stats(): critic_context, context_metrics = build_critic_context( tokenizer, generator_text, + protocol="goal_anchored_recursive_gan_v3", ) if ( critic_context != generator_text @@ -356,8 +441,9 @@ def get_stats(): ): raise RuntimeError("Critic full-context invariant violated") critic_messages = build_critic_messages( - prompt, + research_goal, critic_context, + steering=steering, stop_reason=generator_actual["stop_reason"], complete=generator_actual["complete"], ) @@ -400,6 +486,8 @@ def get_stats(): ) if not critic_stage["ok"] and not telemetry_state["degraded"]: raise _gate_failure("Critic", critic_warm, critic_actual) + previous_generator = generator_text + previous_critic = critic_text completed = None if remote_run: completed = _telemetry_request( diff --git a/tests/inference_engine/bench/test_prefill_autoresearch.py b/tests/inference_engine/bench/test_prefill_autoresearch.py index 3bece3d..4bef89c 100644 --- a/tests/inference_engine/bench/test_prefill_autoresearch.py +++ b/tests/inference_engine/bench/test_prefill_autoresearch.py @@ -18,7 +18,7 @@ def _report(**overrides): "generator_full_tokens": 900, "critic_context_tokens": 900, "critic_omitted_tokens": 0, - "critic_protocol": "recursive_proof_decomposition_v2", + "critic_protocol": "goal_anchored_recursive_gan_v3", "delta": {"fallbacks": 0, "remote_job_failures": 0}, } stage.update(overrides) diff --git a/tests/inference_engine/bridge/test_agent_gan_repl.py b/tests/inference_engine/bridge/test_agent_gan_repl.py index 896429c..2034eb5 100644 --- a/tests/inference_engine/bridge/test_agent_gan_repl.py +++ b/tests/inference_engine/bridge/test_agent_gan_repl.py @@ -11,6 +11,7 @@ build_critic_messages, build_generator_messages, install_signal_protection, + is_runtime_artifact_prompt, ) @@ -149,13 +150,13 @@ def test_stage_includes_full_context_metrics(): "critic_context_tokens": 100, "critic_omitted_tokens": 0, "review_scope": "full", - "critic_protocol": "recursive_proof_decomposition_v2", + "critic_protocol": "goal_anchored_recursive_gan_v3", }, ) assert stage["critic_context_tokens"] == 100 assert stage["critic_omitted_tokens"] == 0 assert stage["review_scope"] == "full" - assert stage["critic_protocol"] == "recursive_proof_decomposition_v2" + assert stage["critic_protocol"] == "goal_anchored_recursive_gan_v3" def test_telemetry_timeout_warns_without_stopping_inference( @@ -185,17 +186,24 @@ def test_gate_failure_exposes_reuse_counters(): def test_interactive_prompts_are_deterministic_for_kv_reuse(): - generator_a = build_generator_messages("prove RH") - generator_b = build_generator_messages("prove RH") + kwargs = { + "steering": "continue the zero-free-region branch", + "previous_generator": "previous complete argument", + "previous_critic": "previous complete correction", + } + generator_a = build_generator_messages("prove RH", **kwargs) + generator_b = build_generator_messages("prove RH", **kwargs) critic_a = build_critic_messages( "prove RH", "complete generator response", + steering=kwargs["steering"], stop_reason="eos", complete=True, ) critic_b = build_critic_messages( "prove RH", "complete generator response", + steering=kwargs["steering"], stop_reason="eos", complete=True, ) @@ -203,6 +211,10 @@ def test_interactive_prompts_are_deterministic_for_kv_reuse(): assert critic_a == critic_b combined = repr(generator_a + critic_a) assert "Internal run" not in combined + assert "IMMUTABLE RESEARCH GOAL" in combined + assert "previous complete correction" in combined + assert "Goal Alignment: ALIGNED" in combined + assert "Goal Alignment: DRIFTED" in combined assert "open problem" in combined assert "recursive adversarial proof analyst" in combined assert "Never output a numeric score" in combined @@ -212,3 +224,15 @@ def test_interactive_prompts_are_deterministic_for_kv_reuse(): assert "Ignore prizes, money, prestige" in combined assert "smallest unresolved frontier" in combined assert "sample, summarize, simplify" in combined + + +def test_runtime_output_cannot_replace_research_goal(): + for text in ( + "critic> ### Central Claim", + "[metrics] KV hit=100%", + "[allens] Critic Prefill: 30s", + "prompt> ", + "Traceback (most recent call last):", + ): + assert is_runtime_artifact_prompt(text) + assert not is_runtime_artifact_prompt("证明黎曼猜想")