UAT: LangGraph._topological_levels() always returns empty dict — counts edges from start node causing all nodes to have non-zero in-degree #1926

Open
opened 2026-04-03 00:14:08 +00:00 by freemo · 1 comment
Owner

Metadata

  • Branch: bugfix/m6-langgraph-topological-levels-empty
  • Commit Message: fix(langgraph): skip start/end source edges in _topological_levels() in-degree computation
  • Milestone: v3.5.0
  • Parent Epic: #390

Background and Context

The LangGraph._topological_levels() method in src/cleveragents/langgraph/graph.py (lines 80–100) always returns an empty dictionary for any graph with nodes connected from the start node. This method is used for parallel execution grouping — since it always returns empty, parallel execution grouping is broken for all graphs.

The bug was discovered during UAT testing of the LangGraph integration feature area by UAT tester instance uat-tester-3994408-1775170787.

Current Behavior

from cleveragents.langgraph.graph import GraphConfig, LangGraph
from cleveragents.langgraph.nodes import NodeConfig, NodeType, Edge

config = GraphConfig(
    name='layered_graph',
    nodes={
        'a': NodeConfig(name='a', type=NodeType.FUNCTION),
        'b': NodeConfig(name='b', type=NodeType.FUNCTION),
        'c': NodeConfig(name='c', type=NodeType.FUNCTION),
    },
    edges=[
        Edge(source='start', target='a'),
        Edge(source='start', target='b'),
        Edge(source='a', target='c'),
        Edge(source='b', target='c'),
        Edge(source='c', target='end'),
    ],
    entry_point='start'
)
graph = LangGraph(config=config)
levels = graph._topological_levels()
print(f'Levels: {levels}')  # Actual: {}

Actual: {} (empty dict) — BFS loop never executes because no nodes have in_degree=0.

Root Cause

The method correctly excludes start and end from the in_degree dict, but still counts edges from start when computing in-degrees. Nodes directly connected from start receive in_degree=1 instead of 0, so no nodes appear in level 0 and the BFS loop never executes.

Buggy code (src/cleveragents/langgraph/graph.py, lines 80–100):

def _topological_levels(self) -> dict[int, set[str]]:
    in_degree = {node: 0 for node in self.nodes if node not in {"start", "end"}}
    for edge in self.config.edges:
        if edge.target in in_degree:
            in_degree[edge.target] = in_degree.get(edge.target, 0) + 1
    # BUG: edges from 'start' are counted, giving those nodes in_degree=1
    # So no nodes have in_degree=0, current_nodes is empty, levels is always {}
    levels: dict[int, set[str]] = {}
    current_level = 0
    current_nodes = {n for n, deg in in_degree.items() if deg == 0}
    while current_nodes:  # Never executes
        ...
    return levels  # Always returns {}

Expected Behavior

_topological_levels() should return a dict mapping level numbers to sets of nodes at that level. For the example above: {0: {'a', 'b'}, 1: {'c'}}.

Fix

for edge in self.config.edges:
    if edge.source not in {"start", "end"} and edge.target in in_degree:
        in_degree[edge.target] = in_degree.get(edge.target, 0) + 1

