UAT: PlanGenerationGraph uses legacy Plan model (.id, .prompt) incompatible with v3 LifecyclePlan domain model #3977

Open
opened 2026-04-06 08:12:49 +00:00 by freemo · 0 comments
Owner

Metadata

  • Branch: fix/plan-generation-graph-v3-plan-model
  • Commit Message: fix(agents): update PlanGenerationGraph to use v3 LifecyclePlan instead of legacy Plan
  • Milestone: None (Backlog)
  • Parent Epic: #397

Backlog note: This issue was discovered during UAT of the Estimation and Planning Intelligence feature area. It does not block milestone completion and has been placed in the backlog for human review and future milestone assignment.


Bug Report

Feature Area: Estimation and Planning Intelligence — Plan Generation Graph
Severity: High — PlanGenerationGraph is broken for v3 plans; it accesses non-existent attributes
Found by: UAT tester (code-level analysis)


Background and Context

The spec describes plan generation as driven by a LangGraph-based workflow. PlanGenerationGraph in src/cleveragents/agents/graphs/plan_generation.py is the LangGraph implementation for generating code plans.

The codebase has two Plan models:

  1. Legacy Plan (plan_legacy.py): Has .id: int | None and .prompt: str fields
  2. v3 LifecyclePlan (plan.py): Has .identity.plan_id: str (ULID) and .description: str fields

Current Behavior

PlanGenerationGraph imports Plan from cleveragents.domain.models.core:

from cleveragents.domain.models.core import (
    Change,
    Context,
    OperationType,
    Plan,
    Project,
)

In domain/models/core/__init__.py, the unqualified Plan name resolves to the legacy Plan from plan_legacy.py (not the v3 LifecyclePlan). This is confirmed by the __init__.py which imports:

from cleveragents.domain.models.core.plan import Plan as LifecyclePlan
from cleveragents.domain.models.core.plan_legacy import (
    Plan,  # <-- this is what PlanGenerationGraph gets
    ...
)

The PlanGenerationGraph._generate_plan() method then accesses:

plan_id = state["plan"].id if state["plan"].id else 0

And the invoke(), ainvoke(), and stream() methods use:

"prompt": plan.prompt or "",

These attributes (.id as integer, .prompt) exist on the legacy Plan but not on the v3 LifecyclePlan. The v3 plan uses:

  • .identity.plan_id (ULID string) instead of .id (integer)
  • .description instead of .prompt

Impact

When PlanGenerationGraph is invoked with a v3 LifecyclePlan:

  1. plan.id returns None (v3 Plan has no .id attribute), causing plan_id = 0 — an invalid plan ID
  2. plan.prompt raises AttributeError — v3 Plan has no .prompt attribute
  3. The generated Change objects get plan_id=0 instead of the real ULID
  4. The entire plan generation workflow silently produces incorrect results

Expected Behavior (per spec)

PlanGenerationGraph should accept and correctly use v3 LifecyclePlan objects:

  • Use plan.identity.plan_id for the plan identifier
  • Use plan.description (or plan.definition_of_done) as the prompt/goal

Code Location

  • File: src/cleveragents/agents/graphs/plan_generation.py
  • Lines: 50 (import), 680, 729, 777 (plan.prompt access), 657 (plan.id access)
  • Root cause: from cleveragents.domain.models.core import Plan resolves to legacy model

Steps to Reproduce

  1. Create a v3 LifecyclePlan object
  2. Instantiate PlanGenerationGraph with an LLM
  3. Call graph.invoke(project, v3_plan, contexts)
  4. Observe: AttributeError: 'Plan' object has no attribute 'prompt' (or silent plan_id=0)

Subtasks

  • Update PlanGenerationGraph to import LifecyclePlan from plan.py instead of legacy Plan
  • Replace plan.id with plan.identity.plan_id in _generate_plan()
  • Replace plan.prompt with plan.description (or plan.definition_of_done) in invoke(), ainvoke(), stream()
  • Update PlanGenerationState TypedDict to use LifecyclePlan type annotation
  • Add/update unit tests (Behave feature file) to cover v3 plan integration
  • Verify PlanGenerationGraph works end-to-end with a v3 plan

Definition of Done

  • PlanGenerationGraph correctly accepts and uses v3 LifecyclePlan objects
  • No AttributeError when passing a v3 plan to invoke(), ainvoke(), or stream()
  • Generated Change objects carry the correct ULID plan ID
  • All existing tests pass
  • New tests cover the v3 plan integration path
  • All nox stages pass
  • Coverage >= 97%
  • PR merged and issue closed

Automated by CleverAgents Bot
Supervisor: UAT Testing | Agent: ca-new-issue-creator

