From f090a4d8d3d945e4f31562448f42449a61b889b5 Mon Sep 17 00:00:00 2001 From: huangruiteng Date: Wed, 21 Oct 2026 13:55:22 +0800 Subject: [PATCH] fix(planning): keep upfront plans sufficient for the next decisive step Signed-off-by: huangruiteng --- .../protocols/loopx-goal-command-v0.md | 13 +++++++++ loopx/control_plane/goals/start_contract.py | 6 ++++- tests/control_plane/test_task_planning.py | 27 +++++++++++++++++++ 3 files changed, 45 insertions(+), 1 deletion(-) diff --git a/docs/reference/protocols/loopx-goal-command-v0.md b/docs/reference/protocols/loopx-goal-command-v0.md index d7803827ec..0e899c45f6 100644 --- a/docs/reference/protocols/loopx-goal-command-v0.md +++ b/docs/reference/protocols/loopx-goal-command-v0.md @@ -154,6 +154,19 @@ but the shape depends on how clear the goal already is: concise todos to make the approach explicit, without arbitrary item-count caps or management-only filler. +The shared `budget_policy` now explicitly asks for the minimum plan needed to +begin useful work: reuse covered Todos, identify the next executable step and its +decisive validation, and defer uncertain later work to replan. Diagnostic validation, +including permitted feedback, can be that first step before dependent implementation +to strengthen the first deliverable. The quality objective and diagnostic means +coexist: a diagnostic result does not replace the requested deliverable. Do not +require exhaustive upfront decomposition. This changes default +planning guidance for both `/loopx` and `todo plan`, in ordinary and fine-grained +mode; it adds no fixed step/time limit or permission to skip planning/readback, +task acceptance, quota admission or genuine blockers. The existing fine-grained +one-checkpoint rule and broad-goal lane suggestions still apply. Lightweight +planning does not mean early task completion or weaker delivery requirements. + The shared planner now includes `first_delivery_guidance`, also rendered in the guided `/loopx` start prompt and the existing `todo plan` packet. This changes default Agent guidance in both ordinary and fine-grained planning: preserve a diff --git a/loopx/control_plane/goals/start_contract.py b/loopx/control_plane/goals/start_contract.py index c7b6564933..040fb3e1cb 100644 --- a/loopx/control_plane/goals/start_contract.py +++ b/loopx/control_plane/goals/start_contract.py @@ -50,7 +50,11 @@ def goal_planner_contract(*, fine_grained: bool = False) -> dict[str, Any]: "default_task_class": "advancement_task", "required_fields": ["priority", "text", "task_class", "action_kind"], "public_safe_only": True, - "budget_policy": "minimum sufficient plan; no fixed-count filler", + "budget_policy": ( + "minimum sufficient plan: next step and decisive validation; no fixed-count " + "filler or exhaustive decomposition. Diagnostics and feedback may come first " + "to strengthen delivery; replan uncertain later work." + ), "first_delivery_guidance": _FIRST_DELIVERY_GUIDANCE, } if fine_grained: diff --git a/tests/control_plane/test_task_planning.py b/tests/control_plane/test_task_planning.py index b83a1f67ab..cdd3ac06e3 100644 --- a/tests/control_plane/test_task_planning.py +++ b/tests/control_plane/test_task_planning.py @@ -92,6 +92,33 @@ def test_existing_plan_is_an_incremental_frontier_not_a_new_goal(bound_goal): assert all(item["todo_id"] != "todo_peer" for item in packet["existing_todos"]) +@pytest.mark.parametrize("fine_grained", [False, True]) +def test_planning_scope_is_sufficient_to_start_without_exhaustive_decomposition(bound_goal, fine_grained): + registry = bound_goal["registry_path"] + contents = json.loads(registry.read_text()) + if fine_grained: + contents["goals"][0]["execution_profile"] = {"turn_granularity": "fine"} + registry.write_text(json.dumps(contents)) + packet = build_task_planning_packet(**bound_goal) + planner = packet["planner"] + assert planner == goal_planner_contract(fine_grained=fine_grained) + for clause in ( + "minimum sufficient plan: next step and decisive validation", + "Diagnostics and feedback may come first to strengthen delivery", + "replan uncertain later work", + "no fixed-count filler or exhaustive decomposition", + ): + assert clause in planner["budget_policy"] + assert planner["profiles"]["clear_bounded_problem"][ + "may_reuse_current_todo_when_it_already_represents_the_plan"] is True + assert planner["required_before_todo_write"] is True + assert packet["execution_handoff"]["requires_quota_guard"] is True + assert packet["execution_handoff"]["planning_is_advancement"] is False + assert "do not substitute them for the requested deliverable" in planner["first_delivery_guidance"] + assert "existing acceptance, feedback and authority limits" in planner["first_delivery_guidance"] + assert planner.get("maximum_runnable_todos_written_ahead") == (1 if fine_grained else None) + + def test_unknown_identity_rejected_without_creating_it(bound_goal): with pytest.raises(ValueError, match="not registered"): build_task_planning_packet(**(bound_goal | {"agent_id": "unknown"}))