Summary
Both agent/architect/graph.py and agent/developer/graph.py define LangGraph StateGraph workflows that contain conditional cycles between tool-calling nodes and routing nodes, but workflow.compile() is invoked without an explicit recursion_limit argument. As a result, both agents fall back to LangGraph's default ceiling (25 super-steps) — when the LLM's tool-selection logic stalls in a loop, the run aborts with GraphRecursionError rather than terminating gracefully with a partial result.
This was found by shingan, a workflow static-analyzer for agent graphs. It flagged 4 sites at confidence 0.9:
| File |
Node in cycle |
Cycle topology |
agent/architect/graph.py |
conduct_research |
conduct_research → tools → conduct_research |
agent/architect/graph.py |
come_up_with_research_next_step |
come_up_with_research_next_step → check_research_step → conduct_research → … → come_up_with_research_next_step |
agent/developer/graph.py |
get_clear_implementation_plan_for_atomic_task |
… → research_tool_node → get_clear_implementation_plan_for_atomic_task |
agent/developer/graph.py |
prepare_for_implementation |
prepare_for_implementation → … → prepare_for_implementation (via developer routing) |
Each cycle has a conditional exit branch — so they're not unbounded in principle — but the bound is implicit in the routing-function's contingent behavior, not declared at the graph layer.
Why this matters
For a research/SWE agent operating on real codebases, a 25-step ceiling is often too tight (large repos can need 30-40 tool calls just to map files + reason). The current symptom on the user side is a hard GraphRecursionError with no partial output — annoying to debug, and the fix (compile(recursion_limit=N) or a RunnableConfig override) isn't discoverable from the code.
Two related risks:
- No max-iteration safety net if a routing function regresses into an unbounded oscillation (LLM keeps selecting
tools → conduct_research → tools). Production logs would show a hung agent for ~25 steps before crashing.
- No telemetry on iteration count. Even if the agent succeeds, there's no record of "this query needed 18 iterations" to feed back into prompt/router tuning.
Repro
git clone https://github.com/langtalks/swe-agent
cd swe-agent
pip install langgraph
python -c "
from agent.architect.graph import swe_architect
print(swe_architect.get_graph().draw_ascii())
# Note: no recursion_limit visible in the compiled graph config.
print(swe_architect.config) # → {'tags': ['research-agent-v3']}, no recursion_limit
"
Running on a sufficiently complex codebase (~50+ files) will hit the default ceiling on the architect's research loop before producing a plan.
Suggested fix (3 options, in order of effort)
Option 1 — Lowest effort: explicit recursion_limit at compile time.
# agent/architect/graph.py
swe_architect = workflow.compile().with_config({
"tags": ["research-agent-v3"],
"recursion_limit": 50, # research can take more steps than the default 25
})
# agent/developer/graph.py
swe_developer = workflow.compile().with_config({
"tags": ["developer-agent-v3"],
"recursion_limit": 50,
})
Pros: 2-line change, immediately removes the hard crash on medium repos.
Cons: still no per-cycle bound; a runaway router can still burn 50 steps.
Option 2 — Add an iteration counter to state, route to a terminal on overflow.
In agent/architect/state.py (and equivalent for developer):
class ArchitectState(TypedDict):
messages: Annotated[list[BaseMessage], add_messages]
research_iterations: int # default 0
# ...
In agent/architect/graph.py, modify the router (the function feeding add_conditional_edges after check_research_step):
MAX_RESEARCH_ITERATIONS = 20
def _should_continue_research(state: ArchitectState) -> Literal["conduct_research", "extract_implementation_plan"]:
if state.get("research_iterations", 0) >= MAX_RESEARCH_ITERATIONS:
return "extract_implementation_plan" # graceful fallback
# ... existing logic
And increment the counter in come_up_with_research_next_step. Pros: graceful degradation, no hard crash. Cons: ~15 lines of changes per agent.
Option 3 — Documentation only.
Add a ## Configuration section in README.md explaining that for large codebases, callers should pass config={"recursion_limit": 50} when invoking the agent (e.g. swe_architect.invoke(input, config={"recursion_limit": 50})). Lowest effort, but doesn't change default behavior.
Happy to send a PR for Option 1 (1-2 lines, no test changes) or Option 2 (with regression tests for the cap behavior) — whichever you'd prefer. If neither, Option 3's README note alone would still help discoverability.
Same pattern appeared in two other LangGraph-based researchers I scanned, where the maintainers chose Option 1 in one case and Option 2 in the other:
assafelovic/gpt-researcher — #1766
langchain-ai/open_deep_research — #269
Worth picking whichever is most consistent with how swe-agent is intended to be operated.
Summary
Both
agent/architect/graph.pyandagent/developer/graph.pydefine LangGraphStateGraphworkflows that contain conditional cycles between tool-calling nodes and routing nodes, butworkflow.compile()is invoked without an explicitrecursion_limitargument. As a result, both agents fall back to LangGraph's default ceiling (25 super-steps) — when the LLM's tool-selection logic stalls in a loop, the run aborts withGraphRecursionErrorrather than terminating gracefully with a partial result.This was found by shingan, a workflow static-analyzer for agent graphs. It flagged 4 sites at confidence 0.9:
agent/architect/graph.pyconduct_researchconduct_research → tools → conduct_researchagent/architect/graph.pycome_up_with_research_next_stepcome_up_with_research_next_step → check_research_step → conduct_research → … → come_up_with_research_next_stepagent/developer/graph.pyget_clear_implementation_plan_for_atomic_task… → research_tool_node → get_clear_implementation_plan_for_atomic_taskagent/developer/graph.pyprepare_for_implementationprepare_for_implementation → … → prepare_for_implementation(via developer routing)Each cycle has a conditional exit branch — so they're not unbounded in principle — but the bound is implicit in the routing-function's contingent behavior, not declared at the graph layer.
Why this matters
For a research/SWE agent operating on real codebases, a 25-step ceiling is often too tight (large repos can need 30-40 tool calls just to map files + reason). The current symptom on the user side is a hard
GraphRecursionErrorwith no partial output — annoying to debug, and the fix (compile(recursion_limit=N)or aRunnableConfigoverride) isn't discoverable from the code.Two related risks:
tools→conduct_research→tools). Production logs would show a hung agent for ~25 steps before crashing.Repro
Running on a sufficiently complex codebase (~50+ files) will hit the default ceiling on the architect's research loop before producing a plan.
Suggested fix (3 options, in order of effort)
Option 1 — Lowest effort: explicit
recursion_limitat compile time.Pros: 2-line change, immediately removes the hard crash on medium repos.
Cons: still no per-cycle bound; a runaway router can still burn 50 steps.
Option 2 — Add an iteration counter to state, route to a terminal on overflow.
In
agent/architect/state.py(and equivalent for developer):In
agent/architect/graph.py, modify the router (the function feedingadd_conditional_edgesaftercheck_research_step):And increment the counter in
come_up_with_research_next_step. Pros: graceful degradation, no hard crash. Cons: ~15 lines of changes per agent.Option 3 — Documentation only.
Add a
## Configurationsection inREADME.mdexplaining that for large codebases, callers should passconfig={"recursion_limit": 50}when invoking the agent (e.g.swe_architect.invoke(input, config={"recursion_limit": 50})). Lowest effort, but doesn't change default behavior.Happy to send a PR for Option 1 (1-2 lines, no test changes) or Option 2 (with regression tests for the cap behavior) — whichever you'd prefer. If neither, Option 3's README note alone would still help discoverability.
Same pattern appeared in two other LangGraph-based researchers I scanned, where the maintainers chose Option 1 in one case and Option 2 in the other:
assafelovic/gpt-researcher— #1766langchain-ai/open_deep_research— #269Worth picking whichever is most consistent with how
swe-agentis intended to be operated.