## Metadata - **Branch**: `fix/plan-generation-graph-v3-plan-model` - **Commit Message**: `fix(agents): update PlanGenerationGraph to use v3 LifecyclePlan instead of legacy Plan` - **Milestone**: None (Backlog) - **Parent Epic**: #397 > **Backlog note:** This issue was discovered during UAT of the Estimation and Planning Intelligence feature area. It does not block milestone completion and has been placed in the backlog for human review and future milestone assignment. --- ## Bug Report **Feature Area:** Estimation and Planning Intelligence — Plan Generation Graph **Severity:** High — `PlanGenerationGraph` is broken for v3 plans; it accesses non-existent attributes **Found by:** UAT tester (code-level analysis) --- ## Background and Context The spec describes plan generation as driven by a LangGraph-based workflow. `PlanGenerationGraph` in `src/cleveragents/agents/graphs/plan_generation.py` is the LangGraph implementation for generating code plans. The codebase has two `Plan` models: 1. **Legacy `Plan`** (`plan_legacy.py`): Has `.id: int | None` and `.prompt: str` fields 2. **v3 `LifecyclePlan`** (`plan.py`): Has `.identity.plan_id: str` (ULID) and `.description: str` fields ## Current Behavior `PlanGenerationGraph` imports `Plan` from `cleveragents.domain.models.core`: ```python from cleveragents.domain.models.core import ( Change, Context, OperationType, Plan, Project, ) ``` In `domain/models/core/__init__.py`, the unqualified `Plan` name resolves to the **legacy** `Plan` from `plan_legacy.py` (not the v3 `LifecyclePlan`). This is confirmed by the `__init__.py` which imports: ```python from cleveragents.domain.models.core.plan import Plan as LifecyclePlan from cleveragents.domain.models.core.plan_legacy import ( Plan, # <-- this is what PlanGenerationGraph gets ... ) ``` The `PlanGenerationGraph._generate_plan()` method then accesses: ```python plan_id = state["plan"].id if state["plan"].id else 0 ``` And the `invoke()`, `ainvoke()`, and `stream()` methods use: ```python "prompt": plan.prompt or "", ``` These attributes (`.id` as integer, `.prompt`) exist on the **legacy** `Plan` but **not** on the v3 `LifecyclePlan`. The v3 plan uses: - `.identity.plan_id` (ULID string) instead of `.id` (integer) - `.description` instead of `.prompt` ## Impact When `PlanGenerationGraph` is invoked with a v3 `LifecyclePlan`: 1. `plan.id` returns `None` (v3 Plan has no `.id` attribute), causing `plan_id = 0` — an invalid plan ID 2. `plan.prompt` raises `AttributeError` — v3 Plan has no `.prompt` attribute 3. The generated `Change` objects get `plan_id=0` instead of the real ULID 4. The entire plan generation workflow silently produces incorrect results ## Expected Behavior (per spec) `PlanGenerationGraph` should accept and correctly use v3 `LifecyclePlan` objects: - Use `plan.identity.plan_id` for the plan identifier - Use `plan.description` (or `plan.definition_of_done`) as the prompt/goal ## Code Location - **File**: `src/cleveragents/agents/graphs/plan_generation.py` - **Lines**: 50 (import), 680, 729, 777 (`plan.prompt` access), 657 (`plan.id` access) - **Root cause**: `from cleveragents.domain.models.core import Plan` resolves to legacy model ## Steps to Reproduce 1. Create a v3 `LifecyclePlan` object 2. Instantiate `PlanGenerationGraph` with an LLM 3. Call `graph.invoke(project, v3_plan, contexts)` 4. Observe: `AttributeError: 'Plan' object has no attribute 'prompt'` (or silent `plan_id=0`) ## Subtasks - [ ] Update `PlanGenerationGraph` to import `LifecyclePlan` from `plan.py` instead of legacy `Plan` - [ ] Replace `plan.id` with `plan.identity.plan_id` in `_generate_plan()` - [ ] Replace `plan.prompt` with `plan.description` (or `plan.definition_of_done`) in `invoke()`, `ainvoke()`, `stream()` - [ ] Update `PlanGenerationState` TypedDict to use `LifecyclePlan` type annotation - [ ] Add/update unit tests (Behave feature file) to cover v3 plan integration - [ ] Verify `PlanGenerationGraph` works end-to-end with a v3 plan ## Definition of Done - `PlanGenerationGraph` correctly accepts and uses v3 `LifecyclePlan` objects - No `AttributeError` when passing a v3 plan to `invoke()`, `ainvoke()`, or `stream()` - Generated `Change` objects carry the correct ULID plan ID - All existing tests pass - New tests cover the v3 plan integration path - All nox stages pass - Coverage >= 97% - PR merged and issue closed --- **Automated by CleverAgents Bot** Supervisor: UAT Testing | Agent: ca-new-issue-creator
HAL9000 added this to the v3.5.0 milestone 2026-04-09 03:12:25 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Blocks
#397 Epic: Server & Autonomy Infrastructure
cleveragents/cleveragents-core
Reference
cleveragents/cleveragents-core#3977
No description provided.