Enforce plan.max-child-depth limit in DecompositionService._build_hierarchy() #10269

Closed
opened 2026-04-17 18:27:17 +00:00 by CoreRasurae · 1 comment
Member

Enforce plan.max-child-depth Recursion Limit in DecompositionService

Metadata

  • Commit Message: fix(decomposition): enforce plan.max-child-depth recursion limit to prevent unbounded hierarchy growth
  • Branch: fix/enforce-max-child-depth

Background and Context

The plan.max-child-depth configuration key is defined in the ConfigService (default: 5, project-scopable per docs/specification.md line 30706) with the stated purpose: "Maximum nesting depth for child plan spawning. Prevents runaway recursive plan decomposition."

However, the recursive _build_hierarchy() method in DecompositionService (file: src/cleveragents/application/services/decomposition_service.py) has NO guard preventing recursion beyond this limit. The function unconditionally recurses: depth + 1 at each level without checking if depth >= config.max_child_depth.

This means:

  • A project's natural file structure can produce arbitrarily deep hierarchies
  • Complex decomposition heuristics can exceed the safety limit without triggering any guard or warning
  • The specification's stated purpose of max-child-depth (preventing runaway recursion) is not enforced
  • Child plans could nest deeper than v3.5.0 acceptance criteria allow

Current Behavior

# src/cleveragents/application/services/decomposition_service.py:_build_hierarchy()
# (Lines ~180-220, simplified)

def _build_hierarchy(
    self,
    files: list[str],
    config: DecompositionConfig,
    depth: int,
    parent_id: str | None,
    nodes: list[DecompositionNode],
) -> int:
    """Recursively partition files into a tree."""
    
    # ... clustering logic ...
    
    for cluster in clusters:
        child_depth = self._build_hierarchy(
            files=cluster,
            config=config,
            depth=depth + 1,  # ❌ No check: if depth >= config.max_child_depth, stop
            parent_id=node_id,
            nodes=nodes,
        )
        # ... continue recursing unconditionally ...

Result: Recursion continues indefinitely (until files are exhausted or memory runs out), potentially exceeding max_child_depth.


Expected Behavior

# src/cleveragents/application/services/decomposition_service.py:_build_hierarchy() (corrected)

def _build_hierarchy(
    self,
    files: list[str],
    config: DecompositionConfig,
    depth: int,
    parent_id: str | None,
    nodes: list[DecompositionNode],
) -> int:
    """Recursively partition files into a tree, respecting max_child_depth limit."""
    
    # ✅ NEW: Guard against exceeding max depth
    max_child_depth = config.get("plan.max-child-depth", 5)
    if depth >= max_child_depth:
        # Create a leaf node without further recursion
        logger.warning(
            f"Decomposition reached max-child-depth limit ({max_child_depth}) "
            f"at depth {depth}. Creating terminal node for {len(files)} files."
        )
        # Create a final node with all remaining files as a leaf
        leaf_node = DecompositionNode(
            node_id=...,
            depth=depth,
            file_paths=files,
            ...
        )
        nodes.append(leaf_node)
        return depth
    
    # ... normal clustering and recursion ...
    for cluster in clusters:
        child_depth = self._build_hierarchy(
            files=cluster,
            config=config,
            depth=depth + 1,
            parent_id=node_id,
            nodes=nodes,
        )
        max_child_depth = max(max_child_depth, child_depth)
    
    return max_child_depth

Result: Recursion stops at the configured limit, preventing unbounded nesting.


Acceptance Criteria

  1. _build_hierarchy() checks the plan.max-child-depth configuration at each recursion level
  2. When depth >= max_child_depth, recursion stops and the current files are assigned to a leaf node (no further subdivision)
  3. A warning log is emitted when the depth limit is reached: "Decomposition reached max-child-depth limit..."
  4. The method returns the actual maximum depth reached (capped at or below max_child_depth)
  5. Existing decomposition tests continue to pass
  6. New BDD scenario added: "Decomposition stops recursing when max-child-depth limit is reached"
  7. Test verifies that a large project with deep file structure respects the max-child-depth limit

Subtasks

  • Add guard logic at the start of _build_hierarchy() to check depth >= max_child_depth
  • When limit is reached, create a terminal node containing all remaining files (no further subdivision)
  • Add structured logging when depth limit is reached
  • Add BDD scenario: "Decomposition respects plan.max-child-depth configuration"
  • Implement step definitions to test with various max-child-depth values (1, 3, 5, 10)
  • Test with a large project fixture that would naturally exceed the limit
  • Run nox -e unit_tests to verify all Behave tests pass
  • Run nox -e integration_tests to verify no regressions
  • Verify coverage >=97% via nox -s coverage_report
  • Run full nox to verify all quality gates pass

Definition of Done

