From 2600db453a59769b3f5ce013ffb0b8e34b2c01cf Mon Sep 17 00:00:00 2001 From: song Date: Sat, 17 Oct 2026 11:50:32 +0800 Subject: [PATCH 1/2] fix(benchmark): bind warm preparation readback to one current revision Bracket the actual guard with the exact preparation Todo authority revision. Refuse interleaved completion or transfer, and expose the observed Todo, revision and current replan identity in the existing context receipt. Preserve phase deadlines, normal worker admission and owed first review. Cover real File/SQLite interleavings and the actual native warm resume entry without paid inference; retain the parent proposal's other recovery boundaries. Signed-off-by: song --- benchmark/README.md | 19 +++++++++ benchmark/runtime/sforge_warm_start.py | 21 +++++++++- .../test_warm_continuation_readback.py | 39 ++++++++++++++++++- 3 files changed, 75 insertions(+), 4 deletions(-) diff --git a/benchmark/README.md b/benchmark/README.md index 4c7d286c82..87a33053b2 100644 --- a/benchmark/README.md +++ b/benchmark/README.md @@ -1023,6 +1023,18 @@ readback neither grants execution nor qualifies subsequent research or recapture It applies only to the existing explicitly admitted complete-checkpoint path; ordinary terminal archives are not converted into complete checkpoints. +The receiver brackets the guard with the exact preparation Todo's canonical +authority revision. Completion, transfer or another canonical edit during that +observation rejects readiness; a later required review cannot revive an earlier +open snapshot. The existing context receipt includes `preparation_readback` with +that revision, the selected Todo and current replan identity, so an operator can +inspect what was actually observed instead of relying on a readiness boolean. +This is a bounded observation, not an execution lease; the worker must still use +fresh ordinary admission. A rejected attempt retains its context, cadence-one +preparation and owed review for inspection rather than silently resetting them. +Cross-version partial receivers and actual model/research adoption retain their +separate qualification boundaries. + Warm 准备在既有 Todo 中传达本次已准入的继续请求:历史中的旧 Host 截止和预算收尾 信息属于源阶段,目标阶段遵循当前 Goal 与 guard。题目时限、原生冷却、用户门槛及 停止权限仍有效。准备步骤须读回当前 claimed Todo 和实际 quota 结果,才在既有 @@ -1031,6 +1043,13 @@ Warm 准备在既有 Todo 中传达本次已准入的继续请求:历史中的 执行权,也不认证后续研究或再次捕获,仅作用于显式准入的完整检查点路径。 普通终态资料归档不会因此被升级为完整检查点。 +接收端用准确准备 Todo 的规范权威修订号夹住 guard 读取;期间完成、转交或其他规范 +修改都会拒绝 ready,后来的 required review 不能复活较早的 open 快照。既有上下文 +回执的 `preparation_readback` 返回该修订、当前选中 Todo 与 replan 身份,便于检查 +实际观察依据;它不是执行租约,worker 仍须重新走普通准入。拒绝的接收端保留上下文、 +cadence-one 准备和欠下的复盘供检查,不默默重置;跨版本部分接收端与真实模型/研究采用 +仍需各自资格化。 + To test a new decision from the same pre-replan state, use `--restore-fresh-replan`. The point must contain a verified pending replan packet. The first destination model call starts a fresh native conversation, before diff --git a/benchmark/runtime/sforge_warm_start.py b/benchmark/runtime/sforge_warm_start.py index 96323127ee..fc8bd0afd0 100644 --- a/benchmark/runtime/sforge_warm_start.py +++ b/benchmark/runtime/sforge_warm_start.py @@ -324,8 +324,9 @@ def cli(*args): cli("task-lease", "release", "--goal-id", goal, "--todo-id", todo, "--owner", agent, "--idempotency-key", key, "--expected-version", str(lease["version"])) cli("configure-goal", "--goal-id", goal, "--execution-replan-after-turns", "1", "--execute") - _read_preparation(cli, goal, agent, todo, declaration) + readback = _read_preparation(cli, goal, agent, todo, declaration) return {"session": native, "context": context, "preparation_todo_id": todo, + "preparation_readback": readback, "preparation_actionable": True, "model_calls": 0, "research_continuation_qualified": False} @@ -334,7 +335,8 @@ def cli(*args): def _read_preparation(cli, goal, agent, todo, declaration): """Read current owner facts; a saved plan alone cannot admit continuation.""" _phase_read_timeout(declaration, 60) - preparation = cli("todo", "list", "--goal-id", goal, "--todo-id", todo)["todo"] + before = cli("todo", "list", "--goal-id", goal, "--todo-id", todo) + preparation = before["todo"] if preparation.get("status") != "open" or preparation.get("claimed_by") != agent: raise ValueError("Warm preparation Todo is not open under the destination agent") guard = cli("quota", "should-run", "--goal-id", goal, "--agent-id", agent, @@ -345,7 +347,22 @@ def _read_preparation(cli, goal, agent, todo, declaration): if (guard.get("should_run") is not True or channel.get("delivery_allowed") is not True or not (selected.get("todo_id") == todo or pending_review.get("required") is True)): raise ValueError("Warm continuation has no actionable current preparation or review") + # The guard can legitimately require a review after this Todo completes. + # That review cannot make an older open/owned preparation snapshot current. + # Bracket it with the existing canonical read revision; do not invent a + # ready receipt, retry budget, or permission from imported history. + after = cli("todo", "list", "--goal-id", goal, "--todo-id", todo) + revision = (before.get("authority_read") or {}).get("provider_revision") + current = after["todo"] + if (not isinstance(revision, str) or not revision + or (after.get("authority_read") or {}).get("provider_revision") != revision + or current.get("status") != "open" or current.get("claimed_by") != agent): + raise ValueError("Warm preparation state changed during current readiness readback; inspect this receiver") _phase_read_timeout(declaration, 60) + return {"authority_read": after["authority_read"], "preparation_todo_id": todo, + "selected_todo_id": selected.get("todo_id"), + "current_replan_obligation_id": pending_review.get("obligation_id"), + "current_replan_required": pending_review.get("required") is True} def finish_preparation(env: dict[str, str]) -> dict: diff --git a/tests/control_plane/test_warm_continuation_readback.py b/tests/control_plane/test_warm_continuation_readback.py index 3cf02851d4..eaaceff9d7 100644 --- a/tests/control_plane/test_warm_continuation_readback.py +++ b/tests/control_plane/test_warm_continuation_readback.py @@ -39,8 +39,12 @@ def test_current_preparation_readback_is_idempotent_and_respects_user_gate(tmp_p declaration = {"deadline_epoch": deadline} state = project / "ACTIVE_GOAL_STATE.md" state_before = state.read_bytes() - _read_preparation(call, GOAL, AGENT, todo, declaration) - _read_preparation(call, GOAL, AGENT, todo, declaration) + readback = _read_preparation(call, GOAL, AGENT, todo, declaration) + repeated = _read_preparation(call, GOAL, AGENT, todo, declaration) + assert readback == repeated + assert readback["preparation_todo_id"] == todo + assert readback["authority_read"]["provider_revision"] + assert readback["selected_todo_id"] == todo or readback["current_replan_required"] assert state.read_bytes() == state_before assert declaration["deadline_epoch"] == deadline listed = call("todo", "list", "--goal-id", GOAL, "--todo-id", todo)["todo"] @@ -62,6 +66,37 @@ def test_expired_preparation_does_not_read_or_renew_a_phase(): assert calls == [] and declaration == {"deadline_epoch": 1} +@pytest.mark.parametrize("provider", ["file", "sqlite"]) +@pytest.mark.parametrize("boundary", ["before_guard", "after_guard"]) +@pytest.mark.parametrize("change", ["complete", "transfer"]) +def test_changed_preparation_cannot_borrow_an_unrelated_current_review( + tmp_path, monkeypatch, provider, boundary, change, +): + call, _, _ = _fixture(tmp_path, monkeypatch, provider, future_monitor=False) + todo = call("todo", "add", "--goal-id", GOAL, "--role", "agent", "--claimed-by", AGENT, + "--text", _PREPARATION_TASK, "--task-class", "advancement_task")["todo_id"] + changed = False + + def interleaved(*args): + nonlocal changed + result = call(*args) + trigger = ("todo", "list") if boundary == "before_guard" else ("quota", "should-run") + if not changed and args[:2] == trigger: + changed = True + if change == "complete": + call("todo", "complete", "--goal-id", GOAL, "--agent-id", AGENT, "--todo-id", todo) + else: + call("todo", "update", "--goal-id", GOAL, "--agent-id", AGENT, "--todo-id", todo, + "--claimed-by", "different-agent") + return result + + with pytest.raises(ValueError, match="preparation.*changed|not open"): + _read_preparation(interleaved, GOAL, AGENT, todo, {"deadline_epoch": int(time.time()) + 600}) + assert changed + final = call("todo", "list", "--goal-id", GOAL, "--todo-id", todo)["todo"] + assert final["status"] == "done" if change == "complete" else final["claimed_by"] == "different-agent" + + @pytest.mark.parametrize("todo", [ {"status": "done", "claimed_by": "agent"}, {"status": "open", "claimed_by": "another-agent"}, From 79c00981c72491851034d6f4e1cf5b471e6ac714 Mon Sep 17 00:00:00 2001 From: song Date: Sat, 17 Oct 2026 12:27:02 +0800 Subject: [PATCH 2/2] fix(benchmark): preserve preparation intent and defer cadence until ready Signed-off-by: song --- benchmark/README.md | 37 +++++---- benchmark/runtime/sforge_warm_start.py | 48 +++++++++--- benchmark/tests/test_native_checkpoint.py | 4 +- .../test_replan_successor_guard_reentry.py | 4 +- .../test_warm_continuation_readback.py | 76 ++++++++++++++++++- 5 files changed, 136 insertions(+), 33 deletions(-) diff --git a/benchmark/README.md b/benchmark/README.md index 87a33053b2..3242892821 100644 --- a/benchmark/README.md +++ b/benchmark/README.md @@ -1011,10 +1011,9 @@ returns. Preparation and context-reading cost belong in the comparison. Omit all restore flags for the unchanged cold entry; never reuse an ambiguous trial folder as a new attempt. -Warm preparation carries the admitted continuation request on its existing Todo. -Earlier Host deadlines and Host-budget closeout messages in retained history -belong to the source phase; the receiver follows its current Goal and guard. -Task-owned time constraints, native cooldowns, user gates and stop authority +Warm preparation retains the original evidence-reading task on its existing Todo; +it does not add a prompt paragraph interpreting historical deadlines. Current +Goal/guard authority, task time constraints, native cooldowns and user stops remain binding. Preparation reads the current claimed Todo and actual quota result before reporting `preparation_actionable=true` in the existing `checkpoint-context.json` receipt. Closed or unavailable work and a deadline that expires during readback @@ -1030,14 +1029,19 @@ open snapshot. The existing context receipt includes `preparation_readback` with that revision, the selected Todo and current replan identity, so an operator can inspect what was actually observed instead of relying on a readiness boolean. This is a bounded observation, not an execution lease; the worker must still use -fresh ordinary admission. A rejected attempt retains its context, cadence-one -preparation and owed review for inspection rather than silently resetting them. -Cross-version partial receivers and actual model/research adoption retain their -separate qualification boundaries. - -Warm 准备在既有 Todo 中传达本次已准入的继续请求:历史中的旧 Host 截止和预算收尾 -信息属于源阶段,目标阶段遵循当前 Goal 与 guard。题目时限、原生冷却、用户门槛及 -停止权限仍有效。准备步骤须读回当前 claimed Todo 和实际 quota 结果,才在既有 +fresh ordinary admission. Readiness is validated before changing cadence, so a +rejected preparation does not change the Goal's cadence. An ambiguous cadence +write must be inspected, not blindly rolled back across an owed current review. +Creation retries read the original `checkpoint-preparation` receipt and replay +its exact accepted task through the canonical create owner, including receivers +created with the earlier longer task. They never mint a second operation or Todo. +This covers partial creation, not arbitrary retries after context adoption or +worker execution. Such uncertain operations retain their original receipt, +revision and lease recovery boundary; actual model/research adoption is separate. + +Warm 准备保留既有 Todo 中原本的证据读取任务,不再增加解释历史截止时间的提示段落。 +当前 Goal、guard、题目时限、原生冷却、用户门槛及停止权限仍有效。 +准备步骤须读回当前 claimed Todo 和实际 quota 结果,才在既有 `checkpoint-context.json` 回执中报告 `preparation_actionable=true`;工作已闭合、不可用或读回 期间期限耗尽时,在模型执行前失败。旧 ready 计划不等于当前准入;此读回不授予 执行权,也不认证后续研究或再次捕获,仅作用于显式准入的完整检查点路径。 @@ -1046,9 +1050,12 @@ Warm 准备在既有 Todo 中传达本次已准入的继续请求:历史中的 接收端用准确准备 Todo 的规范权威修订号夹住 guard 读取;期间完成、转交或其他规范 修改都会拒绝 ready,后来的 required review 不能复活较早的 open 快照。既有上下文 回执的 `preparation_readback` 返回该修订、当前选中 Todo 与 replan 身份,便于检查 -实际观察依据;它不是执行租约,worker 仍须重新走普通准入。拒绝的接收端保留上下文、 -cadence-one 准备和欠下的复盘供检查,不默默重置;跨版本部分接收端与真实模型/研究采用 -仍需各自资格化。 +实际观察依据;它不是执行租约,worker 仍须重新走普通准入。先验证准备资格,再改 +cadence,因此拒绝准备不会改变 Goal 的 cadence;不确定的配置写入须检查,不能盲目 +回滚而抹掉欠下的当前复盘。创建重试读回原 `checkpoint-preparation` 回执,通过规范 +创建 owner 重放当时接受的准确任务(兼容此前较长任务),不创建第二个操作或 Todo。 +此兼容范围是部分创建,不是上下文采用或 worker 执行后的任意重试;不确定的操作仍须 +保留原回执、修订与租约的恢复边界,真实模型/研究采用也需单独验证。 To test a new decision from the same pre-replan state, use `--restore-fresh-replan`. The point must contain a verified pending replan packet. diff --git a/benchmark/runtime/sforge_warm_start.py b/benchmark/runtime/sforge_warm_start.py index fc8bd0afd0..ae5ef89007 100644 --- a/benchmark/runtime/sforge_warm_start.py +++ b/benchmark/runtime/sforge_warm_start.py @@ -26,14 +26,11 @@ "Read the full retained checkpoint research and pending decision, verify the current code, " "incumbent and remaining native opportunities. Do not modify candidates or submit during " "this preparation. Settle actual findings under current authority, then complete the first " - "current replan before choosing a complete research experiment. " - "This receiver was explicitly admitted to continue the retained work. Earlier Host deadlines " - "and Host-budget closeout messages belong to the source phase; follow the current Goal and " - "guard rather than using those historical messages to close this continuation. The Host " - "enforces the admitted allowance externally. Original task time constraints, submission " - "cooldowns, user gates and stop authority remain binding. No additional time or authority " - "is granted by this instruction." + "current replan before choosing a complete research experiment." ) +# Compatibility for the already accepted longer preparation request. Never +# submit that guidance to a new receiver or rewrite an existing create receipt. +_LEGACY_PREPARATION_TEXT_SHA256 = "c1480d8fcec20909567f39bac1b0af2a0a3d3703c56556f4356e5b3e43e0ba45" @dataclass(frozen=True) @@ -283,6 +280,34 @@ def admit_warm_session(env: dict[str, str], declaration: dict) -> dict: return result +def _preparation_todo(cli, goal: str, agent: str) -> str: + """Replay the exact accepted create payload; history grants no readiness.""" + operation = "checkpoint-preparation" + found = cli("todo", "receipt", "--goal-id", goal, "--operation-id", operation) + text = _PREPARATION_TASK + if found.get("status") == "found": + receipts = found.get("receipts") + original = receipts[0] if isinstance(receipts, list) and len(receipts) == 1 else {} + todo = original.get("todo") or {} + # Canonical presentation may decorate text with a priority prefix; + # title retains the create intent. The create owner checks its digest. + text = todo.get("title", todo.get("text")) + if (original.get("schema_version") != "loopx_coordination_todo_create_receipt_v0" + or original.get("goal_id") != goal or original.get("operation_id") != operation + or not isinstance(text, str) + or (text != _PREPARATION_TASK and hashlib.sha256(text.encode()).hexdigest() + != _LEGACY_PREPARATION_TEXT_SHA256)): + raise ValueError("Warm preparation operation names another request; inspect its original receipt") + elif found.get("status") != "missing": + raise ValueError("Warm preparation create receipt is unavailable; preserve its operation identity") + # The canonical owner still compares the complete request digest, including + # owner/capabilities/class. Do not derive a second Todo or accept a receipt + # as permission to execute a completed/transferred preparation. + return cli("todo", "add", "--goal-id", goal, "--role", "agent", "--claimed-by", agent, + "--operation-id", operation, "--required-capability", "shell", + "--task-class", "advancement_task", "--text", text)["todo_id"] + + def prepare_context(env: dict[str, str], declaration: dict) -> dict: """Join actual session + research owners on one freshly bootstrapped Goal.""" from loopx.hosts.research_checkpoint import adopt_todo_context @@ -308,9 +333,7 @@ def cli(*args): goal, agent = declaration["goal_id"], declaration["agent_id"] # Preparation is actual current work, not imported completion. The normal # model/guard must inspect the retained evidence and settle this Todo. - todo = cli("todo", "add", "--goal-id", goal, "--role", "agent", "--claimed-by", agent, - "--operation-id", "checkpoint-preparation", "--required-capability", "shell", - "--task-class", "advancement_task", "--text", _PREPARATION_TASK)["todo_id"] + todo = _preparation_todo(cli, goal, agent) key = "checkpoint-preparation" lease = cli("task-lease", "acquire", "--goal-id", goal, "--todo-id", todo, "--owner", agent, "--idempotency-key", key)["lease"] @@ -323,8 +346,11 @@ def cli(*args): # finished before this lease is released; no historical lease is imported. cli("task-lease", "release", "--goal-id", goal, "--todo-id", todo, "--owner", agent, "--idempotency-key", key, "--expected-version", str(lease["version"])) - cli("configure-goal", "--goal-id", goal, "--execution-replan-after-turns", "1", "--execute") readback = _read_preparation(cli, goal, agent, todo, declaration) + # Rejected readiness must not mutate cadence. Once this succeeds the worker + # still acquires fresh ordinary admission; no best-effort rollback may erase + # an owed current review after an ambiguous configuration response. + cli("configure-goal", "--goal-id", goal, "--execution-replan-after-turns", "1", "--execute") return {"session": native, "context": context, "preparation_todo_id": todo, "preparation_readback": readback, "preparation_actionable": True, "model_calls": 0, diff --git a/benchmark/tests/test_native_checkpoint.py b/benchmark/tests/test_native_checkpoint.py index 7204191d0e..c3e85b19e7 100644 --- a/benchmark/tests/test_native_checkpoint.py +++ b/benchmark/tests/test_native_checkpoint.py @@ -832,8 +832,8 @@ def forbidden_fork(*args, **kwargs): assert secret not in json.dumps(requests) if case == "warm_preparation": assert "previous Host deadline has passed" in json.dumps(requests[1]) - assert "Earlier Host deadlines" in json.dumps(requests[2]) - assert "No additional time or authority" in json.dumps(requests[2]) + assert _PREPARATION_TASK in json.dumps(requests[2]) + assert "Earlier Host deadlines" not in json.dumps(requests[2]) assert "Retained candidate 7 verified" in result.stdout retained = native_session_checkpoint(env, admitted["session_id"], timeout=20) assert retained["metadata"]["turn_ids"][:-1] == native["metadata"]["turn_ids"] diff --git a/tests/control_plane/test_replan_successor_guard_reentry.py b/tests/control_plane/test_replan_successor_guard_reentry.py index de8326e6ff..fc64a76a31 100644 --- a/tests/control_plane/test_replan_successor_guard_reentry.py +++ b/tests/control_plane/test_replan_successor_guard_reentry.py @@ -24,7 +24,7 @@ ROOT = Path(loopx.__file__).resolve().parents[1] -def _fixture(tmp_path: Path, monkeypatch: pytest.MonkeyPatch, provider: str, *, later_vision: bool = False, future_monitor: bool = True, initial_todos: list[dict] | None = None): +def _fixture(tmp_path: Path, monkeypatch: pytest.MonkeyPatch, provider: str, *, later_vision: bool = False, future_monitor: bool = True, initial_todos: list[dict] | None = None, handoff_mode: str = "soft_claim"): if provider == "sqlite": isolate_sqlite_runtime(tmp_path, monkeypatch) project, runtime = tmp_path / "project", tmp_path / "runtime" @@ -67,7 +67,7 @@ def _fixture(tmp_path: Path, monkeypatch: pytest.MonkeyPatch, provider: str, *, "coordination": {"agent_model": "peer_v1", "registered_agents": [AGENT, "different-agent"]}, }]})) initialize_canonical_authority(runtime, GOAL, build_todo_runtime_shadow_projection( - goal_id=GOAL, todos=initial_todos or [], handoff_mode="soft_claim", leases=[], + goal_id=GOAL, todos=initial_todos or [], handoff_mode=handoff_mode, leases=[], ), state_path=state, provider=provider) def call(*args: str, expected_code: int = 0) -> dict: diff --git a/tests/control_plane/test_warm_continuation_readback.py b/tests/control_plane/test_warm_continuation_readback.py index eaaceff9d7..c7ca5d548f 100644 --- a/tests/control_plane/test_warm_continuation_readback.py +++ b/tests/control_plane/test_warm_continuation_readback.py @@ -49,9 +49,7 @@ def test_current_preparation_readback_is_idempotent_and_respects_user_gate(tmp_p assert declaration["deadline_epoch"] == deadline listed = call("todo", "list", "--goal-id", GOAL, "--todo-id", todo)["todo"] assert listed["status"] == "open" and listed["claimed_by"] == AGENT - assert "Earlier Host deadlines" in listed["text"] - assert "Original task time constraints" in listed["text"] - assert "No additional time or authority" in listed["text"] + assert listed["title"] == _PREPARATION_TASK call("todo", "add", "--goal-id", GOAL, "--role", "user", "--task-class", "user_gate", "--global-gate", "--text", "Authorize the separate destination operation.") with pytest.raises(ValueError, match="no actionable"): @@ -131,3 +129,75 @@ def expires_after_actual_guard(*args): with pytest.raises(ValueError, match="expired"): _read_preparation(expires_after_actual_guard, GOAL, AGENT, todo, {"deadline_epoch": deadline}) + + +@pytest.mark.parametrize("provider", ["file", "sqlite"]) +@pytest.mark.parametrize("legacy", [False, True]) +def test_preparation_create_reuses_original_payload_without_a_second_todo(tmp_path, monkeypatch, provider, legacy): + from benchmark.runtime.sforge_warm_start import _preparation_todo + + call, _, _ = _fixture(tmp_path, monkeypatch, provider, future_monitor=False) + text = _PREPARATION_TASK + if legacy: + text += ( + " This receiver was explicitly admitted to continue the retained work. Earlier Host deadlines " + "and Host-budget closeout messages belong to the source phase; follow the current Goal and " + "guard rather than using those historical messages to close this continuation. The Host " + "enforces the admitted allowance externally. Original task time constraints, submission " + "cooldowns, user gates and stop authority remain binding. No additional time or authority " + "is granted by this instruction." + ) + original = call("todo", "add", "--goal-id", GOAL, "--role", "agent", "--claimed-by", AGENT, + "--text", text, "--task-class", "advancement_task", "--required-capability", "shell", + "--operation-id", "checkpoint-preparation")["todo_id"] + before = call("todo", "list", "--goal-id", GOAL) + assert _preparation_todo(call, GOAL, AGENT) == original + assert _preparation_todo(call, GOAL, AGENT) == original + after = call("todo", "list", "--goal-id", GOAL) + assert after["authority_read"] == before["authority_read"] + assert after["todos"] == before["todos"] + + +@pytest.mark.parametrize("provider", ["file", "sqlite"]) +def test_preparation_operation_cannot_be_repurposed(tmp_path, monkeypatch, provider): + from benchmark.runtime.sforge_warm_start import _preparation_todo + + call, _, _ = _fixture(tmp_path, monkeypatch, provider, future_monitor=False) + call("todo", "add", "--goal-id", GOAL, "--role", "agent", "--claimed-by", AGENT, + "--text", "Different work", "--operation-id", "checkpoint-preparation") + before = call("todo", "list", "--goal-id", GOAL) + with pytest.raises(ValueError, match="another request"): + _preparation_todo(call, GOAL, AGENT) + assert call("todo", "list", "--goal-id", GOAL)["authority_read"] == before["authority_read"] + + +@pytest.mark.parametrize("provider", ["file", "sqlite"]) +def test_rejected_context_preparation_does_not_change_cadence(tmp_path, monkeypatch, provider): + import json + import os + import sys + from benchmark.runtime import sforge_warm_start as warm, worker + from loopx.hosts import research_checkpoint + + call, _, _ = _fixture(tmp_path, monkeypatch, provider, future_monitor=False, handoff_mode="hard_lease") + registry = tmp_path / "registry.json" + call("configure-goal", "--goal-id", GOAL, "--execution-replan-after-turns", "6", "--execute") + call("todo", "add", "--goal-id", GOAL, "--role", "user", "--task-class", "user_gate", + "--global-gate", "--text", "Wait for the user's authorization") + before = registry.read_bytes() + home, skills = tmp_path / "home", tmp_path / "skills" + home.mkdir() + skills.mkdir() + env = {**os.environ, "LOOPX_GOAL_ID": GOAL, "LOOPX_AGENT_ID": AGENT, + "CODEX_HOME": str(home), "LOOPX_SHARED_SKILLS": str(skills), + "LOOPX_PROJECT": str(tmp_path / "project")} + monkeypatch.setattr(warm, "admit_warm_session", lambda *_: {}) + monkeypatch.setattr(research_checkpoint, "adopt_todo_context", lambda *_, **__: {}) + monkeypatch.setattr(worker, "loopx_command", lambda _: [sys.executable, "-m", "loopx.cli", + "--registry", str(registry), "--runtime-root", str(tmp_path / "runtime"), "--format", "json"]) + with pytest.raises(ValueError, match="no actionable"): + warm.prepare_context(env, {"root": str(tmp_path / "point"), "sha256": "a" * 64, + "goal_id": GOAL, "agent_id": AGENT, "source_todo_id": "source", "normal_cadence": 6, + "deadline_epoch": int(time.time()) + 600}) + assert registry.read_bytes() == before + assert json.loads(registry.read_bytes()) == json.loads(before)