Let’s say I’m building a Claude Code style agent, which takes requests to make code changes, decomposes them into subtasks, and delegates each task to a coder agent.
With basic subgraph routing, I can first generate a list of tasks, and then use Send API to process them in parallel.
But I’m wondering if this might not be the ideal “Langgraph way”. Would it be possible to create a supervisor, and have the supervisor handoff to each coder subgraph in parallel? I notice that supervisor LLM is specifically hardcoded with parallel_tool_calls: false.
What is the preferred way to do this?
Hi @brettcassette
Run tasks in parallel using the graph’s built‑in parallelism primitives (conditional edges + Send, or the Functional API’s @task futures). Don’t rely on a “supervisor LLM with parallel_tool_calls=true”; the supervisor primitive is designed for routing/coordination, while parallelism is a runtime feature of the graph.
Key points
- Graph API parallelism: If a node has multiple outgoing edges, all target nodes run in parallel in the next super‑step. Use conditional edges and
Send to fan out dynamically (e.g., map over a generated task list). See “Graph API overview → Edges” and “Send” in the docs.
- Dynamic fan‑out: Return
[Send("coder_subgraph", {…}) for task in tasks] from a router node to start many coder runs concurrently, each with its own state. This is the LangGraph way to do map/reduce style parallelism.
- Subgraphs: Model each coder as a subgraph. The parent graph (your supervisor) can invoke subgraphs with shared or private schemas. Parallelism happens because the parent routes to many subgraph nodes at once; the runtime (Pregel) executes them concurrently per super‑step.
- Supervisor LLM vs. parallelism:
parallel_tool_calls applies to a single LLM tool‑calling turn, not graph execution. Even with parallel_tool_calls: false on the supervisor, the graph still executes multiple branches concurrently. Let the supervisor decide routing; let the graph runtime handle parallelism.
- Functional API option: If you prefer imperative code, decorate coder calls as
@task and kick them off together, then .result() to join. Tasks run concurrently and are checkpointed.
Recommended patterns
- Graph API (stateful, visualizable, works great with subgraphs)
- Router/supervisor node produces a list of subtasks
- Router returns
Send("coder", sub_state) for each subtask
- Optional aggregator node waits for results and reduces
- Functional API (imperative, minimal scaffolding)
@entrypoint decomposes work → spawn multiple @task coder calls
- Await or
.result() to join; checkpointing and resume work out of the box
What to avoid
- Forcing parallelism inside one LLM call via
parallel_tool_calls. It’s orthogonal and unnecessary; rely on graph branching or tasks.
Doc pointers
- Graph API: parallel branches and
Send (map‑reduce): Graph API overview → Edges and Send
- Subgraphs (shared vs different state schemas, persistence):
Use subgraphs
- Runtime semantics (Bulk Synchronous Parallel / super‑steps):
LangGraph runtime
- Functional API:
@entrypoint, @task, parallel execution
Citations