This issue is complete when:

  • All subtasks above are completed and checked off.
  • A Git commit is created where the first line of the commit message matches the Commit Message in Metadata exactly, followed by a blank line, then additional lines providing relevant implementation details.
  • The commit is pushed to the remote on the fix/enforce-max-child-depth branch.
  • The commit is submitted as a pull request to master, reviewed, and merged before this issue is marked done.
  • All quality gates pass: nox -e lint, nox -e typecheck, nox -e unit_tests, nox -e integration_tests, and coverage >=97%.
  • The specification's stated purpose ("prevents runaway recursive plan decomposition") is verifiably enforced.
# Enforce plan.max-child-depth Recursion Limit in DecompositionService ## Metadata - **Commit Message**: `fix(decomposition): enforce plan.max-child-depth recursion limit to prevent unbounded hierarchy growth` - **Branch**: `fix/enforce-max-child-depth` --- ## Background and Context The `plan.max-child-depth` configuration key is defined in the ConfigService (default: 5, project-scopable per `docs/specification.md` line 30706) with the stated purpose: **"Maximum nesting depth for child plan spawning. Prevents runaway recursive plan decomposition."** However, the recursive `_build_hierarchy()` method in `DecompositionService` (file: `src/cleveragents/application/services/decomposition_service.py`) has **NO guard** preventing recursion beyond this limit. The function unconditionally recurses: `depth + 1` at each level without checking if `depth >= config.max_child_depth`. This means: - A project's natural file structure can produce arbitrarily deep hierarchies - Complex decomposition heuristics can exceed the safety limit without triggering any guard or warning - The specification's stated purpose of `max-child-depth` (preventing runaway recursion) is not enforced - Child plans could nest deeper than v3.5.0 acceptance criteria allow --- ## Current Behavior ```python # src/cleveragents/application/services/decomposition_service.py:_build_hierarchy() # (Lines ~180-220, simplified) def _build_hierarchy( self, files: list[str], config: DecompositionConfig, depth: int, parent_id: str | None, nodes: list[DecompositionNode], ) -> int: """Recursively partition files into a tree.""" # ... clustering logic ... for cluster in clusters: child_depth = self._build_hierarchy( files=cluster, config=config, depth=depth + 1, # ❌ No check: if depth >= config.max_child_depth, stop parent_id=node_id, nodes=nodes, ) # ... continue recursing unconditionally ... ``` Result: Recursion continues indefinitely (until files are exhausted or memory runs out), potentially exceeding `max_child_depth`. --- ## Expected Behavior ```python # src/cleveragents/application/services/decomposition_service.py:_build_hierarchy() (corrected) def _build_hierarchy( self, files: list[str], config: DecompositionConfig, depth: int, parent_id: str | None, nodes: list[DecompositionNode], ) -> int: """Recursively partition files into a tree, respecting max_child_depth limit.""" # ✅ NEW: Guard against exceeding max depth max_child_depth = config.get("plan.max-child-depth", 5) if depth >= max_child_depth: # Create a leaf node without further recursion logger.warning( f"Decomposition reached max-child-depth limit ({max_child_depth}) " f"at depth {depth}. Creating terminal node for {len(files)} files." ) # Create a final node with all remaining files as a leaf leaf_node = DecompositionNode( node_id=..., depth=depth, file_paths=files, ... ) nodes.append(leaf_node) return depth # ... normal clustering and recursion ... for cluster in clusters: child_depth = self._build_hierarchy( files=cluster, config=config, depth=depth + 1, parent_id=node_id, nodes=nodes, ) max_child_depth = max(max_child_depth, child_depth) return max_child_depth ``` Result: Recursion stops at the configured limit, preventing unbounded nesting. --- ## Acceptance Criteria 1. `_build_hierarchy()` checks the `plan.max-child-depth` configuration at each recursion level 2. When `depth >= max_child_depth`, recursion stops and the current files are assigned to a leaf node (no further subdivision) 3. A warning log is emitted when the depth limit is reached: `"Decomposition reached max-child-depth limit..."` 4. The method returns the actual maximum depth reached (capped at or below `max_child_depth`) 5. Existing decomposition tests continue to pass 6. New BDD scenario added: "Decomposition stops recursing when max-child-depth limit is reached" 7. Test verifies that a large project with deep file structure respects the `max-child-depth` limit --- ## Subtasks - [ ] Add guard logic at the start of `_build_hierarchy()` to check `depth >= max_child_depth` - [ ] When limit is reached, create a terminal node containing all remaining files (no further subdivision) - [ ] Add structured logging when depth limit is reached - [ ] Add BDD scenario: "Decomposition respects plan.max-child-depth configuration" - [ ] Implement step definitions to test with various `max-child-depth` values (1, 3, 5, 10) - [ ] Test with a large project fixture that would naturally exceed the limit - [ ] Run `nox -e unit_tests` to verify all Behave tests pass - [ ] Run `nox -e integration_tests` to verify no regressions - [ ] Verify coverage >=97% via `nox -s coverage_report` - [ ] Run full `nox` to verify all quality gates pass --- ## Definition of Done This issue is complete when: - All subtasks above are completed and checked off. - A Git commit is created where the **first line** of the commit message matches the Commit Message in Metadata exactly, followed by a blank line, then additional lines providing relevant implementation details. - The commit is pushed to the remote on the `fix/enforce-max-child-depth` branch. - The commit is submitted as a **pull request** to `master`, reviewed, and **merged** before this issue is marked done. - All quality gates pass: `nox -e lint`, `nox -e typecheck`, `nox -e unit_tests`, `nox -e integration_tests`, and coverage >=97%. - The specification's stated purpose ("prevents runaway recursive plan decomposition") is verifiably enforced.
HAL9000 added this to the v3.5.0 milestone 2026-04-18 07:55:47 +00:00
Owner

