diff --git a/docs/ops/distributed-prefill-kv-network.md b/docs/ops/distributed-prefill-kv-network.md index de4bb23..76228d0 100644 --- a/docs/ops/distributed-prefill-kv-network.md +++ b/docs/ops/distributed-prefill-kv-network.md @@ -351,6 +351,12 @@ Completed Generator/Critic text is atomically checkpointed with mode `0600` at `--recover-run --recover-log --auto-continue` reconstructs a checkpoint from a complete timestamped run and resumes without using any partial Terminal echo. +Continuous `auto-loop` is enabled by default. After every successful +Generator/Critic turn, the READY state internally queues `/continue`; it does +not wait for Terminal automation. A short boundary window still accepts an +explicit whitelisted command such as `/quit` or `/steer`. Any inference +exception pauses auto-loop and preserves the last complete checkpoint. +`--no-auto-loop` restores manual one-turn-at-a-time operation. Generator output is always streamed in full to Terminal and passed verbatim to the Gemma Critic. Sampling, truncation, summarization, independent chunk scores, and semantic fallback are forbidden. A global Critic score is valid only when diff --git a/scripts/agent_gan_repl.py b/scripts/agent_gan_repl.py index 161f607..d5108f0 100644 --- a/scripts/agent_gan_repl.py +++ b/scripts/agent_gan_repl.py @@ -8,6 +8,7 @@ import json import os import re +import select import signal import sys import threading @@ -481,9 +482,24 @@ def main() -> int: default="~/.kakeya/logs/agent_gan_repl.log", ) parser.add_argument("--auto-continue", action="store_true") + parser.add_argument( + "--no-auto-loop", + action="store_false", + dest="auto_loop", + help="Pause after each successful turn instead of continuing.", + ) + parser.set_defaults(auto_loop=True) + parser.add_argument( + "--auto-loop-boundary-wait-s", + type=float, + default=0.5, + help="Boundary window for an explicit command before auto-continue.", + ) args = parser.parse_args() if args.output_tokens <= 0: raise SystemExit("output-tokens must be > 0") + if args.auto_loop_boundary_wait_s < 0: + raise SystemExit("auto-loop-boundary-wait-s must be >= 0") transcript = TimestampedTee( sys.stdout, Path(args.log_file).expanduser(), @@ -536,12 +552,19 @@ def get_stats(): previous_generator = checkpoint.previous_generator if checkpoint else "" previous_critic = checkpoint.previous_critic if checkpoint else "" phase = ReplPhase.READY if checkpoint else ReplPhase.WAITING_FOR_GOAL - pending_input = "/continue" if args.auto_continue and checkpoint else "" + auto_loop_active = bool(args.auto_loop) + pending_input = ( + "/continue" + if checkpoint and (args.auto_continue or auto_loop_active) + else "" + ) print( "Kakeya Agent GAN REPL ready.\n" "Commands: /new , /continue, /steer , /quit.\n" "Raw text is accepted only as the initial goal; inference output can " "never become implicit steering.\n" + f"Auto-loop is {'enabled' if auto_loop_active else 'disabled'}; " + "successful turns continue automatically and exceptions pause it.\n" "Each turn runs allens Prefill → Primary hot Generator → " "allens Prefill → Primary hot Critic.", flush=True, @@ -560,6 +583,19 @@ def get_stats(): raw_input = pending_input pending_input = "" print(f"\nprompt> {raw_input}", flush=True) + elif auto_loop_active and phase == ReplPhase.READY: + print("\nprompt> ", end="", flush=True) + readable, _, _ = select.select( + [sys.stdin], + [], + [], + args.auto_loop_boundary_wait_s, + ) + if readable: + raw_input = input() + else: + raw_input = "/continue" + print(raw_input, flush=True) else: try: raw_input = input("\nprompt> ") @@ -584,8 +620,11 @@ def get_stats(): ReplCheckpoint(research_goal=research_goal), ) phase = ReplPhase.READY + auto_loop_active = bool(args.auto_loop) print(f"[goal] reset: {research_goal}", flush=True) steering = command.payload if command.action == "steer" else "" + if command.action in {"continue", "steer"} and args.auto_loop: + auto_loop_active = True phase = ReplPhase.RUNNING run_nonce = uuid.uuid4().hex telemetry_state["degraded"] = False @@ -776,6 +815,12 @@ def get_stats(): ), ) phase = ReplPhase.READY + if auto_loop_active: + print( + "[auto-loop] successful turn complete; " + "/continue queued", + flush=True, + ) except Exception as exc: if remote_run: _telemetry_request( @@ -791,6 +836,12 @@ def get_stats(): flush=True, ) phase = ReplPhase.READY + auto_loop_active = False + print( + "[auto-loop-paused] inference exception; checkpoint " + "preserved. Use /continue after remediation.", + flush=True, + ) transcript.log_only("[session-end]") return 0 diff --git a/tests/inference_engine/bridge/test_agent_gan_repl.py b/tests/inference_engine/bridge/test_agent_gan_repl.py index 5c58b78..c86bc14 100644 --- a/tests/inference_engine/bridge/test_agent_gan_repl.py +++ b/tests/inference_engine/bridge/test_agent_gan_repl.py @@ -299,6 +299,21 @@ def test_command_state_machine_requires_explicit_ready_commands(): raise AssertionError(f"expected rejection for {raw!r} in {phase}") +def test_continuous_auto_loop_is_default_and_exception_pauses(): + source = ( + Path(__file__).resolve().parents[3] + / "scripts" + / "agent_gan_repl.py" + ).read_text() + assert 'parser.set_defaults(auto_loop=True)' in source + assert "select.select(" in source + assert 'raw_input = "/continue"' in source + assert '"/continue queued"' in source + assert "auto_loop_active = False" in source + assert "[auto-loop-paused]" in source + assert '"--no-auto-loop"' in source + + def test_checkpoint_round_trip_is_private(tmp_path): path = tmp_path / "state.json" expected = ReplCheckpoint(