From ef0bed643be34c4e7d5b6531938ffecd3515d2b3 Mon Sep 17 00:00:00 2001 From: HAL9000 Date: Thu, 7 May 2026 17:15:35 +0000 Subject: [PATCH 1/2] fix(plan-lifecycle): record prompt_definition as root decision during Strategize Fixed PlanLifecycleService.start_strategize() to record a prompt_definition decision as the root of the decision tree instead of strategy_choice. The root decision now correctly uses question=What is the plan prompt? and chosen_option=plan.description, ensuring the decision tree accurately reflects the plan's prompt definition as the foundational decision during the Strategize phase, conforming to the decision model specification. Changes: - Modified start_strategize() in plan_lifecycle_service.py to use DecisionType.PROMPT_DEFINITION - Added BDD test feature file plan_lifecycle_bug9061.feature (2 scenarios) - Added BDD step definitions plan_lifecycle_bug9061_steps.py - Updated CHANGELOG.md with fix entry under [Unreleased]/Fixed - Updated CONTRIBUTORS.md with contribution detail Closes #9061 ISSUES CLOSED: #9061 --- CHANGELOG.md | 38 +++- CONTRIBUTORS.md | 3 +- features/plan_lifecycle_bug9061.feature | 25 +++ .../steps/plan_lifecycle_bug9061_steps.py | 174 ++++++++++++++++++ .../services/plan_lifecycle_service.py | 6 +- 5 files changed, 234 insertions(+), 12 deletions(-) create mode 100644 features/plan_lifecycle_bug9061.feature create mode 100644 features/steps/plan_lifecycle_bug9061_steps.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c9d662fd..66f00de59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -56,6 +56,12 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ``cli_init_yes_flag_steps.py`` cleanup against ``None`` temp_dir. Annotated ``features/architecture.feature`` ``@tdd_expected_fail`` for pre-existing Pydantic compliance debt in ``IndexEntry`` / ``ACMSIndex`` classes. +- **Plan Lifecycle Root Decision Type** (#9061): Fixed `PlanLifecycleService.start_strategize()` + to record a `prompt_definition` decision as the root of the decision tree instead of + `strategy_choice`. The root decision now correctly uses `question="What is the plan prompt?"` + and `chosen_option=plan.description`, ensuring the decision tree accurately reflects the + plan's prompt definition as the foundational decision during the Strategize phase, conforming + to the decision model specification. - **Cross-actor subgraph cycle detection reads actor_ref field** (#1431): Fixed `_detect_subgraph_cycles()`, `_map_node()`, and the `compile_actor()` main loop @@ -105,7 +111,12 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). version constraints. ### Fixed - +- **Plan Lifecycle Root Decision Type** (#9061): Fixed `PlanLifecycleService.start_strategize()` + to record a `prompt_definition` decision as the root of the decision tree instead of + `strategy_choice`. The root decision now correctly uses `question="What is the plan prompt?"` + and `chosen_option=plan.description`, ensuring the decision tree accurately reflects the + plan's prompt definition as the foundational decision during the Strategize phase, conforming + to the decision model specification. - **Error suppression removed from `reactive_registry_adapter.py`** (#9060): Removed two `try...except Exception:` blocks in `register_registry_agents()` that silently suppressed errors, violating the CONTRIBUTING.md fail-fast policy. Exceptions from `actor_registry.list_actors()` and the route bridge refresh now propagate to the caller instead of being swallowed. Added Behave scenarios verifying RuntimeError, AttributeError, and TypeError propagation. - **Implementation Supervisor PR Compliance Checklist** (#9824): Added a mandatory @@ -179,7 +190,12 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). execution and confirms proper cleanup behavior. ### Fixed - +- **Plan Lifecycle Root Decision Type** (#9061): Fixed `PlanLifecycleService.start_strategize()` + to record a `prompt_definition` decision as the root of the decision tree instead of + `strategy_choice`. The root decision now correctly uses `question="What is the plan prompt?"` + and `chosen_option=plan.description`, ensuring the decision tree accurately reflects the + plan's prompt definition as the foundational decision during the Strategize phase, conforming + to the decision model specification. - **fix(repositories): derive PlanResult.success from result_success column instead of error_message** (#7501): Fixed a critical bug in `PlanRepository._to_domain` where `PlanResult.success` was incorrectly derived from `error_message is None`. Because `error_message` is shared between the build phase @@ -427,8 +443,6 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). `definition_of_done`. `StrategizeStubActor.execute` accepts `**kwargs` for forward-compatibility. Added BDD coverage for the stored-JSON path, corrupt-JSON fallback, resource-passing, and stub extra-kwargs scenarios. (#828) - - - **TDD Issue-Capture Test Activation** (#7025): Replaced 234 bare `@skip` tags across 82 Behave feature files with the correct `@tdd_expected_fail @tdd_issue @tdd_issue_` tag system. Scenarios whose referenced bugs were already fixed @@ -598,10 +612,13 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). dispatch loop sleep from 10s to 2s, simplified worker verification, reduced retry delays from 15s to 2s, and reduced idle sleep from 60s to 10s for dramatically faster throughput. - - ### Fixed - +- **Plan Lifecycle Root Decision Type** (#9061): Fixed `PlanLifecycleService.start_strategize()` + to record a `prompt_definition` decision as the root of the decision tree instead of + `strategy_choice`. The root decision now correctly uses `question="What is the plan prompt?"` + and `chosen_option=plan.description`, ensuring the decision tree accurately reflects the + plan's prompt definition as the foundational decision during the Strategize phase, conforming + to the decision model specification. - **Plan Concurrency Race Condition** (#7989): Fixed critical race condition in `execute_plan()` and `apply_plan()` where concurrent CLI/worker sessions could simultaneously modify the same plan, corrupting plan state. `LockService` is now wired into the plan lifecycle with plan-level advisory @@ -713,7 +730,12 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). --- ### Fixed - +- **Plan Lifecycle Root Decision Type** (#9061): Fixed `PlanLifecycleService.start_strategize()` + to record a `prompt_definition` decision as the root of the decision tree instead of + `strategy_choice`. The root decision now correctly uses `question="What is the plan prompt?"` + and `chosen_option=plan.description`, ensuring the decision tree accurately reflects the + plan's prompt definition as the foundational decision during the Strategize phase, conforming + to the decision model specification. - **CLI (`agents actor remove`)** (#6491): Restores output parity with the other actor commands by honoring `--format`/`-f` for JSON/YAML/plain/Rich envelopes. Adds a Robot Framework regression test to assert the JSON diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 51815111f..fa9118365 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -33,4 +33,5 @@ Below are some of the specific details of various contributions. * HAL 9000 has contributed the LLMTraceRepository data-integrity fix (PR #8185 / issue #7505): replaced the unconditional `session.commit()` in `LLMTraceRepository.save()` with a dual-path implementation that respects the UnitOfWork pattern — flushing only when an external session is provided, and flushing + committing + closing when operating standalone. This eliminates premature transaction commits, loss of rollback capability, and a docstring/implementation mismatch. * HAL 9000 has contributed the ACMS Index Data Model and File Traversal Engine (PR #9664 / issue #9579): foundational data structures for indexed context entries with hot/warm/cold/archive storage tier classification, tag system, and a timeout-safe chunked file traversal engine for large projects with 10,000+ files. -* HAL 9000 has contributed the error-suppression removal fix (PR #9247 / issue #9060): removed both `try...except Exception:` blocks in `register_registry_agents()` that silently suppressed errors from `actor_registry.list_actors()` and the route bridge refresh, enabling exceptions to propagate per CONTRIBUTING.md fail-fast policy. Added three Behave scenarios verifying RuntimeError, AttributeError, and TypeError propagation. \ No newline at end of file +* HAL 9000 has contributed the error-suppression removal fix (PR #9247 / issue #9060): removed both `try...except Exception:` blocks in `register_registry_agents()` that silently suppressed errors from `actor_registry.list_actors()` and the route bridge refresh, enabling exceptions to propagate per CONTRIBUTING.md fail-fast policy. Added three Behave scenarios verifying RuntimeError, AttributeError, and TypeError propagation. + * HAL 9000 has contributed the plan lifecycle root decision type fix (#9061): corrected `PlanLifecycleService.start_strategize()` to record a `prompt_definition` decision as the root of the decision tree instead of `strategy_choice`, ensuring the decision tree correctly reflects the plan's prompt definition per the decision model specification. diff --git a/features/plan_lifecycle_bug9061.feature b/features/plan_lifecycle_bug9061.feature new file mode 100644 index 000000000..2738d6354 --- /dev/null +++ b/features/plan_lifecycle_bug9061.feature @@ -0,0 +1,25 @@ +@plan_lifecycle @bug9061 +Feature: Plan lifecycle records prompt_definition as root decision during Strategize + As a developer + I want the plan lifecycle to record a prompt_definition decision as the root during Strategize + So that the decision tree conforms to the specification + + Background: + Given I have a fresh plan lifecycle service for bug 9061 + + Scenario: start_strategize records prompt_definition as root decision + Given a plan lifecycle service with a capturing decision service for bug 9061 + And an action "local/bug9061-action" exists for bug 9061 + And a plan created from "local/bug9061-action" for bug 9061 + When I start strategize for bug 9061 + Then the bug9061 recorded decision type should be "prompt_definition" + And the bug9061 recorded decision should have no parent (root) + And the bug9061 recorded decision question should be "What is the plan prompt?" + And the bug9061 recorded decision chosen_option should equal the plan description + + Scenario: start_strategize root decision chosen_option matches plan description + Given a plan lifecycle service with a capturing decision service for bug 9061 + And an action "local/bug9061-desc" exists for bug 9061 with description "Deploy the application to production" + And a plan created from "local/bug9061-desc" for bug 9061 + When I start strategize for bug 9061 + Then the bug9061 recorded decision chosen_option should be "Deploy the application to production" diff --git a/features/steps/plan_lifecycle_bug9061_steps.py b/features/steps/plan_lifecycle_bug9061_steps.py new file mode 100644 index 000000000..18b9feb8d --- /dev/null +++ b/features/steps/plan_lifecycle_bug9061_steps.py @@ -0,0 +1,174 @@ +"""Step definitions for plan_lifecycle_bug9061.feature. + +Verifies that PlanLifecycleService.start_strategize() records a +prompt_definition decision as the root (not strategy_choice). + +Bug: #9061 - Plan lifecycle records strategy_choice as root decision +instead of prompt_definition during Strategize phase. +""" + +from __future__ import annotations + +from unittest.mock import MagicMock + +from behave import given, then, when +from behave.runner import Context + +from cleveragents.application.services.plan_lifecycle_service import ( + PlanLifecycleService, +) +from cleveragents.config.settings import Settings +from cleveragents.domain.models.core.plan import ( + ProjectLink, +) + +# Background + + +@given("I have a fresh plan lifecycle service for bug 9061") +def step_create_fresh_service_bug9061(context: Context) -> None: + """Create a clean PlanLifecycleService for bug 9061 tests.""" + Settings._instance = None + settings = Settings() + context.service = PlanLifecycleService(settings=settings) + context.error = None + context.captured_calls = [] + + +def _create_action_bug9061( + context: Context, name: str, description: str | None = None +) -> object: + """Create a basic action with sensible defaults.""" + return context.service.create_action( + name=name, + description=description or f"Action {name}", + definition_of_done="Tests pass", + strategy_actor="openai/gpt-4", + execution_actor="openai/gpt-4", + ) + + +@given("a plan lifecycle service with a capturing decision service for bug 9061") +def step_create_service_with_capturing_ds(context: Context) -> None: + """Create a PlanLifecycleService with a DecisionService that captures calls.""" + Settings._instance = None + settings = Settings() + mock_ds = MagicMock() + context.captured_calls = [] + + def _capture_record_decision(**kwargs: object) -> MagicMock: + context.captured_calls.append(dict(kwargs)) + result = MagicMock() + result.decision_id = "01ABCDEFGHIJKLMNOPQRSTUVWX" + result.decision_type = kwargs.get("decision_type") + result.parent_decision_id = kwargs.get("parent_decision_id") + result.question = kwargs.get("question") + result.chosen_option = kwargs.get("chosen_option") + return result + + mock_ds.record_decision.side_effect = _capture_record_decision + context.service = PlanLifecycleService( + settings=settings, + decision_service=mock_ds, + ) + context.mock_decision_service = mock_ds + context.error = None + + +@given('an action "{name}" exists for bug 9061') +def step_create_named_action_bug9061(context: Context, name: str) -> None: + """Create an action with the given name.""" + context.action = _create_action_bug9061(context, name) + context.plan_description = context.action.description + + +@given('an action "{name}" exists for bug 9061 with description "{description}"') +def step_create_named_action_with_desc_bug9061( + context: Context, name: str, description: str +) -> None: + """Create an action with the given name and description.""" + context.action = _create_action_bug9061(context, name, description=description) + context.plan_description = description + + +@given('a plan created from "{action_name}" for bug 9061') +def step_create_plan_from_action_bug9061(context: Context, action_name: str) -> None: + """Create a plan from the named action.""" + context.plan = context.service.use_action( + action_name=action_name, + project_links=[ProjectLink(project_name="proj-bug9061")], + ) + + +@when("I start strategize for bug 9061") +def step_start_strategize_bug9061(context: Context) -> None: + """Start strategize phase.""" + context.error = None + try: + pid = context.plan.identity.plan_id + context.plan = context.service.start_strategize(pid) + except Exception as e: + context.error = e + + +@then('the bug9061 recorded decision type should be "{expected_type}"') +def step_verify_recorded_decision_type(context: Context, expected_type: str) -> None: + """Verify the first recorded decision has the expected type.""" + assert context.error is None, f"Unexpected error: {context.error}" + assert context.captured_calls, "No decision was recorded" + first_call = context.captured_calls[0] + actual_type = str(first_call.get("decision_type", "")) + assert actual_type == expected_type, ( + f"Expected decision_type={expected_type!r}, got {actual_type!r}" + ) + + +@then("the bug9061 recorded decision should have no parent (root)") +def step_verify_recorded_decision_no_parent(context: Context) -> None: + """Verify the first recorded decision has no parent (is root).""" + assert context.captured_calls, "No decision was recorded" + first_call = context.captured_calls[0] + parent_id = first_call.get("parent_decision_id") + assert parent_id is None, ( + f"Expected parent_decision_id=None (root), got {parent_id!r}" + ) + + +@then('the bug9061 recorded decision question should be "{expected_question}"') +def step_verify_recorded_decision_question( + context: Context, expected_question: str +) -> None: + """Verify the first recorded decision has the expected question.""" + assert context.captured_calls, "No decision was recorded" + first_call = context.captured_calls[0] + actual_question = first_call.get("question", "") + assert actual_question == expected_question, ( + f"Expected question={expected_question!r}, got {actual_question!r}" + ) + + +@then("the bug9061 recorded decision chosen_option should equal the plan description") +def step_verify_chosen_option_equals_description( + context: Context, +) -> None: + """Verify the first recorded decision chosen_option equals the plan description.""" + assert context.captured_calls, "No decision was recorded" + first_call = context.captured_calls[0] + actual_chosen = first_call.get("chosen_option", "") + expected = context.plan.description + assert actual_chosen == expected, ( + f"Expected chosen_option={expected!r}, got {actual_chosen!r}" + ) + + +@then('the bug9061 recorded decision chosen_option should be "{expected_option}"') +def step_verify_recorded_decision_chosen_option( + context: Context, expected_option: str +) -> None: + """Verify the first recorded decision chosen_option matches expected value.""" + assert context.captured_calls, "No decision was recorded" + first_call = context.captured_calls[0] + actual_chosen = first_call.get("chosen_option", "") + assert actual_chosen == expected_option, ( + f"Expected chosen_option={expected_option!r}, got {actual_chosen!r}" + ) diff --git a/src/cleveragents/application/services/plan_lifecycle_service.py b/src/cleveragents/application/services/plan_lifecycle_service.py index b3b28aa2d..0adb8b048 100644 --- a/src/cleveragents/application/services/plan_lifecycle_service.py +++ b/src/cleveragents/application/services/plan_lifecycle_service.py @@ -1400,9 +1400,9 @@ class PlanLifecycleService: self._try_record_decision( plan_id=plan_id, - decision_type="strategy_choice", - question="Which strategy should the plan follow?", - chosen_option=f"Begin strategize phase for plan {plan_id}", + decision_type=DecisionType.PROMPT_DEFINITION, + question="What is the plan prompt?", + chosen_option=plan.description, ) return plan -- 2.52.0 From ac1ef6a9ae95ee74074d53f3ecbb382462be6bbe Mon Sep 17 00:00:00 2001 From: CleverAgents Bot Date: Wed, 10 Jun 2026 20:20:46 -0400 Subject: [PATCH 2/2] ci: stop master workflow on PR updates Remove the stale pull_request trigger from master.yml so PR branch commits do not launch the master workflow. Maintenance patch for PR #10998. --- .forgejo/workflows/master.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.forgejo/workflows/master.yml b/.forgejo/workflows/master.yml index 7c959ba40..ccdede22d 100644 --- a/.forgejo/workflows/master.yml +++ b/.forgejo/workflows/master.yml @@ -3,8 +3,6 @@ name: CI on: push: branches: [master, develop] - pull_request: - branches: [master, develop] vars: docker_prefix: "http://harbor.cleverthis.com/docker/" -- 2.52.0