- A new MIT-licensed spike from Focused.io shows how to wire LangGraph subgraphs across HTTP — and surfaces a non-obvious state gotcha: RemoteGraph returns the full subgraph state, not a delta.
- Get reducers wrong and your parent graph silently double-counts.
TL;DR
LangChain just spotlighted focused-dot-io/remote-graph — a minimal, MIT-licensed example of running a LangGraph parent on one server and a subgraph on another, wired together over HTTP via RemoteGraph. The headline insight is small but load-bearing: RemoteGraph returns the full remote state, not a delta. If your parent and subgraph both attach operator.add to the same list key, you will silently double-count items. The fix is one line of design: only the parent owns the reducer.
What's new
The repo itself is intentionally tiny — a parent graph on port 2024, a subgraph on port 2025, and a three-node parent flow (before → remote → after) where remote is a RemoteGraph pointed at the subgraph server. Both langgraph.json files declare "dependencies": [".."] and "env": "../.env", so the two dev servers share a single pyproject.toml and a single .env at the repo root. The parent reads SUBGRAPH_URL (default http://localhost:2025) to locate its remote dependency.
What makes this worth a spotlight isn't the scaffolding. It's the README's blunt "Gotcha" section, written by a team that just ran into the bug at a Friday code club at Focused.io and decided to publish the workaround instead of pretending it didn't bite them.
Why it matters
Most LangGraph tutorials show you local subgraphs — a CompiledStateGraph dropped in as a node, sharing the parent's process, returning deltas, projecting state through schema matching. That model is forgiving. The parent and child can declare different reducers on the same key; the framework reconciles it for you.
RemoteGraph looks identical from the outside — same Runnable interface, same .invoke() / .stream() / .get_state() calls. But the moment you cross an HTTP boundary, semantics shift: the node update is the entire remote state, including everything the parent passed in. If you reused your local subgraph patterns verbatim, your tests will pass and your production traces will look subtly wrong for weeks.
Technical facts
| Property | Local subgraph | RemoteGraph |
|---|---|---|
| Wire | In-process call | HTTP, LangGraph Server API |
| Node update | Delta (changed keys) | Full subgraph state |
| Reducer ownership | Parent & child can both declare | Parent only — or you double-count |
| Checkpoint thread IDs | Any string | UUIDs required |
| Calling same deployment | Fine | Forbidden — deadlock risk |
| Available since | Core LangGraph | langgraph v0.2 (latest v1.1.9) |
The class talks to standard LangGraph Server endpoints — POST /threads/{thread_id}/runs/stream, POST /threads/{thread_id}/state, GET /assistants/{assistant_id}/graph, and friends. Init requires at least one of url, client, or sync_client; otherwise it raises ValueError at runtime.
The reducer trap, in plain code
The repo's parent state has a list key steps with operator.add as its reducer. The parent appends "before" to steps, then calls the remote node, then appends "after". If the subgraph also declares operator.add on steps, here is what actually happens:
- Parent passes
steps=["before"]to the subgraph. - Subgraph appends
"sub"internally → its state becomes["before", "sub"]. - RemoteGraph returns the full subgraph state:
["before", "sub"]. - Parent's reducer adds that list to its own current
["before"]→["before", "before", "sub"].
The fix in the repo: the subgraph omits the reducer on steps and returns only its own contribution (["sub"]). The parent's operator.add handles all accumulation. Two other escape hatches the README calls out: use disjoint keys in parent and child so they cannot collide, or wrap the RemoteGraph call in a custom node that whitelists which fields propagate upward.
Where this fits in the scaling playbook
Lina Faik's recent breakdown on scaling LangGraph agents frames the choice nicely. As a graph grows from 5 nodes to 30+, you reach for four primitives in order: static parallelization to cut latency on fixed independent calls; map-reduce via the Send API for variable workloads; local subgraphs to encapsulate domain logic; and finally RemoteGraph when an agent deserves its own deployment cadence, scaling profile, or team ownership. RemoteGraph is the heaviest tool in the box — only reach for it when in-process composition is no longer enough.
Use cases
- Agents-as-microservices: different teams ship LangGraph deployments on independent cycles, glued by stable I/O schemas.
- Reusable agents-as-tools: expose a deployed agent as a node in any number of consumer graphs.
- Dev/prod parity: build with
CompiledGraphlocally, deploy to LangSmith, call withRemoteGraphin prod — zero code change at the call site. - Human-in-the-loop across services: create a thread, pass its ID through
config, persist checkpoints between remote invocations.
Limitations & pricing
Hard rules: never call the same deployment with RemoteGraph — the docs explicitly warn about deadlocks and resource exhaustion. If you need composition inside one deployment, use local subgraphs. Runs are stateless unless you wire a thread, and any checkpointer on a graph containing a RemoteGraph node requires UUID thread IDs. There is also an open upstream issue (langgraph#4879) where remote subgraphs using interrupt don't always resume cleanly — worth tracking if your design depends on remote human-in-the-loop pauses.
Cost-wise, the focused-dot-io repo is MIT and free. RemoteGraph ships inside the OSS langgraph package. The remote target needs a running Agent Server — either self-hosted or LangSmith Deployment, priced per LangSmith tier.
What's next
This is a spike, not a library — the value is in the gotcha-and-fix being written down where the next person can find it. Expect tighter remote-checkpoint and interrupt semantics in upstream LangGraph as more teams push real distributed agent topologies into production. If you are about to wire your first RemoteGraph, do two things before anything else: read the spike's README, and decide which side of every shared state key owns the reducer. One side. Always.
Sources: focused-dot-io/remote-graph, LangChain RemoteGraph docs, RemoteGraph API reference, Scaling LangGraph Agents — Lina Faik.
