A minimal example of using RemoteGraph to call a subgraph that is running on
its own langgraph dev server.
parent (port 2024) ──HTTP──▶ subgraph (port 2025)
Parent is on 2024 because that's the default langgraph dev port and the one
Studio opens into — you want Studio pointed at the top-level graph you're
driving. The subgraph is a dependency, so it sits on 2025.
Both graphs share the same keys (message, steps), but only the parent puts
a reducer on steps — see the "Gotcha" section below for why:
class ParentState(TypedDict):
message: str
steps: Annotated[list[str], operator.add]
class SubgraphState(TypedDict):
message: str
steps: list[str] # no reducerThe parent invokes three nodes in order: before → remote → after, where
remote is a RemoteGraph pointed at the subgraph server. The steps list
accumulates across the HTTP boundary, so a successful run produces:
{
"message": "[subgraph] hello",
"steps": ["parent-before", "subgraph-processed", "parent-after"]
}uv syncYou need two terminals. Start the subgraph first so the parent can reach it.
Terminal 1 — subgraph on port 2025:
cd subgraph
uv run langgraph dev --port 2025 --no-browserTerminal 2 — parent on port 2024:
cd parent
uv run langgraph dev --port 2024The second server will open LangGraph Studio in your browser (pointed at the
parent). Invoke the parent graph with:
{ "message": "hello", "steps": [] }…and watch the run. You will see the remote node make an HTTP call to
localhost:2025, which is visible as a separate run in the subgraph server's
logs / Studio instance.
With both servers running:
from langgraph.pregel.remote import RemoteGraph
parent = RemoteGraph("parent", url="http://localhost:2024")
result = parent.invoke({"message": "hello", "steps": []})
print(result)The parent reads the subgraph URL from SUBGRAPH_URL (defaults to
http://localhost:2025). Set it in .env if you need to point at a different
host or port.
RemoteGraph returns the full remote state as the node's update, not just
the subgraph's delta. If both the parent and the subgraph declare
operator.add on the same list key, items the subgraph received as input come
back in its return value and the parent's reducer double-counts them.
The fix used here: the subgraph does not put a reducer on steps — it
returns only its own contribution, and the parent's reducer handles the
accumulation. See the comment in subgraph/graph.py.
Other options for more complex cases:
- Use different keys in the parent and subgraph so they can't collide
- Wrap the
RemoteGraphcall in a custom node that extracts only the fields you want from the remote result before returning the update
remote-graph-spike/
├── pyproject.toml # shared deps (langgraph, langgraph-cli[inmem], …)
├── subgraph/
│ ├── langgraph.json # exposes "subgraph", deps: [".."]
│ └── graph.py
├── parent/
│ ├── langgraph.json # exposes "parent", deps: [".."]
│ └── graph.py # uses RemoteGraph
└── README.md
Both langgraph.json files declare "dependencies": [".."] so each dev server
picks up the root pyproject.toml, and "env": "../.env" so they share a
single .env at the repo root (e.g. for SUBGRAPH_URL).
This was made at a Friday afternoon code club at Focused. Contributors included Jordan Kamm and Emilio Navarro driving, with Prachi More, Kevin Solorio, and Jonathan Eyler-Werve. Focused.io an enterprise agent consultancy that partners with Langchain on implementation of agents on LangChain, LangGraph and LangSmith and related technologies.