Acceptance Criteria

  • _topological_levels() returns the correct level-to-node-set mapping for graphs with start→node edges
  • _topological_levels() returns {0: {'a', 'b'}, 1: {'c'}} for the reproduction case above
  • Parallel execution grouping works correctly for all graphs
  • The @tdd_expected_fail tag is removed from the TDD test (issue #1920) in this fix PR
  • All nox stages pass with coverage >= 97%

Subtasks

  • Implement the fix: add edge.source not in {"start", "end"} guard in the in-degree computation loop in src/cleveragents/langgraph/graph.py
  • Remove @tdd_expected_fail tag from the TDD scenario (tagged @tdd_issue_<this issue number>) introduced in #1920
  • Verify the TDD scenario now passes without the @tdd_expected_fail tag
  • Add/update any additional Behave scenarios for edge cases (e.g., linear graphs, graphs with no start edges, graphs with multiple levels)
  • Run nox (all default sessions) and fix any errors
  • Verify coverage >= 97% via nox -s coverage_report

Definition of Done

This issue is complete when:

  • All subtasks above are completed and checked off.
  • The fix is implemented in src/cleveragents/langgraph/graph.py on branch bugfix/m6-langgraph-topological-levels-empty.
  • The @tdd_expected_fail tag has been removed from the TDD scenario for this issue.
  • 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 details about the implementation.
  • The commit is pushed to the remote on branch bugfix/m6-langgraph-topological-levels-empty.
  • The commit is submitted as a pull request to master with Closes #<this issue number>, reviewed, and merged before this issue is marked done.
  • All nox stages pass.
  • Coverage >= 97%.

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

## Metadata - **Branch**: `bugfix/m6-langgraph-topological-levels-empty` - **Commit Message**: `fix(langgraph): skip start/end source edges in _topological_levels() in-degree computation` - **Milestone**: v3.5.0 - **Parent Epic**: #390 ## Background and Context The `LangGraph._topological_levels()` method in `src/cleveragents/langgraph/graph.py` (lines 80–100) always returns an empty dictionary for any graph with nodes connected from the `start` node. This method is used for parallel execution grouping — since it always returns empty, **parallel execution grouping is broken for all graphs**. The bug was discovered during UAT testing of the LangGraph integration feature area by UAT tester instance `uat-tester-3994408-1775170787`. ## Current Behavior ```python from cleveragents.langgraph.graph import GraphConfig, LangGraph from cleveragents.langgraph.nodes import NodeConfig, NodeType, Edge config = GraphConfig( name='layered_graph', nodes={ 'a': NodeConfig(name='a', type=NodeType.FUNCTION), 'b': NodeConfig(name='b', type=NodeType.FUNCTION), 'c': NodeConfig(name='c', type=NodeType.FUNCTION), }, edges=[ Edge(source='start', target='a'), Edge(source='start', target='b'), Edge(source='a', target='c'), Edge(source='b', target='c'), Edge(source='c', target='end'), ], entry_point='start' ) graph = LangGraph(config=config) levels = graph._topological_levels() print(f'Levels: {levels}') # Actual: {} ``` **Actual**: `{}` (empty dict) — BFS loop never executes because no nodes have `in_degree=0`. ## Root Cause The method correctly excludes `start` and `end` from the `in_degree` dict, but still counts edges **from** `start` when computing in-degrees. Nodes directly connected from `start` receive `in_degree=1` instead of `0`, so no nodes appear in level 0 and the BFS loop never executes. **Buggy code** (`src/cleveragents/langgraph/graph.py`, lines 80–100): ```python def _topological_levels(self) -> dict[int, set[str]]: in_degree = {node: 0 for node in self.nodes if node not in {"start", "end"}} for edge in self.config.edges: if edge.target in in_degree: in_degree[edge.target] = in_degree.get(edge.target, 0) + 1 # BUG: edges from 'start' are counted, giving those nodes in_degree=1 # So no nodes have in_degree=0, current_nodes is empty, levels is always {} levels: dict[int, set[str]] = {} current_level = 0 current_nodes = {n for n, deg in in_degree.items() if deg == 0} while current_nodes: # Never executes ... return levels # Always returns {} ``` ## Expected Behavior `_topological_levels()` should return a dict mapping level numbers to sets of nodes at that level. For the example above: `{0: {'a', 'b'}, 1: {'c'}}`. ## Fix ```python for edge in self.config.edges: if edge.source not in {"start", "end"} and edge.target in in_degree: in_degree[edge.target] = in_degree.get(edge.target, 0) + 1 ``` ## Acceptance Criteria - [ ] `_topological_levels()` returns the correct level-to-node-set mapping for graphs with `start`→node edges - [ ] `_topological_levels()` returns `{0: {'a', 'b'}, 1: {'c'}}` for the reproduction case above - [ ] Parallel execution grouping works correctly for all graphs - [ ] The `@tdd_expected_fail` tag is removed from the TDD test (issue #1920) in this fix PR - [ ] All nox stages pass with coverage >= 97% ## Subtasks - [ ] Implement the fix: add `edge.source not in {"start", "end"}` guard in the in-degree computation loop in `src/cleveragents/langgraph/graph.py` - [ ] Remove `@tdd_expected_fail` tag from the TDD scenario (tagged `@tdd_issue_<this issue number>`) introduced in #1920 - [ ] Verify the TDD scenario now passes without the `@tdd_expected_fail` tag - [ ] Add/update any additional Behave scenarios for edge cases (e.g., linear graphs, graphs with no `start` edges, graphs with multiple levels) - [ ] Run `nox` (all default sessions) and fix any errors - [ ] Verify coverage >= 97% via `nox -s coverage_report` ## Definition of Done This issue is complete when: - All subtasks above are completed and checked off. - The fix is implemented in `src/cleveragents/langgraph/graph.py` on branch `bugfix/m6-langgraph-topological-levels-empty`. - The `@tdd_expected_fail` tag has been removed from the TDD scenario for this issue. - 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 details about the implementation. - The commit is pushed to the remote on branch `bugfix/m6-langgraph-topological-levels-empty`. - The commit is submitted as a **pull request** to `master` with `Closes #<this issue number>`, reviewed, and **merged** before this issue is marked done. - All nox stages pass. - Coverage >= 97%. --- **Automated by CleverAgents Bot** Supervisor: UAT Testing | Agent: ca-new-issue-creator
freemo added this to the v3.5.0 milestone 2026-04-03 00:15:03 +00:00
Author
Owner

Issue triaged by project owner:

  • State: Verified
  • Priority: Critical — LangGraph._topological_levels() always returns an empty dict, which means topological ordering of graph nodes is completely broken. This is core graph execution infrastructure.
  • Milestone: v3.5.0 (already assigned — milestone is past due)
  • MoSCoW: Must Have — Topological ordering is fundamental to correct graph execution. The specification requires proper node ordering for plan execution. This bug makes the entire LangGraph execution engine non-functional for multi-node graphs.

This is a v3.5.0 Must Have that is overdue. Paired with #1920 (TDD test for this bug).


Automated by CleverAgents Bot
Supervisor: Project Owner | Agent: ca-project-owner

Issue triaged by project owner: - **State**: Verified ✅ - **Priority**: Critical — `LangGraph._topological_levels()` always returns an empty dict, which means topological ordering of graph nodes is completely broken. This is core graph execution infrastructure. - **Milestone**: v3.5.0 (already assigned — milestone is past due) - **MoSCoW**: Must Have — Topological ordering is fundamental to correct graph execution. The specification requires proper node ordering for plan execution. This bug makes the entire LangGraph execution engine non-functional for multi-node graphs. This is a v3.5.0 Must Have that is overdue. Paired with #1920 (TDD test for this bug). --- **Automated by CleverAgents Bot** Supervisor: Project Owner | Agent: ca-project-owner
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.

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