From 2385b99d4b92b005fe7fd8a5aa256e7988eff5e4 Mon Sep 17 00:00:00 2001 From: song Date: Mon, 2 Nov 2026 21:08:44 +0800 Subject: [PATCH 1/5] fix(goals): expose exact semantic feedback checkpoints Signed-off-by: song --- benchmark/runtime/EXECUTION_OBSERVATIONS.md | 28 +++ .../goal-vision-replan-contract-v0.md | 41 ++++- loopx/control_plane/effect_program.py | 3 + loopx/control_plane/effect_program.ts | 3 + .../control_plane/goals/vision_checkpoint.ts | 6 + loopx/control_plane/quota/effect_program.py | 20 ++- loopx/control_plane/quota/settlement_plan.ts | 18 +- .../test_material_feedback_checkpoint.py | 164 ++++++++++++++++++ .../test_quota_settlement_cli.py | 5 +- .../quota_settlement_plan.test.ts | 28 +++ 10 files changed, 298 insertions(+), 18 deletions(-) create mode 100644 tests/control_plane/test_material_feedback_checkpoint.py diff --git a/benchmark/runtime/EXECUTION_OBSERVATIONS.md b/benchmark/runtime/EXECUTION_OBSERVATIONS.md index 06edc94773..aab7dd6455 100644 --- a/benchmark/runtime/EXECUTION_OBSERVATIONS.md +++ b/benchmark/runtime/EXECUTION_OBSERVATIONS.md @@ -39,6 +39,24 @@ merely to produce an attachment. Configuration and readback invoke neither a probe nor a model. Runtime receipts record the selected producer, which does not attest execution, adoption or improvement. +Decision-relevant released feedback closes the current work segment before the +next experiment or closeout decision. The ordinary settlement plan's +`durable_writeback.semantic_closeout_command_template` selects that boundary +with the original Turn, Todo and Goal identity; fill its result and Vision +placeholders, then follow the same plan's remaining steps and yield to the Host. +Keep an unfinished Todo open. Record the evaluated artifact/input and the +decision in the existing Vision/path delta even when the route stays unchanged. +The next guarded wake can continue the same Goal/session or perform an existing +due review; neither feedback counts nor this command enable Stride. Repeated +unchanged polls and bookkeeping alone need no new checkpoint. + +For an explicitly continuing optimization objective, a usable candidate is a +checkpoint. Before stopping, review remaining acceptance gaps, plausible +headroom, feasible experiments, full marginal cost and the accepted stopping +condition. Stop when justified; do not force a pivot or use the remaining time +merely because it is available. This is Agent-consumed execution guidance, +not automatic tool-result classification or proof that a model adopted it. + ## Retiring the bundled research treatment `research_loop` and `LOOPX_RESEARCH_LOOP` have been removed. Any presence of @@ -170,6 +188,16 @@ Stop、原预算和真实阻塞继续有效,不为填满时长制造任务。 变化拒绝归因;文件清单哈希不是原生归档哈希,只覆盖明确选择的文件及探针 边界,不覆盖传递依赖或独立写入者,结果仍须证明对应实际评测快照和输入版本。 +影响下一实验或收尾判断的已释放反馈,是当前工作段的结算边界。使用当前 +settlement plan 的 `durable_writeback.semantic_closeout_command_template`, +保留原 Turn、Todo、Goal 身份,填写实际结果和 Vision,再按同一 plan 的剩余 +步骤结算并让宿主接续;未完成的 Todo 保持 open。即使路线不变,也在既有 +Vision/path delta 中记录被评估版本、输入和决策。下一次 guard 继续同一 Goal/session +或执行已有到期复核;反馈数量与这个命令都不启用 Stride。未变化轮询、记账本身 +无需新 checkpoint。明确持续优化的目标不能因已有可用候选就自动收尾;结束前 +评估剩余验收、改进空间、可行实验、完整边际成本和原停止条件。合理时可停止, +不强制换路或用满时间。这是 Agent 执行指导,不是自动分类工具结果或模型已采用的证明。 + 观测通过既有 Explore 写回、replan 和研究历程保留,输入版本与引用必须属于 该 finding,计划/实际方法不同须记录理由。finding 引用及观测的 `evidence_refs`、 `result_ref` 共用既有语法,可保留 `validation:run/1`;方法 ID 仍使用独立规则。 diff --git a/docs/reference/protocols/goal-vision-replan-contract-v0.md b/docs/reference/protocols/goal-vision-replan-contract-v0.md index 4fd53e2ff2..7e913b6f3b 100644 --- a/docs/reference/protocols/goal-vision-replan-contract-v0.md +++ b/docs/reference/protocols/goal-vision-replan-contract-v0.md @@ -896,14 +896,37 @@ receipts retain their checkpoint semantics: } ``` -CLI in-flight settlement omits the material-closeout Vision authoring schema. -Use the projected continuation command and its original boundary/identity flags; -this does not waive validation, writeback or debit rules. Material CLI closeout -and MCP `complete_task` retain their evidence-linked Vision authoring contract. - -CLI 进行中结算不附带实质收尾的 Vision 编写结构。沿用投影命令及原边界、身份 -参数,仍须验证、写回与原 debit;实质 CLI 收尾和 MCP `complete_task` 保留 -证据关联的 Vision 编写契约。 +Ordinary CLI Todo settlement includes the existing Vision authoring schema even +when its default boundary is in-flight. The durable-writeback step also exposes +`semantic_closeout_command_template`: the same admitted Goal, Todo and Turn, +with `--delivery-boundary semantic_closeout --agent-vision-json ` selected. +It is an alternative for that step, not another plan, claim, debit or admission. +Material CLI closeout and MCP `complete_task` retain their existing evidence gates. + +Released feedback affecting the next experiment or closeout decision ends the +current work segment. Bind it to the evaluated artifact/input, record the actual +decision in Vision/path delta even when retaining the route, execute the current +plan's writeback/spend and yield for a freshly guarded Host continuation. Leave +an unfinished Todo open. Repeated unchanged polls and bookkeeping alone need no +new checkpoint. This is Agent execution guidance, not automatic classification +of tool text, a new Stride trigger, or proof of adoption or useful outcomes. + +For an explicitly continuing or optimization objective, a usable candidate is a +checkpoint. Before stopping, compare unmet acceptance, plausible headroom, +feasible next work and full marginal cost with the original stopping condition. +Retain an evidenced no-follow-up, Stop, budget/permission limit or genuine wait; +neither a submission nor unused budget decides whether further work is useful. + +普通 CLI Todo 结算在默认 in-flight 边界下也提供既有 Vision 编写结构;写回步骤的 +`semantic_closeout_command_template` 沿用原 Goal、Todo、Turn,只显式选择 +semantic closeout 与 Vision 文件。它是当前步骤的可执行变体,不增加 plan、认领、 +扣额或准入。影响下一实验或收尾判断的已释放反馈结束当前工作段;绑定评估版本与 +输入,即使保留路线也记录实际 Vision/path 决策,再按原计划写回、扣额并让宿主 +以新 guard 接续,未完成 Todo 保持 open。未变化轮询、记账无需新 checkpoint。 +持续优化目标中的可用候选只是检查点;停止前对照剩余验收、改进空间、可行工作、 +完整边际成本和原停止条件,保留有依据的 no-follow-up、Stop、预算/权限限制与真实 +等待。不强制用满预算或换路,也不由工具文本自动分类或新增 Stride trigger;执行 +指导与模型实际采用、结果收益仍须分别验证。 One explicit exception exists for in-flight delivery. When quota admits an open advancement Todo for normal delivery, its settlement guidance may add the @@ -933,7 +956,7 @@ decision merely because the scheduler woke up. Agents must start from projected boundary and identity flags; reconstructing a generic `semantic_closeout` command discards that continuity contract. -If the work actually replanned or completed the Todo, explicitly choose +If decision-relevant feedback arrived, the work actually replanned or completed the Todo, explicitly choose `semantic_closeout` and provide its required vision checkpoint. Do not simply remove the replan ACK to make a real replan pass as in-flight work. Conversely, ordinary within-Todo progress must not add `--autonomous-replan-recorded`. diff --git a/loopx/control_plane/effect_program.py b/loopx/control_plane/effect_program.py index f283d6a9c1..d870c94d7d 100644 --- a/loopx/control_plane/effect_program.py +++ b/loopx/control_plane/effect_program.py @@ -636,6 +636,7 @@ class SettlementStep: conditional: bool = False command_condition: str | None = None vision_authoring: Mapping[str, Any] | None = None + semantic_closeout_command_template: str | None = None def as_dict(self) -> dict[str, Any]: step: dict[str, Any] = { @@ -653,6 +654,8 @@ def as_dict(self) -> dict[str, Any]: step["command_condition"] = self.command_condition if self.vision_authoring is not None: step["vision_authoring"] = dict(self.vision_authoring) + if self.semantic_closeout_command_template is not None: + step["semantic_closeout_command_template"] = self.semantic_closeout_command_template return step diff --git a/loopx/control_plane/effect_program.ts b/loopx/control_plane/effect_program.ts index 7e468d3642..b66576161c 100644 --- a/loopx/control_plane/effect_program.ts +++ b/loopx/control_plane/effect_program.ts @@ -213,6 +213,8 @@ export interface SettlementStep { idempotency_key_ref: string; expected_receipt: string; command_template?: string; + /** Same admitted writeback, with the existing semantic boundary selected. */ + semantic_closeout_command_template?: string; command_condition?: "todo_deliverable_complete"; vision_authoring?: JsonObject; conditional?: true; @@ -658,6 +660,7 @@ export function settlementStepPayload(step: SettlementStep): JsonObject { expected_receipt: step.expected_receipt, }; if (step.command_template) payload.command_template = step.command_template; + if (step.semantic_closeout_command_template) payload.semantic_closeout_command_template = step.semantic_closeout_command_template; if (step.command_condition) payload.command_condition = step.command_condition; if (step.conditional) payload.conditional = true; if (step.vision_authoring) payload.vision_authoring = step.vision_authoring; diff --git a/loopx/control_plane/goals/vision_checkpoint.ts b/loopx/control_plane/goals/vision_checkpoint.ts index 303d406e18..c933ec49e5 100644 --- a/loopx/control_plane/goals/vision_checkpoint.ts +++ b/loopx/control_plane/goals/vision_checkpoint.ts @@ -126,6 +126,12 @@ export function visionAuthoringContract(autonomousReplan = false): JsonObject { ? `Autonomous review: changing ${GOAL_VISION_DURABLE_FIELDS.join(", ")} requires outcome=replan, even when the Goal objective stays unchanged. Retaining these fields permits continue/no_change. ` : "Changed mainline: outcome=replan. ") + "vision_closed closes a stage, not Goal; no_followup: no scoped work.", + continuation_review: "For an explicitly continuing or optimization objective, a usable candidate is a checkpoint. " + + "Before stop/no_followup, compare remaining acceptance gaps, plausible headroom, a feasible next experiment " + + "and its full marginal cost with the accepted stopping condition. Record the judgment and evidence in path_delta; " + + "continue or replan useful authorized work. A submission or unused budget alone proves neither completion nor " + + "a need for more work. Preserve explicit Stop, budget/permission limits and genuine waits; never invent a successor, " + + "force a pivot or fill time when no useful authorized step remains.", }; } // Bounded typed fallback declarations survive prepare unchanged so the diff --git a/loopx/control_plane/quota/effect_program.py b/loopx/control_plane/quota/effect_program.py index 37644130de..33025bb26f 100644 --- a/loopx/control_plane/quota/effect_program.py +++ b/loopx/control_plane/quota/effect_program.py @@ -220,11 +220,19 @@ def build_turn_scoped_cli_settlement_plan( f"{lifecycle_actor_args}{turn_arg}{goal_ref_arg} --evidence ''" ) terminal_closeout = ordinary_completion + " --no-follow-up" - writeback = ( - f"{cli_prefix} refresh-state --goal-id {shlex.quote(goal_id)} " - "--classification --delivery-batch-scale " - f"--delivery-outcome {boundary_arg}{binding_arg}{turn_arg}{goal_ref_arg}" - f"{scoped_cli_args}{writeback_path_args}" + + def writeback_command(boundary: str) -> str: + return ( + f"{cli_prefix} refresh-state --goal-id {shlex.quote(goal_id)} " + "--classification --delivery-batch-scale " + f"--delivery-outcome {boundary}{binding_arg}{turn_arg}{goal_ref_arg}" + f"{scoped_cli_args}{writeback_path_args}" + ) + + writeback = writeback_command(boundary_arg) + semantic_writeback = ( + writeback_command(" --delivery-boundary semantic_closeout") + + " --agent-vision-json " ) spend = ( f"{cli_prefix} quota spend-slot --goal-id {shlex.quote(goal_id)} --slots 1 " @@ -241,6 +249,7 @@ def build_turn_scoped_cli_settlement_plan( "command_templates": { **({"todo_claim": task_claim} if todo_id else {}), "todo_completion": ordinary_completion, "durable_writeback": writeback, + "semantic_closeout": semantic_writeback, "quota_spend": spend, "terminal_closeout": terminal_closeout, }, }) @@ -253,6 +262,7 @@ def build_turn_scoped_cli_settlement_plan( expected_receipt=row["expected_receipt"], command_template=row.get("command_template"), conditional=row.get("conditional", False), command_condition=row.get("command_condition"), vision_authoring=row.get("vision_authoring"), + semantic_closeout_command_template=row.get("semantic_closeout_command_template"), ) for row in payload["ordered_steps"] ), _runtime_payload=payload) diff --git a/loopx/control_plane/quota/settlement_plan.ts b/loopx/control_plane/quota/settlement_plan.ts index e7e86b82ba..8cb8ba73af 100644 --- a/loopx/control_plane/quota/settlement_plan.ts +++ b/loopx/control_plane/quota/settlement_plan.ts @@ -19,6 +19,8 @@ export function turnScopedCliSettlementPlan(params: JsonObject): SettlementPlan const commands = requireJsonObject(params.command_templates, "command_templates"); const writeback = requireNonEmptyString(commands.durable_writeback, "durable_writeback"); const spend = requireNonEmptyString(commands.quota_spend, "quota_spend"); + const semanticWriteback = commands.semantic_closeout === undefined ? null + : requireNonEmptyString(commands.semantic_closeout, "semantic_closeout"); // Scale remains CLI-owned. Project its actual accepted inputs rather than // copying another enum or asking callers to load the entire command help. const scales = requireStringArray(params.delivery_batch_scale_choices, "delivery_batch_scale_choices"); @@ -81,6 +83,10 @@ export function turnScopedCliSettlementPlan(params: JsonObject): SettlementPlan "an artifact pointer; --result-file requires approved Goal acceptance criteria bound to this Todo, " + "not merely a Todo validator. No invented successor " + "or --no-follow-up is required. Todo done does not settle the Turn. " + + "Released feedback that changes or justifies the next experiment or closeout is a work-segment boundary: " + + "bind it to the evaluated artifact/input, then execute this plan's writeback and spend before the next decision. " + + "Keep an unfinished Todo open; after settlement yield to the Host for a freshly guarded continuation. " + + "Unchanged polls and bookkeeping alone create no checkpoint. " + (inFlight ? "For unfinished bounded progress, keep the Todo open with its original declaration. " + "The projected in-flight boundary is not an instruction to retain completed work: " + "when complete or changing route, use --delivery-boundary semantic_closeout with an " + @@ -95,7 +101,10 @@ export function turnScopedCliSettlementPlan(params: JsonObject): SettlementPlan " Pass a new packet with --agent-vision-json.") + " Route elimination needs evidence; failure alone is not progress. " + (autonomousReplan ? "Autonomous replan uses --delivery-boundary semantic_closeout even while its Todo remains open; " - : "outcome_progress may use in_flight_continuation; ") + "outcome_gap requires " + + : "For decision-relevant feedback use --delivery-boundary semantic_closeout and a fresh evidence-linked " + + "Vision/path_delta with --agent-vision-json, including when the route stays unchanged. " + + "Use this step's semantic_closeout_command_template when present; follow the remaining plan unchanged. " + + "Other outcome_progress may use in_flight_continuation; ") + "outcome_gap requires " + "semantic_closeout plus blocked + blocker/evidence IDs and continuation checks. " + "For an admitted open continuous Monitor whose observation is unavailable, " + "use this original identity for typed blocked outcome_gap; it settles without " + @@ -103,9 +112,12 @@ export function turnScopedCliSettlementPlan(params: JsonObject): SettlementPlan "An already committed Monitor poll needs neither refresh nor spend." + inputHint, idempotency_key_ref: "$.identity.effect_id", expected_receipt: "durable_writeback_receipt", command_template: writeback, - // In-flight continuation uses its projected flags without a material vision packet. + ...(identity.binding_kind === "todo" && !autonomousReplan && semanticWriteback !== null + ? {semantic_closeout_command_template: semanticWriteback} : {}), + // The same open Todo can reach a semantic feedback checkpoint. Deliver + // its existing authoring contract with the executable command variant. // Autonomous replan already carries this owner in its writeback contract. - ...(identity.binding_kind === "todo" && !inFlight + ...(identity.binding_kind === "todo" && (!inFlight || semanticWriteback !== null) ? {vision_authoring: visionAuthoringContract(autonomousReplan)} : {}), }, { diff --git a/tests/control_plane/test_material_feedback_checkpoint.py b/tests/control_plane/test_material_feedback_checkpoint.py new file mode 100644 index 0000000000..9dc9bfb3d5 --- /dev/null +++ b/tests/control_plane/test_material_feedback_checkpoint.py @@ -0,0 +1,164 @@ +"""Released feedback closes a segment without closing its continuing Todo. + +The actor is deterministic. Real CLI, File/SQLite authority, settlement, +Explore, Stride and local evaluations run; model adoption is a separate check. +""" +from __future__ import annotations + +import hashlib +import json +from pathlib import Path +import shlex +import sys + +import pytest + +from benchmark.runtime.research_probe import run_probe +from canonical_authority_fixture import isolate_sqlite_runtime +import test_quota_settlement_cli as fixture + + +@pytest.mark.parametrize("provider", ["file", "sqlite"]) +@pytest.mark.parametrize("strategy", ["off", "evidence_conditioned_v1"]) +def test_feedback_checkpoint_reaches_next_actual_experiment(tmp_path, monkeypatch, provider, strategy): + isolate_sqlite_runtime(tmp_path, monkeypatch) + project, runtime, registry = fixture._write_fixture(tmp_path) + configuration = json.loads(registry.read_text()) + configuration["goals"][0]["quota"]["allowed_slots"] = 8 + registry.write_text(json.dumps(configuration)) + state = project / f".codex/goals/{fixture.GOAL_ID}/ACTIVE_GOAL_STATE.md" + state.write_text(state.read_text().replace("Settle one standard Codex App delivery.", + "Minimize route cost on the supplied graph while retaining a valid route. " + "A feasible candidate is a checkpoint; stop when no useful authorized comparison remains.")) + fixture._initialize_fixture_authority(project, runtime, provider=provider) + monkeypatch.chdir(project) + binding = ("--goal-id", fixture.GOAL_ID, "--agent-id", fixture.AGENT_ID) + + def cli(*args): + code, packet = fixture._run_cli(registry, runtime, *args, cwd=project) + assert code == 0, packet + return packet + + def execute(command, **values): + for name, value in values.items(): + command = command.replace("<" + name + ">", shlex.quote(str(value))) + assert "<" not in command, command + code, packet = fixture._run_generated_cli(command, registry_path=registry, cwd=project) + assert code == 0, packet + return packet + + cli("configure-goal", "--goal-id", fixture.GOAL_ID, "--execution-replan-after-turns", "1", + "--execution-stride-strategy", strategy, "--explore-graph-enabled", "--execute") + route = project / "route.json" + graph = {"start": {"middle": 1, "finish": 1}, "middle": {"finish": 1}, "finish": {}} + (project / "graph.json").write_text(json.dumps(graph)) + validator = project / "evaluate.py" + validator.write_text( + "import json\nfrom pathlib import Path\n" + "g=json.loads(Path('graph.json').read_text()); r=json.loads(Path('route.json').read_text())\n" + "assert r[0]=='start' and r[-1]=='finish' and len(r)==len(set(r))\n" + "cost=sum(g[a][b] for a,b in zip(r,r[1:]))\n" + "print(json.dumps({'valid':True,'cost':cost}))\n") + route.write_text(json.dumps(["start", "middle", "finish"])) + guard = cli("quota", "should-run", *binding, "--runtime-profile", "generic_cli", + "--todo-id", fixture.TODO_ID, "--turn-instance-id", "route-feedback-1") + assert guard["should_run"] and guard["selected_todo"]["todo_id"] == fixture.TODO_ID + plan = guard["interaction_contract"]["cli_channel"]["settlement_plan"] + writeback = plan["ordered_steps"][1] + checkpoint = writeback["semantic_closeout_command_template"] + assert "--delivery-boundary semantic_closeout" in checkpoint and "" not in checkpoint + assert writeback["vision_authoring"]["continuation_review"] + # Merely obtaining the new executable projection performs no work or writeback. + assert not (runtime / "goals" / fixture.GOAL_ID / "runs/index.jsonl").exists() + observed = run_probe(command=[sys.executable, str(validator)], candidate_files=[route], + output=project / "probe-1.json", input_revision="graph-v1", planned_method_id="retained-route", + executed_method_id="retained-route", evidence_ref="probe:route-1", limit_seconds=10) + first = json.loads((project / "probe-1.json.stdout").read_text()) + assert first == {"valid": True, "cost": 2} + evidence = "result:" + hashlib.sha256((project / "probe-1.json.stdout").read_bytes()).hexdigest() + observed.update(result_ref=evidence, result_candidate_sha256=observed["candidate_sha256"]) + observed["evidence_refs"].append(evidence) + result = project / "released-result.json" + result.write_text(json.dumps({"schema_version": "explore_result_attachment_v0", + "question": "Can an allowed route reduce the original cost?", "input_revision": "graph-v1", + "applicability": "Only the supplied public synthetic graph.", + "observation": "The independently evaluated feasible route costs two.", + "interpretation": "The allowed direct edge is an informative next comparison; feasibility alone is not closeout.", + "status": "tentative", "evidence_refs": observed["evidence_refs"], "execution_observation": observed})) + patch = {"vision_summary": "Minimize route cost under the original graph and authority.", + "acceptance_summary": "Retain a valid route and compare useful allowed alternatives before closeout.", + "advancement_policy": "repeat_until_closed"} + vision = project / "vision.json" + vision.write_text(json.dumps({"schema_version": "goal_vision_replan_contract_v0", "state": "active", + "vision_patch": patch, "todo_delta": ["retain:" + fixture.TODO_ID], + "path_delta": {"outcome": "continue", "prior_assumption": "The retained route is feasible.", + "observed_reality": "Released validation confirms cost two; the permitted direct edge is untested.", + "retained": ["Original optimization objective and current feasible route."], "evidence_refs": [evidence]}})) + result_command = (checkpoint + " --explore-result-json " + shlex.quote(str(result)) + + " --no-global-sync --suppress-external-sinks") + rejected_command = result_command + for key, value in dict(validated_progress="bookkeeping", scale="single_surface", + outcome="surface_only", file=vision).items(): + rejected_command = rejected_command.replace("<" + key + ">", shlex.quote(str(value))) + rejected_code, rejected = fixture._run_generated_cli(rejected_command, registry_path=registry, cwd=project) + assert rejected_code == 1 and not rejected.get("appended") + assert fixture._spend_run_count(runtime) == 0 + refreshed = execute(result_command, validated_progress="route_feedback_verified", + scale="implementation", outcome="outcome_progress", file=vision) + execute(result_command, validated_progress="route_feedback_verified", + scale="implementation", outcome="outcome_progress", file=vision) + assert fixture._classification_count(runtime, "route_feedback_verified") == 1 + persisted = json.loads(Path(refreshed["json_path"]).read_text()) + assert persisted["vision_checkpoint"]["delivery_boundary"] == "semantic_closeout" + assert persisted["agent_vision"]["path_delta"]["evidence_refs"] == [evidence] + execute(plan["ordered_steps"][2]["command_template"]) + execute(plan["ordered_steps"][2]["command_template"]) + assert fixture._spend_run_count(runtime) == 1 + assert cli("todo", "list", "--goal-id", fixture.GOAL_ID, "--todo-id", fixture.TODO_ID)["todo"]["status"] == "open" + # A new Host wake has a new Turn and the same Goal/Agent/Todo lane. The + # ordinary cadence reads the material result; raw experiment counts do not. + review = cli("quota", "should-run", *binding, "--runtime-profile", "generic_cli", + "--turn-instance-id", "route-review") + assert any(t["kind"] == "periodic_review_due" for t in review["autonomous_replan_obligation"]["triggers"]) + selection = review["interaction_contract"]["cli_channel"]["selection_command"] + review = execute(selection["route_prefix"] + " " + + selection["command_args_template"].format(todo_id=fixture.TODO_ID)) + control = review["replan_action_packet"].get("stride_control") + packet = {"schema_version": "goal_vision_replan_contract_v0", "state": "active", "vision_patch": patch, + "todo_delta": ["retain:" + fixture.TODO_ID], "path_delta": {"outcome": "continue", + "prior_assumption": "A feasible candidate may still leave useful optimization work.", + "observed_reality": "The scoped validation retains a cost-two route; one allowed direct route remains untested.", + "retained": ["Original objective and feasible incumbent."], + "changed": ["Compare the allowed direct route on the same graph."], "evidence_refs": [evidence]}} + if strategy == "off": + assert control is None + else: + assert control + finding = next(row for row in control["explore_basis"]["finding_refs"] + if row.get("execution_observation", {}).get("candidate_sha256") == observed["candidate_sha256"]) + decision = control["minimal_example"] + decision.update(action="switch", next_probe={"scope": "Compare the permitted direct route.", + "prediction": "The independently checked route cost falls from two to one.", + "adoption_criterion": "Same graph, valid endpoints and a lower actual route cost.", + "cost_bound": "One local process and existing settlement; no new evaluation authority.", + "minimum_evidence": "Exact route bytes and evaluator output."}) + decision["explore_review"] = {"revision": control["explore_basis"]["revision"], "disposition": "used", + "considered_refs": [finding["finding_id"]], "reason": "The measured feasible candidate leaves the direct edge untested."} + packet["stride_decision"] = decision + vision.write_text(json.dumps(packet)) + review_plan = review["interaction_contract"]["cli_channel"]["settlement_plan"] + acknowledged = execute(review_plan["ordered_steps"][1]["command_template"] + + " --agent-vision-json " + shlex.quote(str(vision)) + " --no-global-sync --suppress-external-sinks", + validated_progress="route_comparison_decided", scale="implementation", outcome="outcome_progress") + assert acknowledged["autonomous_replan_ack"]["semantic_delta"]["accepted"] + execute(review_plan["ordered_steps"][2]["command_template"]) + next_guard = cli("quota", "should-run", *binding, "--runtime-profile", "generic_cli", + "--todo-id", fixture.TODO_ID, "--turn-instance-id", "route-feedback-2") + assert next_guard["should_run"] and next_guard["selected_todo"]["todo_id"] == fixture.TODO_ID + route.write_text(json.dumps(["start", "finish"])) + changed = run_probe(command=[sys.executable, str(validator)], candidate_files=[route], + output=project / "probe-2.json", input_revision="graph-v1", planned_method_id="direct-route", + executed_method_id="direct-route", evidence_ref="probe:route-2", limit_seconds=10) + assert changed["candidate_sha256"] != observed["candidate_sha256"] + assert json.loads((project / "probe-2.json.stdout").read_text()) == {"valid": True, "cost": 1} + assert first["cost"] == 2 # Retain the original result; no hindsight rewrite. diff --git a/tests/control_plane/test_quota_settlement_cli.py b/tests/control_plane/test_quota_settlement_cli.py index 6282fc3b59..f0117ab0ed 100644 --- a/tests/control_plane/test_quota_settlement_cli.py +++ b/tests/control_plane/test_quota_settlement_cli.py @@ -2516,7 +2516,10 @@ def test_in_flight_progress_preserves_todo_across_heartbeat_settlements( ] assert "--delivery-boundary " in first_writeback writeback_step = guard["interaction_contract"]["cli_channel"]["settlement_plan"]["ordered_steps"][1] - assert "vision_authoring" not in writeback_step + assert writeback_step["vision_authoring"]["schema_version"] == "goal_vision_replan_contract_v0" + checkpoint = writeback_step["semantic_closeout_command_template"] + assert "--delivery-boundary semantic_closeout" in checkpoint and "" not in checkpoint + assert f"--turn-instance-id {first_turn_id}" in checkpoint assert "outcome_gap requires semantic_closeout" in writeback_step["precondition"] offered = writeback_step["precondition"].split("; outcome=", 1)[1].split(".", 1)[0].split("|") blocked = { diff --git a/tests/control_plane_ts/quota_settlement_plan.test.ts b/tests/control_plane_ts/quota_settlement_plan.test.ts index ddeaba33d7..e264a461ae 100644 --- a/tests/control_plane_ts/quota_settlement_plan.test.ts +++ b/tests/control_plane_ts/quota_settlement_plan.test.ts @@ -53,6 +53,9 @@ test("ordinary completion is conditional on deliverable acceptance, not Turn acc assert.match(plan.steps[0].precondition, /--result-file requires approved Goal acceptance criteria bound to this Todo, not merely a Todo validator/); assert.match(plan.steps[0].precondition, /Todo done does not settle the Turn/); + assert.match(plan.steps[0].precondition, /Released feedback.*work-segment boundary/); + assert.match(plan.steps[0].precondition, /unfinished Todo open.*freshly guarded continuation/); + assert.match(plan.steps[0].precondition, /Unchanged polls.*no checkpoint/); assert.match(plan.steps[1].precondition, /--agent-vision-json/); assert.match(plan.steps[1].precondition, /path_delta.*evidence_refs/); assert.match(plan.steps[1].precondition, /acceptance_summary/); @@ -98,6 +101,8 @@ test("in-flight validation offers completion only after original deliverable acc assert.match(plan.steps[1].precondition, /^validation succeeded/); assert.match(plan.steps[1].precondition, /Route elimination needs evidence/); assert.match(plan.steps[1].precondition, /failure alone is not progress/); + assert.match(plan.steps[1].precondition, /decision-relevant feedback.*semantic_closeout.*fresh evidence-linked/i); + assert.match(plan.steps[1].precondition, /including when the route stays unchanged/); assert.match( plan.steps[1].precondition, /outcome_gap requires semantic_closeout.*blocker\/evidence IDs.*continuation checks/, @@ -108,6 +113,29 @@ test("in-flight validation offers completion only after original deliverable acc assert.equal(plan.steps[3].conditional, true); }); +test("semantic feedback checkpoint is an exact variant of the current writeback, not another plan", () => { + for (const delivery_boundary of [undefined, "in_flight_continuation"]) { + const before = turnScopedCliSettlementPlan({...input, delivery_boundary}); + const checkpoint = "same bound writeback --delivery-boundary semantic_closeout --agent-vision-json "; + const plan = turnScopedCliSettlementPlan({...input, delivery_boundary, + command_templates: {...input.command_templates, semantic_closeout: checkpoint}}); + assert.deepEqual(plan.identity, before.identity); + assert.equal(plan.steps.length, before.steps.length); + for (const i of [0, 2, 3]) assert.deepEqual(plan.steps[i], before.steps[i]); + assert.equal(plan.steps[1].command_template, before.steps[1].command_template); + assert.equal(plan.steps[1].semantic_closeout_command_template, checkpoint); + assert.deepEqual(plan.steps[1].vision_authoring, visionAuthoringContract()); + const serialized = settlementPlanPayload(plan).ordered_steps as Record[]; + assert.equal(serialized[1].semantic_closeout_command_template, checkpoint); + assert.match(visionAuthoringContract().continuation_review as string, + /usable candidate is a checkpoint.*full marginal cost/); + assert.match(visionAuthoringContract().continuation_review as string, + /no useful authorized step remains/); + } + assert.throws(() => turnScopedCliSettlementPlan({...input, + command_templates: {...input.command_templates, semantic_closeout: ""}}), /must be a non-empty string/); +}); + test("Todo-bound semantic planning shares its exact autonomous authoring contract", () => { const ordinary = turnScopedCliSettlementPlan(input); assert.deepEqual(turnScopedCliSettlementPlan({...input, autonomous_replan: false}), ordinary); From 2b636206635aefc93436b8fd362b5fa66b965cbd Mon Sep 17 00:00:00 2001 From: song Date: Mon, 2 Nov 2026 22:59:45 +0800 Subject: [PATCH 2/5] fix(todos): show semantic checkpoint recovery in Markdown Signed-off-by: song --- loopx/control_plane/todos/markdown.py | 2 ++ .../test_material_feedback_checkpoint.py | 15 ++++++-- .../test_todo_settlement_markdown.py | 36 +++++++++++++++++++ 3 files changed, 51 insertions(+), 2 deletions(-) diff --git a/loopx/control_plane/todos/markdown.py b/loopx/control_plane/todos/markdown.py index 9d8c2b9921..72b3630da7 100644 --- a/loopx/control_plane/todos/markdown.py +++ b/loopx/control_plane/todos/markdown.py @@ -44,6 +44,8 @@ def _render_settlement_plan(plan: object) -> list[str]: lines.append(f"- {step.get('kind')}: {step.get('precondition')}") if step.get("command_template"): lines.append(f" - command{f' ({condition})' if condition else ''}: `{step['command_template']}`") + if step.get("semantic_closeout_command_template"): + lines.append(f" - semantic closeout: `{step['semantic_closeout_command_template']}`") return lines diff --git a/tests/control_plane/test_material_feedback_checkpoint.py b/tests/control_plane/test_material_feedback_checkpoint.py index 9dc9bfb3d5..f27cbcb98d 100644 --- a/tests/control_plane/test_material_feedback_checkpoint.py +++ b/tests/control_plane/test_material_feedback_checkpoint.py @@ -20,7 +20,8 @@ @pytest.mark.parametrize("provider", ["file", "sqlite"]) @pytest.mark.parametrize("strategy", ["off", "evidence_conditioned_v1"]) -def test_feedback_checkpoint_reaches_next_actual_experiment(tmp_path, monkeypatch, provider, strategy): +@pytest.mark.parametrize("handoff_mode", ["soft_claim", "hard_lease"]) +def test_feedback_checkpoint_reaches_next_actual_experiment(tmp_path, monkeypatch, provider, strategy, handoff_mode): isolate_sqlite_runtime(tmp_path, monkeypatch) project, runtime, registry = fixture._write_fixture(tmp_path) configuration = json.loads(registry.read_text()) @@ -30,7 +31,7 @@ def test_feedback_checkpoint_reaches_next_actual_experiment(tmp_path, monkeypatc state.write_text(state.read_text().replace("Settle one standard Codex App delivery.", "Minimize route cost on the supplied graph while retaining a valid route. " "A feasible candidate is a checkpoint; stop when no useful authorized comparison remains.")) - fixture._initialize_fixture_authority(project, runtime, provider=provider) + fixture._initialize_fixture_authority(project, runtime, provider=provider, handoff_mode=handoff_mode) monkeypatch.chdir(project) binding = ("--goal-id", fixture.GOAL_ID, "--agent-id", fixture.AGENT_ID) @@ -49,6 +50,12 @@ def execute(command, **values): cli("configure-goal", "--goal-id", fixture.GOAL_ID, "--execution-replan-after-turns", "1", "--execution-stride-strategy", strategy, "--explore-graph-enabled", "--execute") + if handoff_mode == "hard_lease": + claimed = cli("todo", "claim", *binding, "--todo-id", fixture.TODO_ID, + "--claimed-by", fixture.AGENT_ID, "--claim-operation-id", "route-original-execution") + assert claimed["execution_managed"] and claimed["claim_operation_id"] == "route-original-execution" + lease_before = cli("task-lease", "inspect", "--goal-id", fixture.GOAL_ID, "--todo-id", fixture.TODO_ID) + assert lease_before["active"] route = project / "route.json" graph = {"start": {"middle": 1, "finish": 1}, "middle": {"finish": 1}, "finish": {}} (project / "graph.json").write_text(json.dumps(graph)) @@ -115,6 +122,10 @@ def execute(command, **values): execute(plan["ordered_steps"][2]["command_template"]) assert fixture._spend_run_count(runtime) == 1 assert cli("todo", "list", "--goal-id", fixture.GOAL_ID, "--todo-id", fixture.TODO_ID)["todo"]["status"] == "open" + if handoff_mode == "hard_lease": + lease_after = cli("task-lease", "inspect", "--goal-id", fixture.GOAL_ID, "--todo-id", fixture.TODO_ID) + assert lease_after["lease"]["idempotency_key"] == lease_before["lease"]["idempotency_key"] + assert lease_after["lease"]["lease_epoch"] == lease_before["lease"]["lease_epoch"] # A new Host wake has a new Turn and the same Goal/Agent/Todo lane. The # ordinary cadence reads the material result; raw experiment counts do not. review = cli("quota", "should-run", *binding, "--runtime-profile", "generic_cli", diff --git a/tests/control_plane/test_todo_settlement_markdown.py b/tests/control_plane/test_todo_settlement_markdown.py index 098d2fbdf6..a612b44962 100644 --- a/tests/control_plane/test_todo_settlement_markdown.py +++ b/tests/control_plane/test_todo_settlement_markdown.py @@ -1,8 +1,14 @@ """Settlement guidance renders the typed plan without changing its semantics.""" from __future__ import annotations +import os +import subprocess +import sys + import pytest +from canonical_authority_fixture import isolate_sqlite_runtime +import test_quota_settlement_cli as fixture from loopx.control_plane.todos.markdown import render_todo_markdown @@ -24,6 +30,7 @@ def test_settlement_recovery_preserves_order_conditions_and_commands() -> None: "precondition": "write back the validated result", "conditional": True, "command_template": "loopx refresh-state --todo-id todo_fixture", + "semantic_closeout_command_template": "loopx refresh-state --todo-id todo_fixture --delivery-boundary semantic_closeout", }, {"kind": "quota_spend", "precondition": "writeback receipt exists"}, { @@ -41,6 +48,7 @@ def test_settlement_recovery_preserves_order_conditions_and_commands() -> None: " - command (todo_deliverable_complete): `loopx todo complete --todo-id todo_fixture`\n" "- durable_writeback: write back the validated result\n" " - command (conditional): `loopx refresh-state --todo-id todo_fixture`\n" + " - semantic closeout: `loopx refresh-state --todo-id todo_fixture --delivery-boundary semantic_closeout`\n" "- quota_spend: writeback receipt exists\n" "- terminal_closeout: all settlement receipts exist\n" " - command: `loopx todo complete --no-follow-up`" @@ -64,3 +72,31 @@ def test_empty_typed_settlement_plan_keeps_its_heading() -> None: assert render_todo_markdown( {"error": "incomplete", "settlement_plan": {"schema_version": "quota_settlement_plan_v1"}} ).endswith("\n\n## Same-Turn settlement plan\n") + + +@pytest.mark.parametrize("handoff_mode", ["soft_claim", "hard_lease"]) +def test_real_cli_recovery_markdown_exposes_the_same_semantic_command(tmp_path, monkeypatch, handoff_mode): + isolate_sqlite_runtime(tmp_path, monkeypatch) + project, runtime, registry = fixture._write_fixture(tmp_path) + fixture._initialize_fixture_authority(project, runtime, provider="sqlite", handoff_mode=handoff_mode) + binding = ["--goal-id", fixture.GOAL_ID, "--agent-id", fixture.AGENT_ID, + "--todo-id", fixture.TODO_ID, "--turn-instance-id", "markdown-feedback"] + if handoff_mode == "hard_lease": + code, claimed = fixture._run_cli(registry, runtime, "todo", "claim", "--goal-id", fixture.GOAL_ID, + "--todo-id", fixture.TODO_ID, "--agent-id", fixture.AGENT_ID, "--claimed-by", fixture.AGENT_ID, + "--claim-operation-id", "markdown-execution", cwd=project) + assert code == 0 and claimed["execution_managed"], claimed + code, guard = fixture._run_cli(registry, runtime, "quota", "should-run", *binding, + "--runtime-profile", "generic_cli", cwd=project) + assert code == 0 and guard["should_run"], guard + complete = ["todo", "complete", *binding, "--no-follow-up", "--evidence", "evidence:route-feedback"] + if handoff_mode == "hard_lease": + complete += ["--claim-operation-id", "markdown-execution"] + code, recovery = fixture._run_cli(registry, runtime, *complete, cwd=project) + assert code == 1, recovery + expected = recovery["settlement_plan"]["ordered_steps"][1]["semantic_closeout_command_template"] + markdown = subprocess.run([sys.executable, "-m", "loopx.entrypoint", "--registry", str(registry), + "--runtime-root", str(runtime), "--format", "markdown", *complete], cwd=project, + env={**os.environ, "PYTHONPATH": str(fixture.REPO_ROOT)}, capture_output=True, text=True) + assert markdown.returncode == 1, markdown.stdout + assert "semantic closeout: `" + expected + "`" in markdown.stdout From 259f49183ffa7ae7c310d796d9f000b293577f24 Mon Sep 17 00:00:00 2001 From: song Date: Tue, 3 Nov 2026 00:42:35 +0800 Subject: [PATCH 3/5] fix(goals): scope semantic checkpoints to new open-Todo progress Signed-off-by: song --- benchmark/runtime/EXECUTION_OBSERVATIONS.md | 16 +++- .../goal-vision-replan-contract-v0.md | 31 +++++-- .../control_plane/effect_runtime_handlers.ts | 5 + .../control_plane/goal_vision_lifecycle.json | 3 + .../control_plane/goals/goal_vision_state.py | 14 +-- .../control_plane/goals/vision_checkpoint.ts | 39 +++++++- loopx/control_plane/quota/settlement_plan.ts | 10 +- .../quota/settlement_validation.py | 25 +++++ .../completion_validation_accountability.py | 9 +- loopx/state_refresh.py | 40 +++++--- .../test_material_feedback_checkpoint.py | 63 +++++++++++-- .../open_todo_semantic_checkpoint.test.ts | 92 +++++++++++++++++++ .../quota_settlement_plan.test.ts | 12 ++- 13 files changed, 302 insertions(+), 57 deletions(-) create mode 100644 loopx/control_plane/goal_vision_lifecycle.json create mode 100644 tests/control_plane_ts/open_todo_semantic_checkpoint.test.ts diff --git a/benchmark/runtime/EXECUTION_OBSERVATIONS.md b/benchmark/runtime/EXECUTION_OBSERVATIONS.md index aab7dd6455..d4f1aeddd9 100644 --- a/benchmark/runtime/EXECUTION_OBSERVATIONS.md +++ b/benchmark/runtime/EXECUTION_OBSERVATIONS.md @@ -39,8 +39,10 @@ merely to produce an attachment. Configuration and readback invoke neither a probe nor a model. Runtime receipts record the selected producer, which does not attest execution, adoption or improvement. -Decision-relevant released feedback closes the current work segment before the -next experiment or closeout decision. The ordinary settlement plan's +New decision evidence produced or obtained by the current admitted work can +close its work segment. Prior-feedback readback, bookkeeping and deterministic +repetition without new information are context, not this Turn's progress; use +them to advance the original task first. The ordinary settlement plan's `durable_writeback.semantic_closeout_command_template` selects that boundary with the original Turn, Todo and Goal identity; fill its result and Vision placeholders, then follow the same plan's remaining steps and yield to the Host. @@ -50,12 +52,14 @@ The next guarded wake can continue the same Goal/session or perform an existing due review; neither feedback counts nor this command enable Stride. Repeated unchanged polls and bookkeeping alone need no new checkpoint. -For an explicitly continuing optimization objective, a usable candidate is a -checkpoint. Before stopping, review remaining acceptance gaps, plausible +For an explicitly continuing optimization objective, candidate usability alone +does not establish completion. Before stopping, review remaining acceptance gaps, plausible headroom, feasible experiments, full marginal cost and the accepted stopping condition. Stop when justified; do not force a pivot or use the remaining time merely because it is available. This is Agent-consumed execution guidance, not automatic tool-result classification or proof that a model adopted it. +Native model adoption, automatic long-Turn supervision and outcome benefit +remain unqualified; scripted lifecycle checks do not establish them. ## Retiring the bundled research treatment @@ -188,7 +192,8 @@ Stop、原预算和真实阻塞继续有效,不为填满时长制造任务。 变化拒绝归因;文件清单哈希不是原生归档哈希,只覆盖明确选择的文件及探针 边界,不覆盖传递依赖或独立写入者,结果仍须证明对应实际评测快照和输入版本。 -影响下一实验或收尾判断的已释放反馈,是当前工作段的结算边界。使用当前 +当前授权工作实际产生或取得新的决策证据后,才进入相应工作段的检查点。旧反馈读回、 +记账和没有新增信息的确定性重复验证只是上下文,应先用于推进原任务。使用当前 settlement plan 的 `durable_writeback.semantic_closeout_command_template`, 保留原 Turn、Todo、Goal 身份,填写实际结果和 Vision,再按同一 plan 的剩余 步骤结算并让宿主接续;未完成的 Todo 保持 open。即使路线不变,也在既有 @@ -197,6 +202,7 @@ Vision/path delta 中记录被评估版本、输入和决策。下一次 guard 无需新 checkpoint。明确持续优化的目标不能因已有可用候选就自动收尾;结束前 评估剩余验收、改进空间、可行实验、完整边际成本和原停止条件。合理时可停止, 不强制换路或用满时间。这是 Agent 执行指导,不是自动分类工具结果或模型已采用的证明。 +原生模型采用、自动长轮次监督和结果收益仍未取得资格,脚本路径通过不能替代这些证据。 观测通过既有 Explore 写回、replan 和研究历程保留,输入版本与引用必须属于 该 finding,计划/实际方法不同须记录理由。finding 引用及观测的 `evidence_refs`、 diff --git a/docs/reference/protocols/goal-vision-replan-contract-v0.md b/docs/reference/protocols/goal-vision-replan-contract-v0.md index 7e913b6f3b..9d298000f8 100644 --- a/docs/reference/protocols/goal-vision-replan-contract-v0.md +++ b/docs/reference/protocols/goal-vision-replan-contract-v0.md @@ -903,16 +903,26 @@ with `--delivery-boundary semantic_closeout --agent-vision-json ` selected It is an alternative for that step, not another plan, claim, debit or admission. Material CLI closeout and MCP `complete_task` retain their existing evidence gates. -Released feedback affecting the next experiment or closeout decision ends the -current work segment. Bind it to the evaluated artifact/input, record the actual +New decision evidence produced or obtained by the current admitted work can end +its work segment. Prior-feedback readback, bookkeeping and deterministic +repetition without new information are context, not this Turn's progress; use +them to advance the original task first. Bind the new result to the evaluated artifact/input, record the actual decision in Vision/path delta even when retaining the route, execute the current plan's writeback/spend and yield for a freshly guarded Host continuation. Leave an unfinished Todo open. Repeated unchanged polls and bookkeeping alone need no new checkpoint. This is Agent execution guidance, not automatic classification of tool text, a new Stride trigger, or proof of adoption or useful outcomes. -For an explicitly continuing or optimization objective, a usable candidate is a -checkpoint. Before stopping, compare unmet acceptance, plausible headroom, +An open Todo with controller-declared completion validation can use this +semantic checkpoint for `outcome_progress` with a fresh, open Vision and an +evidence-linked `continue` or `no_change` path. The shared settlement owner +qualifies the admitted writeback and its exact receipted spend through the same +rule. This closes only the original Turn: the Todo remains open and its final +completion validator remains pending. Primary-outcome, closed-Vision and +terminal declarations retain that validator gate. + +For an explicitly continuing or optimization objective, candidate usability +alone does not establish completion. Before stopping, compare unmet acceptance, plausible headroom, feasible next work and full marginal cost with the original stopping condition. Retain an evidenced no-follow-up, Stop, budget/permission limit or genuine wait; neither a submission nor unused budget decides whether further work is useful. @@ -920,14 +930,21 @@ neither a submission nor unused budget decides whether further work is useful. 普通 CLI Todo 结算在默认 in-flight 边界下也提供既有 Vision 编写结构;写回步骤的 `semantic_closeout_command_template` 沿用原 Goal、Todo、Turn,只显式选择 semantic closeout 与 Vision 文件。它是当前步骤的可执行变体,不增加 plan、认领、 -扣额或准入。影响下一实验或收尾判断的已释放反馈结束当前工作段;绑定评估版本与 +扣额或准入。当前授权工作实际产生或取得新的决策证据后,才进入相应工作段的 +检查点;旧反馈读回、记账与没有新增信息的确定性重验只是上下文,应先用于推进原 +任务。绑定新结果的评估版本与 输入,即使保留路线也记录实际 Vision/path 决策,再按原计划写回、扣额并让宿主 以新 guard 接续,未完成 Todo 保持 open。未变化轮询、记账无需新 checkpoint。 -持续优化目标中的可用候选只是检查点;停止前对照剩余验收、改进空间、可行工作、 +持续优化目标不能仅因已有可用候选就宣布完成;停止前对照剩余验收、改进空间、可行工作、 完整边际成本和原停止条件,保留有依据的 no-follow-up、Stop、预算/权限限制与真实 等待。不强制用满预算或换路,也不由工具文本自动分类或新增 Stride trigger;执行 指导与模型实际采用、结果收益仍须分别验证。 +声明完成验证器的开放 Todo,可以用 fresh、open 的 Vision 和有证据关联的 +`continue` / `no_change` 路径记录 `outcome_progress`;准入写回与精确回执的扣额 +复用同一 TS 资格规则。它只结算原 Turn,不完成 Todo,也不满足最终完成验证器。 +primary outcome、已闭合 Vision 与终态声明仍须通过原完成验证门。 + One explicit exception exists for in-flight delivery. When quota admits an open advancement Todo for normal delivery, its settlement guidance may add the boundary below. After a resulting `delivery_outcome=outcome_progress`, the next @@ -956,7 +973,7 @@ decision merely because the scheduler woke up. Agents must start from projected boundary and identity flags; reconstructing a generic `semantic_closeout` command discards that continuity contract. -If decision-relevant feedback arrived, the work actually replanned or completed the Todo, explicitly choose +If this admitted work obtains new decision evidence, actually replans or completes the Todo, explicitly choose `semantic_closeout` and provide its required vision checkpoint. Do not simply remove the replan ACK to make a real replan pass as in-flight work. Conversely, ordinary within-Todo progress must not add `--autonomous-replan-recorded`. diff --git a/loopx/control_plane/effect_runtime_handlers.ts b/loopx/control_plane/effect_runtime_handlers.ts index 125e4caeef..edd439015c 100644 --- a/loopx/control_plane/effect_runtime_handlers.ts +++ b/loopx/control_plane/effect_runtime_handlers.ts @@ -759,6 +759,11 @@ export function createEffectRuntimeHandlers( requiredString(params.expected_effect_id, "expected_effect_id"), ), ], + [ + "settlement.open_todo_semantic_checkpoint", + lazyHandler(() => import("./goals/vision_checkpoint.ts"), ({qualifiesOpenTodoSemanticCheckpoint}) => (params) => + qualifiesOpenTodoSemanticCheckpoint(params.writeback, settlementIdentity(settlementIdentityInput(params.identity)))), + ], [ "settlement.receipt_bound_monitor_phase", lazyHandler(() => import("./quota/settlement_phase.ts"), ({receiptBoundMonitorPhase}) => (params) => receiptBoundMonitorPhase({ diff --git a/loopx/control_plane/goal_vision_lifecycle.json b/loopx/control_plane/goal_vision_lifecycle.json new file mode 100644 index 0000000000..a2c3f8980f --- /dev/null +++ b/loopx/control_plane/goal_vision_lifecycle.json @@ -0,0 +1,3 @@ +{ + "closed_states": ["vision_closed", "retired", "retired_or_superseded", "superseded", "no_followup"] +} diff --git a/loopx/control_plane/goals/goal_vision_state.py b/loopx/control_plane/goals/goal_vision_state.py index ae8306dc7d..238b94c120 100644 --- a/loopx/control_plane/goals/goal_vision_state.py +++ b/loopx/control_plane/goals/goal_vision_state.py @@ -1,19 +1,15 @@ from __future__ import annotations +from importlib.resources import files +import json import re from typing import Any GOAL_VISION_DEFAULT_STATE = "vision_patch_proposed" -GOAL_VISION_CLOSED_STATES = frozenset( - { - "vision_closed", - "retired", - "retired_or_superseded", - "superseded", - "no_followup", - } -) +GOAL_VISION_CLOSED_STATES = frozenset(json.loads( + files("loopx.control_plane").joinpath("goal_vision_lifecycle.json").read_text(encoding="utf-8") +)["closed_states"]) GOAL_VISION_SUCCESSOR_REQUIRED_STATES = frozenset({"vision_closed"}) GOAL_VISION_STATE_ALIASES = { "closed": "vision_closed", diff --git a/loopx/control_plane/goals/vision_checkpoint.ts b/loopx/control_plane/goals/vision_checkpoint.ts index c933ec49e5..61b30df588 100644 --- a/loopx/control_plane/goals/vision_checkpoint.ts +++ b/loopx/control_plane/goals/vision_checkpoint.ts @@ -1,6 +1,7 @@ -import type { JsonObject } from "../effect_program.ts"; +import {settlementIdentityPayload, type JsonObject, type SettlementIdentity} from "../effect_program.ts"; import { EffectRuntimeRequestError } from "../effect_runtime_errors.ts"; -import { isBoundedBlockedRetry } from "../quota/settlement_phase.ts"; +import {jsonObject} from "../runtime_decode.ts"; +import { isAcceptedProgressWriteback, isBoundedBlockedRetry } from "../quota/settlement_phase.ts"; import { DELIVERY_BOUNDARIES, type DeliveryBoundary, @@ -12,6 +13,7 @@ import { } from "../work_items/delivery_outcome.ts"; import {normalizeStrideDecision, STRIDE_DECISION_SCHEMA, RECORDED_STRIDE_SCHEMA, STRIDE_PROBE_LIMITS, STRIDE_AXIS_NAMES, STRIDE_PLAN_LIMIT, STRIDE_REASON_LIMIT, STRIDE_REVIEW_LIMIT} from "../work_items/stride_policy.ts"; +import lifecycle from "../goal_vision_lifecycle.json" with {type: "json"}; export const VISION_REFRESH_REQUEST_SCHEMA = "loopx_vision_refresh_request_v0"; export const VISION_REFRESH_PREPARED_SCHEMA_VERSION = @@ -126,7 +128,7 @@ export function visionAuthoringContract(autonomousReplan = false): JsonObject { ? `Autonomous review: changing ${GOAL_VISION_DURABLE_FIELDS.join(", ")} requires outcome=replan, even when the Goal objective stays unchanged. Retaining these fields permits continue/no_change. ` : "Changed mainline: outcome=replan. ") + "vision_closed closes a stage, not Goal; no_followup: no scoped work.", - continuation_review: "For an explicitly continuing or optimization objective, a usable candidate is a checkpoint. " + + continuation_review: "For an explicitly continuing or optimization objective, candidate usability alone does not establish completion. " + "Before stop/no_followup, compare remaining acceptance gaps, plausible headroom, a feasible next experiment " + "and its full marginal cost with the accepted stopping condition. Record the judgment and evidence in path_delta; " + "continue or replan useful authorized work. A submission or unused budget alone proves neither completion nor " + @@ -414,6 +416,37 @@ function normalizeGoalVisionState(value: unknown): string { return GOAL_VISION_STATE_ALIASES[state] ?? state; } +/** The existing lifecycle vocabulary is shared with the Python read model. */ +export function goalVisionStateIsOpen(value: unknown): boolean { + try { return !lifecycle.closed_states.includes(normalizeGoalVisionState(value)); } + catch { return false; } +} + +/** Qualify an open Todo's evidence checkpoint without accepting its deliverable. + * Write-time callers supply the admitted identity and prepared Vision; spend + * callers first verify the exact durable receipt. Terminal validation remains + * with Todo completion. An unchanged reason alone is not new decision evidence. */ +export function qualifiesOpenTodoSemanticCheckpoint(value: unknown, identity: SettlementIdentity): boolean { + const run = jsonObject(value), checkpoint = jsonObject(run?.vision_checkpoint); + const persisted = jsonObject(run?.settlement_identity), vision = jsonObject(run?.agent_vision); + if (!isAcceptedProgressWriteback(run, identity) || run?.completion_todo_id != null || + checkpoint?.delivery_boundary !== "semantic_closeout" || checkpoint.required !== true || + checkpoint.decision !== "patched" || !persisted || + Object.entries(settlementIdentityPayload(identity)).some(([key, value]) => persisted[key] !== value) || + vision?.schema_version !== "goal_vision_replan_contract_v0" || + // Compact run history omits this redundant Goal field; the exact run + // and settlement identity above still bind it. Present conflicts fail. + (vision.goal_id != null && vision.goal_id !== identity.goal_id) || + vision.agent_id !== identity.agent_id || typeof vision.state !== "string" || !vision.state.trim() || + !goalVisionStateIsOpen(vision.state)) return false; + const acceptance = jsonObject(vision.vision_patch)?.acceptance_summary; + let path; + try { [path] = normalizeGoalPathDelta(vision.path_delta); } catch { return false; } + return typeof acceptance === "string" && !!acceptance.trim() && !!path && + (path.outcome === "continue" || path.outcome === "no_change") && + Array.isArray(path.evidence_refs) && path.evidence_refs.length > 0; +} + function normalizeAdvancementPolicy(value: unknown): string { const candidate = compactText(value).toLowerCase().replaceAll("-", "_"); if (!GOAL_VISION_ADVANCEMENT_POLICIES.some(policy => policy === candidate)) { diff --git a/loopx/control_plane/quota/settlement_plan.ts b/loopx/control_plane/quota/settlement_plan.ts index 8cb8ba73af..1fb3fd16b2 100644 --- a/loopx/control_plane/quota/settlement_plan.ts +++ b/loopx/control_plane/quota/settlement_plan.ts @@ -83,10 +83,10 @@ export function turnScopedCliSettlementPlan(params: JsonObject): SettlementPlan "an artifact pointer; --result-file requires approved Goal acceptance criteria bound to this Todo, " + "not merely a Todo validator. No invented successor " + "or --no-follow-up is required. Todo done does not settle the Turn. " + - "Released feedback that changes or justifies the next experiment or closeout is a work-segment boundary: " + - "bind it to the evaluated artifact/input, then execute this plan's writeback and spend before the next decision. " + - "Keep an unfinished Todo open; after settlement yield to the Host for a freshly guarded continuation. " + - "Unchanged polls and bookkeeping alone create no checkpoint. " + + "Checkpoint new decision evidence produced or obtained by this admitted work, bound to its artifact/input. " + + "Prior-feedback readback, bookkeeping and deterministic repetition without new information are context, " + + "not this Turn's progress; use them to advance the task first. After that progress, use this plan's writeback/spend; " + + "keep an unfinished Todo open, then yield for freshly guarded continuation. " + (inFlight ? "For unfinished bounded progress, keep the Todo open with its original declaration. " + "The projected in-flight boundary is not an instruction to retain completed work: " + "when complete or changing route, use --delivery-boundary semantic_closeout with an " + @@ -101,7 +101,7 @@ export function turnScopedCliSettlementPlan(params: JsonObject): SettlementPlan " Pass a new packet with --agent-vision-json.") + " Route elimination needs evidence; failure alone is not progress. " + (autonomousReplan ? "Autonomous replan uses --delivery-boundary semantic_closeout even while its Todo remains open; " - : "For decision-relevant feedback use --delivery-boundary semantic_closeout and a fresh evidence-linked " + + : "For that new decision evidence use --delivery-boundary semantic_closeout and a fresh evidence-linked " + "Vision/path_delta with --agent-vision-json, including when the route stays unchanged. " + "Use this step's semantic_closeout_command_template when present; follow the remaining plan unchanged. " + "Other outcome_progress may use in_flight_continuation; ") + "outcome_gap requires " + diff --git a/loopx/control_plane/quota/settlement_validation.py b/loopx/control_plane/quota/settlement_validation.py index 8b4e80f90c..199c597420 100644 --- a/loopx/control_plane/quota/settlement_validation.py +++ b/loopx/control_plane/quota/settlement_validation.py @@ -4,12 +4,33 @@ from typing import Any +from ..effect_runtime import effect_runtime_result from ..todos.completion_validation_projection import ( pending_completion_validation_todo, ) from ..todos.contract import normalize_todo_id +def qualified_open_todo_semantic_checkpoint( + delivery_run: dict[str, Any] | None, *, goal_id: str, + todo_id: str, agent_id: str | None, +) -> bool: + """Transport prepared or exact receipted facts to the shared TS qualifier.""" + if not isinstance(delivery_run, dict) or agent_id is None: + return False + identity = delivery_run.get("settlement_identity") + if not isinstance(identity, dict): + return False + turn_id = identity.get("turn_instance_id") + if not isinstance(turn_id, str) or not turn_id.strip(): + return False + return effect_runtime_result("settlement.open_todo_semantic_checkpoint", { + "identity": {"goal_id": goal_id, "agent_id": agent_id, "todo_id": todo_id, + "turn_instance_id": turn_id}, + "writeback": delivery_run, + }) is True + + def _is_qualified_semantic_replan_writeback( delivery_run: dict[str, Any] | None, *, @@ -158,6 +179,10 @@ def completion_validation_spend_error( is not None for summary in summaries ): + if qualified_open_todo_semantic_checkpoint( + delivery_run, goal_id=goal_id, todo_id=normalized_todo_id, agent_id=agent_id, + ): + return None return ( "quota spend is blocked until controller-declared completion " f"validation durably completes todo {normalized_todo_id}" diff --git a/loopx/control_plane/todos/completion_validation_accountability.py b/loopx/control_plane/todos/completion_validation_accountability.py index d08fb5ade9..d073f9bdfb 100644 --- a/loopx/control_plane/todos/completion_validation_accountability.py +++ b/loopx/control_plane/todos/completion_validation_accountability.py @@ -4,6 +4,7 @@ from typing import Any +from ..quota.settlement_validation import qualified_open_todo_semantic_checkpoint from ..turn_driver.delivery_continuity import DELIVERY_BOUNDARY_IN_FLIGHT from ..work_items.delivery_outcome import DeliveryOutcome from .active_state_todo_parser import parse_active_state_todos @@ -19,8 +20,10 @@ def require_accountable_completion_validation( delivery_boundary: str | None = None, delivery_outcome: str | None = None, semantic_replan_recorded: bool = False, + goal_id: str | None = None, + progress_writeback: dict[str, Any] | None = None, ) -> None: - """Reject accountable evidence while its exact validation Todo is open.""" + """Keep terminal validation pending while qualifying scoped open-Todo progress.""" # A qualified semantic replan updates the path for an open Todo; it does # not claim that Todo completed. Its write-time qualification already @@ -42,6 +45,10 @@ def require_accountable_completion_validation( agent_id=agent_id, ) if pending is not None: + if goal_id is not None and qualified_open_todo_semantic_checkpoint( + progress_writeback, goal_id=goal_id, todo_id=str(pending.get("todo_id")), agent_id=agent_id, + ): + return raise ValueError( "accountable refresh is blocked until controller-declared completion " f"validation durably completes todo {pending.get('todo_id')}" diff --git a/loopx/state_refresh.py b/loopx/state_refresh.py index b172e508a2..db213b74fc 100644 --- a/loopx/state_refresh.py +++ b/loopx/state_refresh.py @@ -1205,21 +1205,6 @@ def read_explore_context(read_goal_id: str, read_agent_id: str | None): # mutations and conflicting vision decisions before this fence. The # supplement repairs only the missing vision decision, so terminal # validation remains attached to the original Todo completion. - if ( - normalized_delivery_outcome in ACCOUNTABLE_DELIVERY_OUTCOMES - and not checkpoint_supplement - ): - require_accountable_completion_validation( - state_text, - todo_fields=todo_fields, - todo_id=(settlement_identity.todo_id if settlement_identity else None), - agent_id=normalized_agent_id or None, - delivery_boundary=normalized_delivery_boundary, - delivery_outcome=normalized_delivery_outcome, - semantic_replan_recorded=( - effective_autonomous_replan_recorded - ), - ) vision_checkpoint = build_vision_checkpoint( agent_id=normalized_agent_id or None, agent_vision=agent_vision, @@ -1233,6 +1218,31 @@ def read_explore_context(read_goal_id: str, read_agent_id: str | None): autonomous_replan_recorded=effective_autonomous_replan_recorded, blocked_retry=blocked_retry, ) + if ( + normalized_delivery_outcome in ACCOUNTABLE_DELIVERY_OUTCOMES + and not checkpoint_supplement + ): + require_accountable_completion_validation( + state_text, + todo_fields=todo_fields, + todo_id=(settlement_identity.todo_id if settlement_identity else None), + agent_id=normalized_agent_id or None, + delivery_boundary=normalized_delivery_boundary, + delivery_outcome=normalized_delivery_outcome, + semantic_replan_recorded=effective_autonomous_replan_recorded, + goal_id=safe_goal_id, + progress_writeback=({ + "goal_id": safe_goal_id, + "agent_id": normalized_agent_id, + "todo_id": settlement_identity.todo_id, + "turn_instance_id": settlement_identity.turn_instance_id, + "settlement_identity": settlement_identity.as_dict(), + "delivery_outcome": normalized_delivery_outcome, + "vision_checkpoint": vision_checkpoint, + "agent_vision": agent_vision, + "completion_todo_id": completion_todo_id, + } if settlement_identity is not None else None), + ) if checkpoint_supplement and not vision_checkpoint.get("satisfied"): raise ValueError( "checkpoint supplement did not satisfy the missing decision; " diff --git a/tests/control_plane/test_material_feedback_checkpoint.py b/tests/control_plane/test_material_feedback_checkpoint.py index f27cbcb98d..434f0acb17 100644 --- a/tests/control_plane/test_material_feedback_checkpoint.py +++ b/tests/control_plane/test_material_feedback_checkpoint.py @@ -6,9 +6,11 @@ from __future__ import annotations import hashlib +from itertools import product import json from pathlib import Path import shlex +import subprocess import sys import pytest @@ -18,10 +20,12 @@ import test_quota_settlement_cli as fixture -@pytest.mark.parametrize("provider", ["file", "sqlite"]) -@pytest.mark.parametrize("strategy", ["off", "evidence_conditioned_v1"]) -@pytest.mark.parametrize("handoff_mode", ["soft_claim", "hard_lease"]) -def test_feedback_checkpoint_reaches_next_actual_experiment(tmp_path, monkeypatch, provider, strategy, handoff_mode): +@pytest.mark.parametrize(("provider", "strategy", "handoff_mode", "preexisting_feedback"), [ + (*case, False) for case in product(["file", "sqlite"], ["off", "evidence_conditioned_v1"], ["soft_claim", "hard_lease"]) +] + [pytest.param("sqlite", "evidence_conditioned_v1", "hard_lease", True, id="historical-feedback-is-context")]) +def test_feedback_checkpoint_reaches_next_actual_experiment( + tmp_path, monkeypatch, provider, strategy, handoff_mode, preexisting_feedback, +): isolate_sqlite_runtime(tmp_path, monkeypatch) project, runtime, registry = fixture._write_fixture(tmp_path) configuration = json.loads(registry.read_text()) @@ -31,6 +35,7 @@ def test_feedback_checkpoint_reaches_next_actual_experiment(tmp_path, monkeypatc state.write_text(state.read_text().replace("Settle one standard Codex App delivery.", "Minimize route cost on the supplied graph while retaining a valid route. " "A feasible candidate is a checkpoint; stop when no useful authorized comparison remains.")) + fixture._configure_completion_validation_todo(project) fixture._initialize_fixture_authority(project, runtime, provider=provider, handoff_mode=handoff_mode) monkeypatch.chdir(project) binding = ("--goal-id", fixture.GOAL_ID, "--agent-id", fixture.AGENT_ID) @@ -57,7 +62,8 @@ def execute(command, **values): lease_before = cli("task-lease", "inspect", "--goal-id", fixture.GOAL_ID, "--todo-id", fixture.TODO_ID) assert lease_before["active"] route = project / "route.json" - graph = {"start": {"middle": 1, "finish": 1}, "middle": {"finish": 1}, "finish": {}} + graph = {"start": {"detour": 1, "middle": 1, "finish": 1}, + "detour": {"middle": 1}, "middle": {"finish": 1}, "finish": {}} (project / "graph.json").write_text(json.dumps(graph)) validator = project / "evaluate.py" validator.write_text( @@ -66,7 +72,13 @@ def execute(command, **values): "assert r[0]=='start' and r[-1]=='finish' and len(r)==len(set(r))\n" "cost=sum(g[a][b] for a,b in zip(r,r[1:]))\n" "print(json.dumps({'valid':True,'cost':cost}))\n") - route.write_text(json.dumps(["start", "middle", "finish"])) + route.write_text(json.dumps(["start", "detour", "middle", "finish"] if preexisting_feedback + else ["start", "middle", "finish"])) + if preexisting_feedback: + previous = subprocess.run([sys.executable, str(validator)], capture_output=True, text=True, check=True) + prior = project / "released-before-admission.json" + prior.write_text(previous.stdout) + assert json.loads(prior.read_text()) == {"valid": True, "cost": 3} guard = cli("quota", "should-run", *binding, "--runtime-profile", "generic_cli", "--todo-id", fixture.TODO_ID, "--turn-instance-id", "route-feedback-1") assert guard["should_run"] and guard["selected_todo"]["todo_id"] == fixture.TODO_ID @@ -77,11 +89,26 @@ def execute(command, **values): assert writeback["vision_authoring"]["continuation_review"] # Merely obtaining the new executable projection performs no work or writeback. assert not (runtime / "goals" / fixture.GOAL_ID / "runs/index.jsonl").exists() + if preexisting_feedback: + guidance = plan["ordered_steps"][0]["precondition"] + assert "not this Turn's progress; use them to advance the task first" in guidance + repeated = run_probe(command=[sys.executable, str(validator)], candidate_files=[route], + output=project / "prior-recheck.json", input_revision="graph-v1", planned_method_id="retained-route", + executed_method_id="retained-route", evidence_ref="probe:prior-recheck", limit_seconds=10) + assert json.loads((project / "prior-recheck.json.stdout").read_text()) == json.loads(prior.read_text()) + # Reading and deterministically reproducing the known result adds no + # decision evidence here. Advance the actual route before checkpointing. + assert not (runtime / "goals" / fixture.GOAL_ID / "runs/index.jsonl").exists() + assert fixture._spend_run_count(runtime) == 0 + route.write_text(json.dumps(["start", "middle", "finish"])) observed = run_probe(command=[sys.executable, str(validator)], candidate_files=[route], output=project / "probe-1.json", input_revision="graph-v1", planned_method_id="retained-route", executed_method_id="retained-route", evidence_ref="probe:route-1", limit_seconds=10) first = json.loads((project / "probe-1.json.stdout").read_text()) assert first == {"valid": True, "cost": 2} + if preexisting_feedback: + assert observed["candidate_sha256"] != repeated["candidate_sha256"] + assert json.loads(prior.read_text())["cost"] > first["cost"] evidence = "result:" + hashlib.sha256((project / "probe-1.json.stdout").read_bytes()).hexdigest() observed.update(result_ref=evidence, result_candidate_sha256=observed["candidate_sha256"]) observed["evidence_refs"].append(evidence) @@ -121,11 +148,18 @@ def execute(command, **values): execute(plan["ordered_steps"][2]["command_template"]) execute(plan["ordered_steps"][2]["command_template"]) assert fixture._spend_run_count(runtime) == 1 - assert cli("todo", "list", "--goal-id", fixture.GOAL_ID, "--todo-id", fixture.TODO_ID)["todo"]["status"] == "open" + retained_todo = cli("todo", "list", "--goal-id", fixture.GOAL_ID, "--todo-id", fixture.TODO_ID)["todo"] + assert retained_todo["status"] == "open" and retained_todo["completion_validation_required"] if handoff_mode == "hard_lease": lease_after = cli("task-lease", "inspect", "--goal-id", fixture.GOAL_ID, "--todo-id", fixture.TODO_ID) assert lease_after["lease"]["idempotency_key"] == lease_before["lease"]["idempotency_key"] assert lease_after["lease"]["lease_epoch"] == lease_before["lease"]["lease_epoch"] + if preexisting_feedback: + code, refused_completion = fixture._run_cli(registry, runtime, "todo", "complete", *binding, + "--todo-id", fixture.TODO_ID, "--turn-instance-id", "route-feedback-1", + "--claim-operation-id", "route-original-execution", "--evidence", "probe:route-1", cwd=project) + assert code == 1 and refused_completion.get("validation_blocked_completion"), refused_completion + assert cli("todo", "list", "--goal-id", fixture.GOAL_ID, "--todo-id", fixture.TODO_ID)["todo"]["status"] == "open" # A new Host wake has a new Turn and the same Goal/Agent/Todo lane. The # ordinary cadence reads the material result; raw experiment counts do not. review = cli("quota", "should-run", *binding, "--runtime-profile", "generic_cli", @@ -173,3 +207,18 @@ def execute(command, **values): assert changed["candidate_sha256"] != observed["candidate_sha256"] assert json.loads((project / "probe-2.json.stdout").read_text()) == {"valid": True, "cost": 1} assert first["cost"] == 2 # Retain the original result; no hindsight rewrite. + terminal = dict(packet, state="no_followup", path_delta={"outcome": "stop", + "prior_assumption": "The retained route could still be improved.", + "observed_reality": "The direct route now has cost one.", "stopped": ["This comparison is complete."], + "evidence_refs": ["probe:route-2"]}) + terminal.pop("stride_decision", None) + vision.write_text(json.dumps(terminal)) + refused_command = next_guard["interaction_contract"]["cli_channel"]["settlement_plan"]["ordered_steps"][1][ + "semantic_closeout_command_template"] + " --no-global-sync --suppress-external-sinks" + for key, value in dict(validated_progress="unvalidated_completion", scale="implementation", + outcome="primary_goal_outcome", file=vision).items(): + refused_command = refused_command.replace("<" + key + ">", shlex.quote(str(value))) + code, refused = fixture._run_generated_cli(refused_command, registry_path=registry, cwd=project) + assert code == 1 and "completion validation" in refused["error"], refused + assert cli("todo", "list", "--goal-id", fixture.GOAL_ID, "--todo-id", fixture.TODO_ID)["todo"]["status"] == "open" + assert fixture._classification_count(runtime, "unvalidated_completion") == 0 diff --git a/tests/control_plane_ts/open_todo_semantic_checkpoint.test.ts b/tests/control_plane_ts/open_todo_semantic_checkpoint.test.ts new file mode 100644 index 0000000000..9c59d01d22 --- /dev/null +++ b/tests/control_plane_ts/open_todo_semantic_checkpoint.test.ts @@ -0,0 +1,92 @@ +import assert from "node:assert/strict"; +import {spawnSync} from "node:child_process"; +import test from "node:test"; +import {fileURLToPath} from "node:url"; +import {resolveTestPython} from "../../scripts/test-python.mjs"; +import {settlementIdentity, settlementIdentityPayload, type JsonObject} from "../../loopx/control_plane/effect_program.ts"; +import {createEffectRuntimeHandlers, dispatchEffectRuntimeMethod} from "../../loopx/control_plane/effect_runtime_handlers.ts"; +import {goalVisionStateIsOpen, qualifiesOpenTodoSemanticCheckpoint} from "../../loopx/control_plane/goals/vision_checkpoint.ts"; + +const identity = settlementIdentity({goal_id: "goal", agent_id: "agent", todo_id: "todo_checkpoint", turn_instance_id: "turn"}); +const writeback: JsonObject = { + goal_id: identity.goal_id, agent_id: identity.agent_id, todo_id: identity.todo_id, + turn_instance_id: identity.turn_instance_id, settlement_identity: settlementIdentityPayload(identity), + delivery_outcome: "outcome_progress", + agent_vision: {schema_version: "goal_vision_replan_contract_v0", goal_id: "goal", agent_id: "agent", state: "active", + vision_patch: {acceptance_summary: "The current result advances the open comparison."}, + path_delta: {outcome: "continue", prior_assumption: "The incumbent may be sufficient.", + observed_reality: "A new comparison leaves further work open.", retained: ["Original acceptance and incumbent."], + evidence_refs: ["evidence:new-comparison"]}}, + vision_checkpoint: {schema_version: "vision_checkpoint_v0", agent_id: "agent", required: true, satisfied: true, + decision: "patched", delivery_boundary: "semantic_closeout", + triggers: [{kind: "material_delivery_outcome", delivery_outcome: "outcome_progress"}]}, +}; +const changed = (path: string[], value: unknown): JsonObject => { + const copy = structuredClone(writeback); + let owner = copy; + for (const key of path.slice(0, -1)) owner = owner[key] as JsonObject; + owner[path.at(-1)!] = value; + return copy; +}; + +test("open Todo semantic evidence has one read-only registered qualification path", async () => { + const handlers = createEffectRuntimeHandlers({fingerprint: "checkpoint-test", requestShutdown() {}}); + const before = structuredClone(writeback); + assert.equal(qualifiesOpenTodoSemanticCheckpoint(writeback, identity), true); + assert.equal(await dispatchEffectRuntimeMethod(handlers, "settlement.open_todo_semantic_checkpoint", + {identity, writeback}), true); + assert.deepEqual(writeback, before); + assert.equal(qualifiesOpenTodoSemanticCheckpoint(changed(["agent_vision", "path_delta", "outcome"], "no_change"), identity), true); + const compact = structuredClone(writeback); + delete (compact.agent_vision as JsonObject).goal_id; + assert.equal(qualifiesOpenTodoSemanticCheckpoint(compact, identity), true); + await assert.rejects(dispatchEffectRuntimeMethod(handlers, "settlement.open_todo_semantic_checkpoint", + {identity: {goal_id: "goal", todo_id: "todo_checkpoint", turn_instance_id: "turn"}, writeback}), /identity.agent_id/); +}); + +test("checkpoint qualification cannot borrow another binding, reuse old context or accept completion", async () => { + const handlers = createEffectRuntimeHandlers({fingerprint: "checkpoint-negatives", requestShutdown() {}}); + const rejected: Array<[string[], unknown]> = [ + ...["goal_id", "agent_id", "todo_id", "turn_instance_id"].flatMap(field => + [[[field], "foreign"], [["settlement_identity", field], "foreign"]] as Array<[string[], unknown]>), + [["settlement_identity", "effect_id"], "foreign-effect"], + [["settlement_identity", "schema_version"], "unknown"], + [["delivery_outcome"], "surface_only"], [["delivery_outcome"], "primary_goal_outcome"], + [["completion_todo_id"], identity.todo_id], [["agent_vision"], null], + [["agent_vision", "goal_id"], "foreign"], [["agent_vision", "agent_id"], "foreign"], + [["agent_vision", "vision_patch", "acceptance_summary"], ""], + [["agent_vision", "path_delta"], null], [["agent_vision", "path_delta", "evidence_refs"], []], + [["agent_vision", "path_delta", "outcome"], "stop"], [["agent_vision", "path_delta", "outcome"], "replan"], + [["agent_vision", "state"], null], [["agent_vision", "state"], undefined], [["agent_vision", "state"], ""], + [["vision_checkpoint", "agent_id"], "foreign"], [["vision_checkpoint", "satisfied"], false], + [["vision_checkpoint", "required"], false], [["vision_checkpoint", "decision"], "unchanged_with_reason"], + [["vision_checkpoint", "triggers"], []], [["vision_checkpoint", "delivery_boundary"], "in_flight_continuation"], + ...["vision_closed", "retired", "retired_or_superseded", "superseded", "no_followup", + "closed", "satisfied", "vision_satisfied", "vision_retired", "vision_superseded", + "vision_no_followup", "closed_no_followup", "no_follow_up", "invalid state"].map(state => + [["agent_vision", "state"], state] as [string[], unknown]), + ]; + for (const [path, value] of rejected) { + const candidate = changed(path, value), before = structuredClone(candidate); + assert.equal(qualifiesOpenTodoSemanticCheckpoint(candidate, identity), false, path.join(".") + "=" + value); + assert.equal(await dispatchEffectRuntimeMethod(handlers, "settlement.open_todo_semantic_checkpoint", + {identity, writeback: candidate}), false, path.join(".")); + assert.deepEqual(candidate, before); + } +}); + +test("the shared closed vocabulary preserves Python aliases and custom open states", () => { + const closed = ["vision_closed", "retired", "retired_or_superseded", "superseded", "no_followup", + "closed", "satisfied", "vision_satisfied", "vision_retired", "vision_superseded", + "vision_no_followup", "closed_no_followup", "no_follow_up"]; + const states = [...closed, "active", "vision_patch_proposed", "custom_open_state", "invalid state", null]; + const expected = [...closed.map(() => false), true, true, true, false, true]; + assert.deepEqual(states.map(goalVisionStateIsOpen), expected); + const python = spawnSync(resolveTestPython(), ["-c", + "import json,sys; from loopx.control_plane.goals.goal_vision_state import normalize_goal_vision_state,goal_vision_state_is_closed\n" + + "def opened(value):\n try: return not goal_vision_state_is_closed(normalize_goal_vision_state(value))\n except ValueError: return False\n" + + "print(json.dumps([opened(value) for value in json.load(sys.stdin)]))"], + {cwd: fileURLToPath(new URL("../../", import.meta.url)), input: JSON.stringify(states), encoding: "utf8"}); + assert.equal(python.status, 0, python.stderr); + assert.deepEqual(JSON.parse(python.stdout), expected); +}); diff --git a/tests/control_plane_ts/quota_settlement_plan.test.ts b/tests/control_plane_ts/quota_settlement_plan.test.ts index e264a461ae..51514d5b78 100644 --- a/tests/control_plane_ts/quota_settlement_plan.test.ts +++ b/tests/control_plane_ts/quota_settlement_plan.test.ts @@ -53,9 +53,11 @@ test("ordinary completion is conditional on deliverable acceptance, not Turn acc assert.match(plan.steps[0].precondition, /--result-file requires approved Goal acceptance criteria bound to this Todo, not merely a Todo validator/); assert.match(plan.steps[0].precondition, /Todo done does not settle the Turn/); - assert.match(plan.steps[0].precondition, /Released feedback.*work-segment boundary/); - assert.match(plan.steps[0].precondition, /unfinished Todo open.*freshly guarded continuation/); - assert.match(plan.steps[0].precondition, /Unchanged polls.*no checkpoint/); + assert.match(plan.steps[0].precondition, /new decision evidence produced or obtained by this admitted work/); + assert.match(plan.steps[0].precondition, /Prior-feedback readback.*deterministic repetition without new information are context/); + assert.match(plan.steps[0].precondition, /not this Turn's progress; use them to advance the task first/); + assert.match(plan.steps[0].precondition, /After that progress.*writeback\/spend.*unfinished Todo open/); + assert.doesNotMatch(plan.steps[0].precondition, /spend before the next decision/); assert.match(plan.steps[1].precondition, /--agent-vision-json/); assert.match(plan.steps[1].precondition, /path_delta.*evidence_refs/); assert.match(plan.steps[1].precondition, /acceptance_summary/); @@ -101,7 +103,7 @@ test("in-flight validation offers completion only after original deliverable acc assert.match(plan.steps[1].precondition, /^validation succeeded/); assert.match(plan.steps[1].precondition, /Route elimination needs evidence/); assert.match(plan.steps[1].precondition, /failure alone is not progress/); - assert.match(plan.steps[1].precondition, /decision-relevant feedback.*semantic_closeout.*fresh evidence-linked/i); + assert.match(plan.steps[1].precondition, /new decision evidence.*semantic_closeout.*fresh evidence-linked/i); assert.match(plan.steps[1].precondition, /including when the route stays unchanged/); assert.match( plan.steps[1].precondition, @@ -128,7 +130,7 @@ test("semantic feedback checkpoint is an exact variant of the current writeback, const serialized = settlementPlanPayload(plan).ordered_steps as Record[]; assert.equal(serialized[1].semantic_closeout_command_template, checkpoint); assert.match(visionAuthoringContract().continuation_review as string, - /usable candidate is a checkpoint.*full marginal cost/); + /candidate usability alone does not establish completion.*full marginal cost/); assert.match(visionAuthoringContract().continuation_review as string, /no useful authorized step remains/); } From 44943cc2ea9bd0ee2acbf244ab5b0d688467bce8 Mon Sep 17 00:00:00 2001 From: song Date: Tue, 3 Nov 2026 01:10:27 +0800 Subject: [PATCH 4/5] fix(goals): avoid duplicate autonomous closeout guidance Signed-off-by: song --- .../control_plane/goals/vision_checkpoint.ts | 16 +++++---- .../control_plane_ts/replan_semantics.test.ts | 33 +++++++++++++++++++ 2 files changed, 43 insertions(+), 6 deletions(-) diff --git a/loopx/control_plane/goals/vision_checkpoint.ts b/loopx/control_plane/goals/vision_checkpoint.ts index 61b30df588..f25781dcac 100644 --- a/loopx/control_plane/goals/vision_checkpoint.ts +++ b/loopx/control_plane/goals/vision_checkpoint.ts @@ -128,12 +128,16 @@ export function visionAuthoringContract(autonomousReplan = false): JsonObject { ? `Autonomous review: changing ${GOAL_VISION_DURABLE_FIELDS.join(", ")} requires outcome=replan, even when the Goal objective stays unchanged. Retaining these fields permits continue/no_change. ` : "Changed mainline: outcome=replan. ") + "vision_closed closes a stage, not Goal; no_followup: no scoped work.", - continuation_review: "For an explicitly continuing or optimization objective, candidate usability alone does not establish completion. " + - "Before stop/no_followup, compare remaining acceptance gaps, plausible headroom, a feasible next experiment " + - "and its full marginal cost with the accepted stopping condition. Record the judgment and evidence in path_delta; " + - "continue or replan useful authorized work. A submission or unused budget alone proves neither completion nor " + - "a need for more work. Preserve explicit Stop, budget/permission limits and genuine waits; never invent a successor, " + - "force a pivot or fill time when no useful authorized step remains.", + // Autonomous planning/Stride and settlement-only recovery carry their own + // decision guidance. Keep this reminder at the ordinary checkpoint entry. + ...(autonomousReplan ? {} : { + continuation_review: "For an explicitly continuing or optimization objective, candidate usability alone does not establish completion. " + + "Before stop/no_followup, compare remaining acceptance gaps, plausible headroom, a feasible next experiment " + + "and its full marginal cost with the accepted stopping condition. Record the judgment and evidence in path_delta; " + + "continue or replan useful authorized work. A submission or unused budget alone proves neither completion nor " + + "a need for more work. Preserve explicit Stop, budget/permission limits and genuine waits; never invent a successor, " + + "force a pivot or fill time when no useful authorized step remains.", + }), }; } // Bounded typed fallback declarations survive prepare unchanged so the diff --git a/tests/control_plane_ts/replan_semantics.test.ts b/tests/control_plane_ts/replan_semantics.test.ts index 93e3a4a55d..4a7927c7e2 100644 --- a/tests/control_plane_ts/replan_semantics.test.ts +++ b/tests/control_plane_ts/replan_semantics.test.ts @@ -39,6 +39,35 @@ test("open-work review guidance does not require configured checks or outcome de } }); +test("autonomous checkpoints reuse owner review while ordinary checkpoints keep continuation guidance", () => { + assert.equal(typeof visionAuthoringContract().continuation_review, "string"); + assert.equal(Object.hasOwn(visionAuthoringContract(true), "continuation_review"), false); + for (const kind of ["vision_acceptance_gap", "periodic_review_due", "external_progress_review_drift"]) { + for (const stride_strategy of ["off", "evidence_conditioned_v1"]) { + const projection = projectReplanSemantics({operation: "requirements", obligation: { + obligation_id: "replan-1111111111111111", triggers: [{kind}], replan_context: {stride_strategy}, + }}); + const authoring = (projection.writeback_contract as JsonObject).vision_authoring as JsonObject; + assert.deepEqual(authoring, visionAuthoringContract(true)); + const guidance = (projection.planning_guidance as string[]).join(" "); + for (const clause of ["original intent", "every requirement", "full marginal cost", + "Before no_followup, review unmet acceptance", "reasonable in-scope next step remains, continue or replan", + "evidence and judgment in existing Vision/path_delta", "budget or stops", "planning grant no authority", + "never forces a pivot", "do not invent work or consume budget merely to stay active"]) { + assert.ok(guidance.includes(clause), clause); + } + if (stride_strategy === "evidence_conditioned_v1") { + const review = (projection.stride_control as JsonObject).decision_value_review as JsonObject; + const advice = Object.values(review).join(" "); + for (const clause of ["one feasible alternative", "size a complete experiment", "plausible headroom", + "artifact version, input conditions and original budget", "budget, feedback and permission limits"]) { + assert.ok(advice.includes(clause), clause); + } + } + } + } +}); + test("a revalidated successor receipt settles only its exact original Turn guard", () => { const id = "replan-1111111111111111"; const delta = {schema_version: "replan_semantic_delta_v0", accepted: true, @@ -579,6 +608,10 @@ test(`${recovery} restores only its exact admitted review and still owes Vision` const packet = result.settlement_action_packet as JsonObject; assert.equal(packet.settlement_only, true); assert.equal(packet.semantic_delta, null); + const writeback = packet.writeback_contract as JsonObject; + assert.match(String(writeback.rule), /Reassess the original acceptance with evidence.*do not repeat the original work/); + assert.deepEqual(writeback.vision_authoring, visionAuthoringContract(true)); + assert.equal(Object.hasOwn(writeback.vision_authoring as JsonObject, "continuation_review"), false); assert.equal(projectReplanSemantics({operation: "qualify", obligation: result}).accepted, false); assert.equal(projectReplanSemantics({operation: "qualify", obligation: result, agent_vision: vision}).accepted, true); const historical = {...row, todo_id: "todo_historical", completion_turn_key: "historical-completion", From c8ca3e6ff543cb151316f5f4f3a870fbfd7e6bb0 Mon Sep 17 00:00:00 2001 From: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> Date: Tue, 3 Nov 2026 02:49:40 +0800 Subject: [PATCH 5/5] refactor(quota): clarify feedback checkpoint selection with compact guidance Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> --- benchmark/runtime/EXECUTION_OBSERVATIONS.md | 39 ++++++------- .../goal-vision-replan-contract-v0.md | 57 ++++++++++--------- .../control_plane/goals/vision_checkpoint.ts | 10 ++-- loopx/control_plane/quota/settlement_plan.ts | 33 ++++++----- .../test_material_feedback_checkpoint.py | 2 +- .../quota_settlement_plan.test.ts | 26 +++++---- .../control_plane_ts/replan_semantics.test.ts | 2 +- 7 files changed, 86 insertions(+), 83 deletions(-) diff --git a/benchmark/runtime/EXECUTION_OBSERVATIONS.md b/benchmark/runtime/EXECUTION_OBSERVATIONS.md index d4f1aeddd9..ccf8e9cfae 100644 --- a/benchmark/runtime/EXECUTION_OBSERVATIONS.md +++ b/benchmark/runtime/EXECUTION_OBSERVATIONS.md @@ -39,27 +39,24 @@ merely to produce an attachment. Configuration and readback invoke neither a probe nor a model. Runtime receipts record the selected producer, which does not attest execution, adoption or improvement. -New decision evidence produced or obtained by the current admitted work can -close its work segment. Prior-feedback readback, bookkeeping and deterministic -repetition without new information are context, not this Turn's progress; use -them to advance the original task first. The ordinary settlement plan's -`durable_writeback.semantic_closeout_command_template` selects that boundary -with the original Turn, Todo and Goal identity; fill its result and Vision -placeholders, then follow the same plan's remaining steps and yield to the Host. -Keep an unfinished Todo open. Record the evaluated artifact/input and the -decision in the existing Vision/path delta even when the route stays unchanged. -The next guarded wake can continue the same Goal/session or perform an existing -due review; neither feedback counts nor this command enable Stride. Repeated -unchanged polls and bookkeeping alone need no new checkpoint. - -For an explicitly continuing optimization objective, candidate usability alone -does not establish completion. Before stopping, review remaining acceptance gaps, plausible -headroom, feasible experiments, full marginal cost and the accepted stopping -condition. Stop when justified; do not force a pivot or use the remaining time -merely because it is available. This is Agent-consumed execution guidance, -not automatic tool-result classification or proof that a model adopted it. -Native model adoption, automatic long-Turn supervision and outcome benefit -remain unqualified; scripted lifecycle checks do not establish them. +Choose the settlement plan's ordinary `command_template` for in-flight progress +without new decision evidence. Use `semantic_closeout_command_template` for new +evidence from this admitted work, a route change or Todo completion; bind the +evaluated artifact/input and actual decision in a fresh Vision/path_delta, +including `continue`/`no_change` when retaining the route. Keep the original +Goal/Todo/Turn, follow the remaining writeback/spend steps and yield for freshly +guarded Host continuation; an unfinished Todo stays open. Prior-feedback +readback, bookkeeping and deterministic rechecks without new information are +context, not new progress: use them to advance the task first. Unchanged polls +alone need no checkpoint; neither feedback counts nor this command enables Stride. + +For explicitly continuing optimization, candidate usability alone is not completion: before +stopping, weigh acceptance gaps, plausible headroom and feasible next work's full marginal +cost against accepted stops, recording evidence in path_delta. Continue/replan +useful authorized work; preserve Stop, budget/permission limits and genuine waits, +and never infer completion or invent work, pivots or successors from submission +or unused budget alone. This is Agent guidance, not automatic tool-result classification; +native-model adoption, long-Turn supervision and outcome benefit remain unqualified. ## Retiring the bundled research treatment diff --git a/docs/reference/protocols/goal-vision-replan-contract-v0.md b/docs/reference/protocols/goal-vision-replan-contract-v0.md index b0760b39e1..2fa34512c1 100644 --- a/docs/reference/protocols/goal-vision-replan-contract-v0.md +++ b/docs/reference/protocols/goal-vision-replan-contract-v0.md @@ -913,15 +913,16 @@ with `--delivery-boundary semantic_closeout --agent-vision-json ` selected It is an alternative for that step, not another plan, claim, debit or admission. Material CLI closeout and MCP `complete_task` retain their existing evidence gates. -New decision evidence produced or obtained by the current admitted work can end -its work segment. Prior-feedback readback, bookkeeping and deterministic -repetition without new information are context, not this Turn's progress; use -them to advance the original task first. Bind the new result to the evaluated artifact/input, record the actual -decision in Vision/path delta even when retaining the route, execute the current -plan's writeback/spend and yield for a freshly guarded Host continuation. Leave -an unfinished Todo open. Repeated unchanged polls and bookkeeping alone need no -new checkpoint. This is Agent execution guidance, not automatic classification -of tool text, a new Stride trigger, or proof of adoption or useful outcomes. +Choose `command_template` for ordinary in-flight progress without new decision +evidence; use `semantic_closeout_command_template` for new evidence from this +admitted work, a route change or Todo completion. Bind the evaluated artifact/input +and actual decision in fresh Vision/path_delta, including `continue`/`no_change` +when retaining the route. Keep the original Goal/Todo/Turn, follow the remaining +writeback/spend steps and yield for freshly guarded Host continuation; an unfinished +Todo stays open. Prior-feedback readback, bookkeeping and deterministic rechecks +without new information are context, not progress: advance the task first. +Unchanged polls alone need no checkpoint. This is Agent guidance, not automatic +tool-text classification, a Stride trigger or proof of adoption/benefit. An open Todo with controller-declared completion validation can use this semantic checkpoint for `outcome_progress` with a fresh, open Vision and an @@ -931,24 +932,26 @@ rule. This closes only the original Turn: the Todo remains open and its final completion validator remains pending. Primary-outcome, closed-Vision and terminal declarations retain that validator gate. -For an explicitly continuing or optimization objective, candidate usability -alone does not establish completion. Before stopping, compare unmet acceptance, plausible headroom, -feasible next work and full marginal cost with the original stopping condition. -Retain an evidenced no-follow-up, Stop, budget/permission limit or genuine wait; -neither a submission nor unused budget decides whether further work is useful. - -普通 CLI Todo 结算在默认 in-flight 边界下也提供既有 Vision 编写结构;写回步骤的 -`semantic_closeout_command_template` 沿用原 Goal、Todo、Turn,只显式选择 -semantic closeout 与 Vision 文件。它是当前步骤的可执行变体,不增加 plan、认领、 -扣额或准入。当前授权工作实际产生或取得新的决策证据后,才进入相应工作段的 -检查点;旧反馈读回、记账与没有新增信息的确定性重验只是上下文,应先用于推进原 -任务。绑定新结果的评估版本与 -输入,即使保留路线也记录实际 Vision/path 决策,再按原计划写回、扣额并让宿主 -以新 guard 接续,未完成 Todo 保持 open。未变化轮询、记账无需新 checkpoint。 -持续优化目标不能仅因已有可用候选就宣布完成;停止前对照剩余验收、改进空间、可行工作、 -完整边际成本和原停止条件,保留有依据的 no-follow-up、Stop、预算/权限限制与真实 -等待。不强制用满预算或换路,也不由工具文本自动分类或新增 Stride trigger;执行 -指导与模型实际采用、结果收益仍须分别验证。 +For explicitly continuing/optimization objectives, candidate usability alone is not completion: +before stop/no_followup, weigh acceptance gaps, plausible headroom and feasible next work's +full marginal cost against accepted stops in an evidence-linked path_delta. +Continue/replan useful authorized work; preserve Stop, budget/permission limits +and genuine waits. Neither submission nor unused budget alone establishes completion +or requires invented work, pivots or successors. + +普通 CLI Todo 的默认 in-flight 结算也提供 Vision 编写结构。无新决策证据的普通 +推进选 `command_template`;本次准入工作产生或取得新证据、改变路线或完成 Todo, +选 `semantic_closeout_command_template`,以 fresh Vision/path_delta 关联评估产物、 +输入版本和实际决策,保留路线也可用 `continue` / `no_change`。沿用原 Goal、Todo、 +Turn,按原计划继续写回和扣额,再由宿主以新 guard 接续;未完成 Todo 保持 open, +不另建 plan、认领、扣额或准入。旧反馈读回、记账、无新信息的确定性重验只是上下文, +应先用于推进任务;未变化轮询不要求新 checkpoint。它是 Agent 指导,不自动分类 +工具文本、不触发 Stride,也不证明模型采用或收益。 + +持续优化目标不能仅因候选可用就完成:停止前在 path_delta 中用证据对照剩余验收、 +改进空间、可行后续工作的完整边际成本和原停止条件;继续或重规划有用且获授权的工作, +保留 Stop、预算/权限限制与真实等待。提交或剩余额度均不证明完成,也不要求制造工作、 +换路或后继 Todo。 声明完成验证器的开放 Todo,可以用 fresh、open 的 Vision 和有证据关联的 `continue` / `no_change` 路径记录 `outcome_progress`;准入写回与精确回执的扣额 diff --git a/loopx/control_plane/goals/vision_checkpoint.ts b/loopx/control_plane/goals/vision_checkpoint.ts index f25781dcac..cc1e029561 100644 --- a/loopx/control_plane/goals/vision_checkpoint.ts +++ b/loopx/control_plane/goals/vision_checkpoint.ts @@ -131,12 +131,10 @@ export function visionAuthoringContract(autonomousReplan = false): JsonObject { // Autonomous planning/Stride and settlement-only recovery carry their own // decision guidance. Keep this reminder at the ordinary checkpoint entry. ...(autonomousReplan ? {} : { - continuation_review: "For an explicitly continuing or optimization objective, candidate usability alone does not establish completion. " + - "Before stop/no_followup, compare remaining acceptance gaps, plausible headroom, a feasible next experiment " + - "and its full marginal cost with the accepted stopping condition. Record the judgment and evidence in path_delta; " + - "continue or replan useful authorized work. A submission or unused budget alone proves neither completion nor " + - "a need for more work. Preserve explicit Stop, budget/permission limits and genuine waits; never invent a successor, " + - "force a pivot or fill time when no useful authorized step remains.", + continuation_review: "For explicitly continuing/optimization objectives, candidate usability alone is not completion: before stop/no_followup, " + + "weigh acceptance gaps, plausible headroom and feasible next work's full marginal cost against accepted stops in an evidence-linked path_delta; " + + "continue/replan useful authorized work, preserve Stop, budget/permission limits and genuine waits, " + + "and never infer completion or invent work/pivots/successors from submission or unused budget alone.", }), }; } diff --git a/loopx/control_plane/quota/settlement_plan.ts b/loopx/control_plane/quota/settlement_plan.ts index 1fb3fd16b2..865cf33791 100644 --- a/loopx/control_plane/quota/settlement_plan.ts +++ b/loopx/control_plane/quota/settlement_plan.ts @@ -83,28 +83,27 @@ export function turnScopedCliSettlementPlan(params: JsonObject): SettlementPlan "an artifact pointer; --result-file requires approved Goal acceptance criteria bound to this Todo, " + "not merely a Todo validator. No invented successor " + "or --no-follow-up is required. Todo done does not settle the Turn. " + - "Checkpoint new decision evidence produced or obtained by this admitted work, bound to its artifact/input. " + - "Prior-feedback readback, bookkeeping and deterministic repetition without new information are context, " + - "not this Turn's progress; use them to advance the task first. After that progress, use this plan's writeback/spend; " + - "keep an unfinished Todo open, then yield for freshly guarded continuation. " + - (inFlight ? "For unfinished bounded progress, keep the Todo open with its original declaration. " + - "The projected in-flight boundary is not an instruction to retain completed work: " + - "when complete or changing route, use --delivery-boundary semantic_closeout with an " + - "evidence-linked vision decision on this same Turn; no new guard or binding is needed." : ""); + "Keep an unfinished Todo open with its original declaration."; } const steps: SettlementStep[] = [ validation, { kind: "durable_writeback", owner: "agent", - precondition: (inFlight || identity.binding_kind !== "todo" ? "validation succeeded" - : "validation succeeded; " + VISION_MATERIAL_CLOSEOUT_HINT + - " Pass a new packet with --agent-vision-json.") + - " Route elimination needs evidence; failure alone is not progress. " + - (autonomousReplan ? "Autonomous replan uses --delivery-boundary semantic_closeout even while its Todo remains open; " - : "For that new decision evidence use --delivery-boundary semantic_closeout and a fresh evidence-linked " + - "Vision/path_delta with --agent-vision-json, including when the route stays unchanged. " + - "Use this step's semantic_closeout_command_template when present; follow the remaining plan unchanged. " + - "Other outcome_progress may use in_flight_continuation; ") + "outcome_gap requires " + + precondition: "validation succeeded; " + + (autonomousReplan ? "Autonomous replan uses --delivery-boundary semantic_closeout even while its Todo remains open. " + + "Bind new decision evidence from this admitted work to its artifact/input; follow the remaining plan, " + + "then yield for freshly guarded continuation. " + : (inFlight ? "Choose one: command_template for ordinary in_flight_continuation without new decision evidence; " + : "Use command_template for semantic_closeout; ") + + (semanticWriteback !== null ? "semantic_closeout_command_template" : "--delivery-boundary semantic_closeout") + + " for new decision evidence, route change or Todo completion. " + + "Bind new evidence produced or obtained by this admitted work to its artifact/input in fresh Vision/path_delta " + + "with --agent-vision-json, including continue/no_change when retaining the route. " + + "Use this same Turn; no new guard or binding. Follow the remaining plan, then yield for freshly guarded continuation. ") + + "Prior-feedback readback, bookkeeping and deterministic repetition without new information are context, " + + "not this Turn's progress; use them to advance the task first. " + + (!inFlight && identity.binding_kind === "todo" ? VISION_MATERIAL_CLOSEOUT_HINT + " " : "") + + "Route elimination needs evidence; failure alone is not progress. outcome_gap requires " + "semantic_closeout plus blocked + blocker/evidence IDs and continuation checks. " + "For an admitted open continuous Monitor whose observation is unavailable, " + "use this original identity for typed blocked outcome_gap; it settles without " + diff --git a/tests/control_plane/test_material_feedback_checkpoint.py b/tests/control_plane/test_material_feedback_checkpoint.py index 434f0acb17..e9a6ed1ff8 100644 --- a/tests/control_plane/test_material_feedback_checkpoint.py +++ b/tests/control_plane/test_material_feedback_checkpoint.py @@ -90,7 +90,7 @@ def execute(command, **values): # Merely obtaining the new executable projection performs no work or writeback. assert not (runtime / "goals" / fixture.GOAL_ID / "runs/index.jsonl").exists() if preexisting_feedback: - guidance = plan["ordered_steps"][0]["precondition"] + guidance = writeback["precondition"] assert "not this Turn's progress; use them to advance the task first" in guidance repeated = run_probe(command=[sys.executable, str(validator)], candidate_files=[route], output=project / "prior-recheck.json", input_revision="graph-v1", planned_method_id="retained-route", diff --git a/tests/control_plane_ts/quota_settlement_plan.test.ts b/tests/control_plane_ts/quota_settlement_plan.test.ts index 51514d5b78..1ad668c00e 100644 --- a/tests/control_plane_ts/quota_settlement_plan.test.ts +++ b/tests/control_plane_ts/quota_settlement_plan.test.ts @@ -53,10 +53,12 @@ test("ordinary completion is conditional on deliverable acceptance, not Turn acc assert.match(plan.steps[0].precondition, /--result-file requires approved Goal acceptance criteria bound to this Todo, not merely a Todo validator/); assert.match(plan.steps[0].precondition, /Todo done does not settle the Turn/); - assert.match(plan.steps[0].precondition, /new decision evidence produced or obtained by this admitted work/); - assert.match(plan.steps[0].precondition, /Prior-feedback readback.*deterministic repetition without new information are context/); - assert.match(plan.steps[0].precondition, /not this Turn's progress; use them to advance the task first/); - assert.match(plan.steps[0].precondition, /After that progress.*writeback\/spend.*unfinished Todo open/); + assert.match(plan.steps[0].precondition, /unfinished Todo open with its original declaration/); + assert.doesNotMatch(plan.steps[0].precondition, /Prior-feedback|semantic_closeout/); + assert.match(plan.steps[1].precondition, /new evidence produced or obtained by this admitted work.*artifact\/input/); + assert.match(plan.steps[1].precondition, /Prior-feedback readback.*deterministic repetition without new information are context/); + assert.match(plan.steps[1].precondition, /not this Turn's progress; use them to advance the task first/); + assert.match(plan.steps[1].precondition, /Follow the remaining plan.*yield for freshly guarded continuation/); assert.doesNotMatch(plan.steps[0].precondition, /spend before the next decision/); assert.match(plan.steps[1].precondition, /--agent-vision-json/); assert.match(plan.steps[1].precondition, /path_delta.*evidence_refs/); @@ -98,13 +100,13 @@ test("in-flight validation offers completion only after original deliverable acc assert.equal(plan.steps[0].command_template, "ordinary completion"); assert.equal(plan.steps[0].command_condition, "todo_deliverable_complete"); assert.equal(plan.steps[0].conditional, undefined); - assert.match(plan.steps[0].precondition, /keep the Todo open with its original declaration/); - assert.match(plan.steps[0].precondition, /semantic_closeout.*same Turn; no new guard or binding/); + assert.match(plan.steps[0].precondition, /unfinished Todo open with its original declaration/); + assert.match(plan.steps[1].precondition, /same Turn; no new guard or binding/); assert.match(plan.steps[1].precondition, /^validation succeeded/); assert.match(plan.steps[1].precondition, /Route elimination needs evidence/); assert.match(plan.steps[1].precondition, /failure alone is not progress/); - assert.match(plan.steps[1].precondition, /new decision evidence.*semantic_closeout.*fresh evidence-linked/i); - assert.match(plan.steps[1].precondition, /including when the route stays unchanged/); + assert.match(plan.steps[1].precondition, /semantic_closeout.*new decision evidence.*fresh Vision\/path_delta/i); + assert.match(plan.steps[1].precondition, /including continue\/no_change when retaining the route/); assert.match( plan.steps[1].precondition, /outcome_gap requires semantic_closeout.*blocker\/evidence IDs.*continuation checks/, @@ -126,13 +128,17 @@ test("semantic feedback checkpoint is an exact variant of the current writeback, for (const i of [0, 2, 3]) assert.deepEqual(plan.steps[i], before.steps[i]); assert.equal(plan.steps[1].command_template, before.steps[1].command_template); assert.equal(plan.steps[1].semantic_closeout_command_template, checkpoint); + if (delivery_boundary === "in_flight_continuation") { + assert.match(plan.steps[1].precondition, + /^validation succeeded; Choose one: command_template for ordinary in_flight_continuation without new decision evidence; semantic_closeout_command_template for new decision evidence, route change or Todo completion/); + } assert.deepEqual(plan.steps[1].vision_authoring, visionAuthoringContract()); const serialized = settlementPlanPayload(plan).ordered_steps as Record[]; assert.equal(serialized[1].semantic_closeout_command_template, checkpoint); assert.match(visionAuthoringContract().continuation_review as string, - /candidate usability alone does not establish completion.*full marginal cost/); + /candidate usability alone is not completion.*full marginal cost/); assert.match(visionAuthoringContract().continuation_review as string, - /no useful authorized step remains/); + /useful authorized work.*preserve Stop, budget\/permission limits and genuine waits/); } assert.throws(() => turnScopedCliSettlementPlan({...input, command_templates: {...input.command_templates, semantic_closeout: ""}}), /must be a non-empty string/); diff --git a/tests/control_plane_ts/replan_semantics.test.ts b/tests/control_plane_ts/replan_semantics.test.ts index 4a7927c7e2..dc3f5b4369 100644 --- a/tests/control_plane_ts/replan_semantics.test.ts +++ b/tests/control_plane_ts/replan_semantics.test.ts @@ -59,7 +59,7 @@ test("autonomous checkpoints reuse owner review while ordinary checkpoints keep if (stride_strategy === "evidence_conditioned_v1") { const review = (projection.stride_control as JsonObject).decision_value_review as JsonObject; const advice = Object.values(review).join(" "); - for (const clause of ["one feasible alternative", "size a complete experiment", "plausible headroom", + for (const clause of ["feasible alternative", "complete experiment", "headroom", "artifact version, input conditions and original budget", "budget, feedback and permission limits"]) { assert.ok(advice.includes(clause), clause); }