[GROOMED] Quality Analysis Complete

Grooming Summary

Issue Validity: VALID & ACTIONABLE

This is a legitimate, well-written task to enforce the plan.max-child-depth configuration limit in the DecompositionService._build_hierarchy() method. The issue includes:

  • Clear background and context
  • Current vs. expected behavior comparison
  • Detailed acceptance criteria (7 items)
  • Comprehensive subtasks (10 items)
  • Definition of Done with quality gates

Label Assessment: ALL REQUIRED LABELS PRESENT

  • State/Unverified (id: 846) — Present
  • Type/Task (id: 857) — Present
  • Priority/High (id: 859) — Present

Recommendation: Move to State/Verified (this is a verified, actionable task)

Milestone Assignment: ASSIGNED

  • Milestone: v3.5.0 (M6: Autonomy Hardening)
  • Rationale: The issue directly supports v3.5.0 acceptance criterion: "Hierarchical decomposition creates 4+ levels of subplans." The plan.max-child-depth limit is essential for controlling recursion depth in hierarchical decomposition.

Epic/Parent Issue: NOT REQUIRED

This issue is directly part of the v3.5.0 milestone and does not require a separate epic parent.

Assignee: ⚠️ NOT ASSIGNED

The issue is currently unassigned. This is acceptable for a State/Unverified→State/Verified transition; assignment will occur during sprint planning.

Grooming Actions Taken

  1. Assigned to v3.5.0 milestone
  2. Label update (State/Unverified → State/Verified) — pending API permissions
  3. Posted this [GROOMED] comment

Next Steps

  • Move to State/Verified when label permissions are available
  • Assign to a developer during sprint planning
  • Implement per acceptance criteria and subtasks
  • Ensure all quality gates pass before merge

Automated by CleverAgents Bot
Supervisor: Grooming | Agent: grooming-pool-supervisor

[GROOMED] Quality Analysis Complete ## Grooming Summary ### Issue Validity: ✅ VALID & ACTIONABLE This is a legitimate, well-written task to enforce the `plan.max-child-depth` configuration limit in the DecompositionService._build_hierarchy() method. The issue includes: - Clear background and context - Current vs. expected behavior comparison - Detailed acceptance criteria (7 items) - Comprehensive subtasks (10 items) - Definition of Done with quality gates ### Label Assessment: ✅ ALL REQUIRED LABELS PRESENT - ✅ State/Unverified (id: 846) — Present - ✅ Type/Task (id: 857) — Present - ✅ Priority/High (id: 859) — Present **Recommendation**: Move to State/Verified (this is a verified, actionable task) ### Milestone Assignment: ✅ ASSIGNED - **Milestone**: v3.5.0 (M6: Autonomy Hardening) - **Rationale**: The issue directly supports v3.5.0 acceptance criterion: "Hierarchical decomposition creates 4+ levels of subplans." The `plan.max-child-depth` limit is essential for controlling recursion depth in hierarchical decomposition. ### Epic/Parent Issue: ✅ NOT REQUIRED This issue is directly part of the v3.5.0 milestone and does not require a separate epic parent. ### Assignee: ⚠️ NOT ASSIGNED The issue is currently unassigned. This is acceptable for a State/Unverified→State/Verified transition; assignment will occur during sprint planning. ## Grooming Actions Taken 1. ✅ Assigned to v3.5.0 milestone 2. ⏳ Label update (State/Unverified → State/Verified) — pending API permissions 3. ✅ Posted this [GROOMED] comment ## Next Steps - Move to State/Verified when label permissions are available - Assign to a developer during sprint planning - Implement per acceptance criteria and subtasks - Ensure all quality gates pass before merge --- **Automated by CleverAgents Bot** Supervisor: Grooming | Agent: grooming-pool-supervisor
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Reference
cleveragents/cleveragents-core#10269
No description provided.