diff --git a/benchmark/README.md b/benchmark/README.md index 15fc76cd7c..f4b756fc80 100644 --- a/benchmark/README.md +++ b/benchmark/README.md @@ -806,6 +806,20 @@ through an authorized controller read route. The destination file must be new, outside the artifact and native bank, and installed at the Host's existing phase-file route before starting its ordinary entry command. +Planning input no longer projects the Host's deadline, remaining time or +execution reserve to the model. The original task-planning packet is preserved; +no replacement Host runtime guidance is added to planning or execution wakes. +The Host still enforces its finite budget and retains clocks in runtime state +and receipts. Task-defined time constraints and evaluation cooldowns are unchanged. + +This is a planning prompt default change. Execution inputs, scoring, acceptance, +TurnEnvelope and terminal authority are unchanged. Old session archives may still +contain historical deadline messages: this change does not rewrite or neutralize +those messages. An authorized continuation must address stale context at its +restore boundary, and a terminal Goal still requires explicit resume. This PR +prevents new planning-clock injection; it does not qualify old-archive recovery +or claim improved model behavior or scores. + The sealed deadline and native capture timestamp determine the remainder; the receiver recomputes it. A point with two hours remaining keeps that duration after an eight-hour offline gap. It does not reuse the old absolute epoch or diff --git a/benchmark/runtime/planning.py b/benchmark/runtime/planning.py index 5e4ffb2ca3..294838e2df 100644 --- a/benchmark/runtime/planning.py +++ b/benchmark/runtime/planning.py @@ -5,40 +5,21 @@ import json import math import subprocess -import time from pathlib import Path -def render_planning_prompt(packet: dict, env: dict[str, str], *, execution_reserve_seconds: int) -> str: - """Project caller-owned clocks without changing the product planning packet.""" +def render_planning_prompt(packet: dict, env: dict[str, str]) -> str: + """Preserve the task packet without projecting the Host's execution clock.""" if "LOOPX_PLANNING_TIMEOUT_SEC" in env: raise ValueError( "Remove LOOPX_PLANNING_TIMEOUT_SEC; planning uses LOOPX_PHASE_DEADLINE_EPOCH" ) - window = [] key = "LOOPX_PHASE_DEADLINE_EPOCH" if key in env: value = float(env[key]) if not math.isfinite(value) or value <= 0: raise ValueError(f"{key} must be finite and positive") - observed = time.time() - window.extend([ - f"Absolute phase deadline: {value} Unix UTC seconds.", - f"Remaining phase time at observation {observed}: {max(0, value - observed):.1f} seconds.", - f"Existing execution reserve: {execution_reserve_seconds} seconds. " - f"Observed work window before that reserve: {max(0, value - observed - execution_reserve_seconds):.1f} seconds.", - "This shared window includes planning and subsequent execution; " - "runtime startup and settlement reserves also consume it.", - ]) - prefix = "$loopx\n\n" - if window: - prefix += "Host execution window:\n" + "\n".join(window) + "\n" - prefix += ( - "Use these observations to size the plan and its validation. They age " - "as work proceeds; the runtime still enforces its original limits. " - "They grant no extra time or authority and do not relax task acceptance.\n\n" - ) - return prefix + "Host-supplied planning checkpoint:\n" + json.dumps(packet, ensure_ascii=False) + return "$loopx\n\nHost-supplied planning checkpoint:\n" + json.dumps(packet, ensure_ascii=False) def task_plan_packet(env: dict[str, str], cli: list[str]) -> dict: diff --git a/benchmark/runtime/worker.py b/benchmark/runtime/worker.py index c3f12d2055..4a7cc3a9fc 100644 --- a/benchmark/runtime/worker.py +++ b/benchmark/runtime/worker.py @@ -412,8 +412,7 @@ def run_once(env: dict[str, str]) -> dict: from benchmark.runtime.planning import render_planning_prompt, task_plan_packet planning_before = task_plan_packet(env, loopx_command(env)) - body = render_planning_prompt(planning_before, env, - execution_reserve_seconds=PHASE_WORK_RESERVE_SECONDS) + body = render_planning_prompt(planning_before, env) (wake / "planning-input.json").write_text(body, encoding="utf-8") (wake / "planning-schema.json").write_text(json.dumps(planning_before["result_schema"])) elif execution.mode in {"heartbeat", "loopx-goal"}: diff --git a/benchmark/tests/test_native_checkpoint_handoff.py b/benchmark/tests/test_native_checkpoint_handoff.py index c56abcc2af..edb585ba28 100644 --- a/benchmark/tests/test_native_checkpoint_handoff.py +++ b/benchmark/tests/test_native_checkpoint_handoff.py @@ -700,11 +700,20 @@ def response(payload, index, message): LOOPX_CODEX_HOME=str(tmp_path / "worker-home"), LOOPX_SHARED_SKILLS=str(skills), MODEL_NAME="fixture", REASONING_EFFORT="high", OPENAI_BASE_URL=base_url, OPENAI_API_KEY="synthetic-local-only", LOOPX_CODEX_TURN_TIMEOUT_SEC="60") + deadline = str(time.time() + 600) + env["LOOPX_PHASE_DEADLINE_EPOCH"] = deadline if capture != "off": env["LOOPX_CHECKPOINT_REQUEST"] = str(request) result = run_once(env) assert result["ok"] and not result["timed_out"], result assert _heartbeat_receipt_count(runtime, TURN_ID) == 1 + # Checkpoint preloading keeps the existing heartbeat contract without + # injecting a Host countdown or runtime guidance. + worker_input = next(t for t in texts(requests[1]) if "quota should-run" in t and "```sh" in t) + assert deadline not in worker_input + assert "Absolute phase deadline" not in worker_input + assert "Current wake execution limit" not in worker_input + assert "The Host enforces runtime limits externally" not in worker_input if capture == "off": assert "checkpoint_handoffs" not in result assert "--decision-handoff-socket" not in guards[0] diff --git a/benchmark/tests/test_resume_sessions.py b/benchmark/tests/test_resume_sessions.py index c540b2ffb5..66e8d78aab 100644 --- a/benchmark/tests/test_resume_sessions.py +++ b/benchmark/tests/test_resume_sessions.py @@ -300,9 +300,9 @@ def log_message(self, *args): assert len(requests) == 2 history = json.dumps(requests[1]["input"]) assert "planning-input" in history and "planning-ack" in history - assert "Host execution window" in history - assert "Existing execution reserve: 160 seconds" in history - assert env["LOOPX_PHASE_DEADLINE_EPOCH"] in history + assert "Host execution window" not in history + assert "Existing execution reserve" not in history + assert env["LOOPX_PHASE_DEADLINE_EPOCH"] not in history assert "Current Todo input." in history assert len(list((tmp_path / "logs/sessions").rglob("*.jsonl"))) == 1 finally: diff --git a/benchmark/tests/test_task_entry.py b/benchmark/tests/test_task_entry.py index 8a72e5887d..592a7327f0 100644 --- a/benchmark/tests/test_task_entry.py +++ b/benchmark/tests/test_task_entry.py @@ -198,7 +198,7 @@ def test_prose_or_fabricated_todo_does_not_qualify_planning(planning_env): ) -def test_planner_receives_existing_phase_clock_without_changing_task_identity(planning_env): +def test_planner_keeps_host_clock_out_of_model_input_and_preserves_task_identity(planning_env): from benchmark.runtime.planning import task_plan_packet from benchmark.runtime.worker import loopx_command @@ -208,9 +208,9 @@ def test_planner_receives_existing_phase_clock_without_changing_task_identity(pl receipt = run_once(env) assert receipt["ok"] and receipt["planning"]["state_readback_verified"] body = next(Path(env["LOOPX_WAKE_LOG_DIR"]).glob("*/planning-input.json")).read_text() - assert deadline in body - assert "Host execution window" in body - assert "Existing execution reserve: 160 seconds" in body + assert deadline not in body + assert "Host execution window" not in body + assert "Existing execution reserve" not in body packet = json.loads(body.split("Host-supplied planning checkpoint:\n", 1)[1]) assert packet == before assert receipt["planning"]["input_digest"] == before["input_digest"] @@ -218,24 +218,17 @@ def test_planner_receives_existing_phase_clock_without_changing_task_identity(pl assert not list(Path(env["LOOPX_PROJECT"]).iterdir()) -def test_planning_window_uses_one_phase_deadline_and_preserves_packet(monkeypatch): - from benchmark.runtime import planning +def test_planning_input_is_clock_independent_but_preserves_task_time_constraints(): + from benchmark.runtime.planning import render_planning_prompt - monkeypatch.setattr(planning, "time", SimpleNamespace(time=lambda: 1000.0)) - packet = {"input_digest": "unchanged", "text": "保留原始任务"} - legacy = "$loopx\n\nHost-supplied planning checkpoint:\n" + json.dumps(packet, ensure_ascii=False) - assert planning.render_planning_prompt(packet, {}, execution_reserve_seconds=160) == legacy - env = {"LOOPX_PHASE_DEADLINE_EPOCH": "1600"} - body = planning.render_planning_prompt(packet, env, execution_reserve_seconds=160) - assert "Absolute phase deadline: 1600.0 Unix UTC seconds" in body - assert "Remaining phase time at observation 1000.0: 600.0 seconds" in body - assert "Observed work window before that reserve: 440.0 seconds" in body - assert "Planning invocation limit" not in body + packet = {"input_digest": "unchanged", "text": "Task constraint: inference under 30 seconds."} + body = render_planning_prompt(packet, {}) + for deadline in ["100", "1600", "9999999999"]: + env = {"LOOPX_PHASE_DEADLINE_EPOCH": deadline} + assert render_planning_prompt(packet, env) == body + assert env == {"LOOPX_PHASE_DEADLINE_EPOCH": deadline} assert json.loads(body.split("Host-supplied planning checkpoint:\n", 1)[1]) == packet - monkeypatch.setattr(planning, "time", SimpleNamespace(time=lambda: 1700.0)) - expired = planning.render_planning_prompt(packet, env, execution_reserve_seconds=160) - assert "Remaining phase time at observation 1700.0: 0.0 seconds" in expired - assert env == {"LOOPX_PHASE_DEADLINE_EPOCH": "1600"} + assert body == "$loopx\n\nHost-supplied planning checkpoint:\n" + json.dumps(packet, ensure_ascii=False) @pytest.mark.parametrize("value", ["nan", "inf", "-inf", "0", "-1", "not-a-clock"]) @@ -244,7 +237,7 @@ def test_planning_window_rejects_invalid_host_clock(value): with pytest.raises(ValueError): render_planning_prompt({"input_digest": "unchanged"}, - {"LOOPX_PHASE_DEADLINE_EPOCH": value}, execution_reserve_seconds=160) + {"LOOPX_PHASE_DEADLINE_EPOCH": value}) def test_retired_planning_limit_fails_before_host_invocation(planning_env): @@ -747,3 +740,39 @@ async def no_pending(**kwargs): assert profile.get("stride_strategy", "off") == (strategy or "off") assert agent._replan_receipt() == {field: value, **({"stride_strategy": strategy} if strategy is not None else {})} + + +@pytest.mark.parametrize("context", ["fresh", "resume"]) +def test_execute_wake_keeps_host_clocks_out_of_fresh_and_resumed_input(planning_env, context): + """Exercise the real worker/heartbeat CLI and capture its model transport input.""" + env = planning_env | {"LOOPX_ITERATION_CONTEXT": context} + assert run_once(env)["ok"] + binary = Path(env["CODEX_BIN"]) + binary.write_text( + f"#!{sys.executable}\n" + "import json, os, pathlib, sys\n" + "pathlib.Path(os.environ['CODEX_HOME'], 'execute-input.txt').write_text(sys.stdin.read())\n" + "print(json.dumps({'type': 'thread.started', 'thread_id': 'planning-fixture-session'}))\n" + ) + before = Path(env["LOOPX_REGISTRY"]).with_name("state.md").read_bytes() + old_deadline = env["LOOPX_PHASE_DEADLINE_EPOCH"] + for offset in [900, 500]: + deadline = str(time.time() + offset) + current = env | {"LOOPX_TASK_STAGE": "execute", + "LOOPX_PHASE_DEADLINE_EPOCH": deadline, + "LOOPX_CODEX_TURN_TIMEOUT_SEC": "120"} + receipt = run_once(current) + assert receipt["ok"] + body = (Path(env["LOOPX_CODEX_HOME"]) / "execute-input.txt").read_text() + assert deadline not in body + assert "Absolute phase deadline" not in body + assert old_deadline not in body + assert "Current wake execution limit" not in body + assert "Existing execution reserve" not in body + assert "The Host enforces runtime limits externally" not in body + assert "heartbeat-prompt" in body or "quota" in body + assert receipt["session"]["action"] == ("resume" if context == "resume" else "start_new") + assert "turn_envelope" not in receipt + assert current["LOOPX_PHASE_DEADLINE_EPOCH"] == deadline + # Runtime clocks remain internal and never change task/completion authority. + assert Path(env["LOOPX_REGISTRY"]).with_name("state.md").read_bytes() == before