From 551403b2be17fc783c4020376269258db89e4dbb Mon Sep 17 00:00:00 2001 From: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> Date: Sun, 18 Oct 2026 05:59:53 +0800 Subject: [PATCH] fix(replan): give executable Todo link authoring example Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> --- .../goal-vision-replan-contract-v0.md | 13 +++ .../control_plane/goals/vision_checkpoint.ts | 2 + .../control_plane/work_items/stride_policy.ts | 2 +- .../test_settlement_input_discovery_cli.py | 90 +++++++++++++++++++ 4 files changed, 106 insertions(+), 1 deletion(-) diff --git a/docs/reference/protocols/goal-vision-replan-contract-v0.md b/docs/reference/protocols/goal-vision-replan-contract-v0.md index fb32a35fd4..73df49e9a9 100644 --- a/docs/reference/protocols/goal-vision-replan-contract-v0.md +++ b/docs/reference/protocols/goal-vision-replan-contract-v0.md @@ -266,6 +266,19 @@ other's active vision. ### Decision followthrough in replan context +Identify planned work in `agent_vision.todo_delta` with a supported action prefix: +for example, `retain:todo_` for an existing Todo. The supported verbs are +`activate`, `create`, `reopen`, `resume` and `retain`; replace `todo_` with the +verified ID after ordinary Todo authoring/readback. These entries record intent; +they do not create, claim or authorize work. Historical descriptive entries remain +readable but create no work links; the literal prefix `action:` is not supported. + +在 `agent_vision.todo_delta` 中使用具体动作关联计划工作,例如用 +`retain:todo_` 保留已有 Todo;支持 `activate`、`create`、`reopen`、`resume` +和 `retain`。先完成普通 Todo 写入/读回,再替换真实 ID。这些条目只记录计划, +不创建、认领或授权任务。历史描述文本仍可读取,但不会形成工作关联; +`action:` 不是可用动作前缀。 + With `evidence_conditioned_v1` enabled, when the captured history contains a prior accepted evidence-linked Vision path, `replan_context.decision_followthrough` returns that path with the later recorded diff --git a/loopx/control_plane/goals/vision_checkpoint.ts b/loopx/control_plane/goals/vision_checkpoint.ts index e9bf89e8de..d4a11500d1 100644 --- a/loopx/control_plane/goals/vision_checkpoint.ts +++ b/loopx/control_plane/goals/vision_checkpoint.ts @@ -98,6 +98,8 @@ export function visionAuthoringContract(): JsonObject { }, }, authoring_hint: "Replace example claims/refs with evidence; obey the live contract and total limit. " + + "For todo_delta, use retain:todo_ for existing work; supported verbs are activate, create, reopen, resume and retain. " + + "Replace todo_ with a verified Todo ID after normal authoring/readback. Entries record plans, not Todo mutations, claims or execution authority. " + "For ordinary CLI writeback, pass the packet with --agent-vision-json . " + "checkpoint-context is only for recovery after the original committed Turn writeback; " + "follow its returned same-Turn recovery action, not a fresh-turn preflight.", diff --git a/loopx/control_plane/work_items/stride_policy.ts b/loopx/control_plane/work_items/stride_policy.ts index 1483ed2c30..5176b76e12 100644 --- a/loopx/control_plane/work_items/stride_policy.ts +++ b/loopx/control_plane/work_items/stride_policy.ts @@ -55,7 +55,7 @@ export function stridePlanningGuidance(value: unknown): string[] { "Missing or refuted evidence is not a universal route ban. Preserve the best qualified artifact and original priorities. " + "Declare minimum evidence, a testable prediction, adoption criterion and full marginal cost including control and validation; review actual adoption, quality and cost next. Verify must resolve a named grow-or-switch question, not repeat an unchanged check. " + "Carry the next_probe into existing executable Todo work, reusing an unfinished experiment when it still answers the question. " + - "Use the existing agent_vision.todo_delta action:todo_id entries to identify that work after its normal authoring/readback; do not invent IDs or add a planning-only Todo. " + + "Use agent_vision.todo_delta entries such as retain:todo_ to identify that work after its normal authoring/readback; do not invent IDs or add a planning-only Todo. " + "Read planned_work and subsequent observations before crediting the previous decision. A negative experiment may finish its Todo while the broader goal remains open. " + "Keep a candidate experiment separate from the best retained artifact: authorized evaluation may be informative before replacement is justified. " + "Use the live stride_control contract for agent_vision.stride_decision. This controls proposal completeness, not execution, " + diff --git a/tests/control_plane/test_settlement_input_discovery_cli.py b/tests/control_plane/test_settlement_input_discovery_cli.py index f1f4f68286..ef21138cfc 100644 --- a/tests/control_plane/test_settlement_input_discovery_cli.py +++ b/tests/control_plane/test_settlement_input_discovery_cli.py @@ -2,6 +2,7 @@ from __future__ import annotations import json +import re import shlex from copy import deepcopy from pathlib import Path @@ -17,6 +18,10 @@ _spend_run_count, _write_fixture, ) from loopx.todos import complete_goal_todo +from loopx.control_plane.work_items.replan_context_codec import ( + project_replan_context, + replan_history_from_status, +) BODY = "[P1] " + "Retain the original requirements and verify the result. " * 24 + "TAIL: preserve cancellation." @@ -200,3 +205,88 @@ def run(*args): rc, spent = _run_generated_cli(accepted["settlement_owed"]["command"], registry_path=registry) assert rc == 0 and spent["appended"] is not replay, spent assert _spend_run_count(runtime) == 2 + + +@pytest.mark.parametrize("provider", ["file", "sqlite"]) +def test_replan_authoring_example_links_actual_subsequent_work(tmp_path, monkeypatch, provider): + """A caller following the delivered example must retain a real work link.""" + isolate_sqlite_runtime(tmp_path, monkeypatch) + project, runtime, registry = _write_fixture(tmp_path) + # Fund all three admitted Turns before execution; the shared fixture's + # ordinary two-slot window is intentionally smaller than this journey. + initial = json.loads(registry.read_text()) + initial["goals"][0]["quota"]["allowed_slots"] = 3 + registry.write_text(json.dumps(initial)) + state = project / f".codex/goals/{GOAL_ID}/ACTIVE_GOAL_STATE.md" + _initialize_authority(runtime, registry, state, provider) + + def run(*args): + return _run_cli(registry, runtime, *args, cwd=project) + + rc, configured = run("configure-goal", "--goal-id", GOAL_ID, + "--execution-replan-after-turns", "1", "--execute") + assert rc == 0, configured + for stage in ("initial", "review", "subsequent"): + binding = ("--goal-id", GOAL_ID, "--agent-id", AGENT_ID, + "--todo-id", TODO_ID, "--turn-instance-id", f"link-example-{stage}") + rc, guard = run("quota", "should-run", "--codex-app", "--goal-id", GOAL_ID, + "--agent-id", AGENT_ID, "--turn-instance-id", f"link-example-{stage}", + "--scan-path", str(project)) + if rc == 0 and guard["interaction_contract"]["cli_channel"].get("selection_required"): + selection = guard["interaction_contract"]["cli_channel"]["selection_command"] + command = selection["route_prefix"] + " " + selection["command_args_template"].format(todo_id=TODO_ID) + rc, guard = _run_generated_cli(command, registry_path=registry) + assert rc == 0 and guard["interaction_contract"]["agent_channel"]["delivery_allowed"], ( + stage, guard.get("error"), guard.get("reason")) + args = ["refresh-state", *binding, "--classification", "validated_progress", + "--delivery-batch-scale", "implementation", "--delivery-outcome", "outcome_progress", + "--no-global-sync", "--suppress-external-sinks"] + if stage == "review": + authoring = guard["interaction_contract"]["cli_channel"]["settlement_plan"]["ordered_steps"][1]["vision_authoring"] + example = re.search(r"retain:todo_", authoring["authoring_hint"]) + assert example, "The authoring contract must offer an executable retention example." + vision = deepcopy(authoring["minimal_example"]) + vision["todo_delta"] = [example.group().replace("todo_", TODO_ID)] + vision["path_delta"].update( + outcome="replan", observed_reality="Continue the admitted original work.", + retained=["Original requirements"], evidence_refs=["validation:work-link"]) + path = tmp_path / "linked-vision.json" + path.write_text(json.dumps(vision)) + args += ["--agent-vision-json", str(path), "--autonomous-replan-recorded", + "--repair-delta-kind", "goal_vision_patch"] + else: + args += ["--delivery-boundary", "in_flight_continuation"] + rc, written = run(*args) + assert rc == 0, written + rc, spent = _run_generated_cli(written["settlement_owed"]["command"], registry_path=registry) + assert rc == 0, spent + + # Read the real immutable CLI history, not a hand-authored decision fixture. + status = {"registry": str(registry), "runtime_root": str(runtime), "run_history": {"runs": []}} + context = project_replan_context( + goal_id=GOAL_ID, agent_id=AGENT_ID, runs=(), source_status=status, + stride_strategy="evidence_conditioned_v1") + follow = context["decision_followthrough"] + assert [row["todo_id"] for row in follow["planned_work"]] == [TODO_ID] + assert follow["ordinary_work"]["recorded_count"] == 1 + evidence = follow["ordinary_work"]["evidence"][0] + assert evidence["todo_id"] == TODO_ID + assert follow["planned_work"][0]["ordinary_evidence_refs"] == [evidence["evidence_ref"]] + assert follow["execution_and_goal_gain"] == "unknown" + # Reading this view neither enables stride nor settles another Turn. + ordinary = project_replan_context( + goal_id=GOAL_ID, agent_id=AGENT_ID, runs=(), source_status=status, stride_strategy="off") + assert "decision_followthrough" not in ordinary + assert _spend_run_count(runtime) == 3 + + # Preserve compatibility without turning a descriptive or unsupported + # action into a causal work link. Mutate a copy, never the authority store. + historical = deepcopy(replan_history_from_status(status, GOAL_ID)) + review = next(row for row in historical if row.get("turn_instance_id") == "link-example-review" + and row.get("agent_vision")) + review["agent_vision"]["todo_delta"] = [f"action:{TODO_ID}", "Keep investigating."] + unlinked = project_replan_context( + goal_id=GOAL_ID, agent_id=AGENT_ID, runs=historical, + history_newest_first=False, stride_strategy="evidence_conditioned_v1") + assert unlinked["decision_followthrough"]["planned_work"] == [] + assert "ordinary_work" not in unlinked["decision_followthrough"]