Subsequent sub graphs bubble up in LangGraph

I’m building a langgraph flow, where I have an architecture like this:
I have a main graph, with my main agent, let’s call this Graph A.
When A certain tool is invoked, I use the Command to go to a Sub-graph node, let’s call this Sub-Graph A.
When another certain tool in called, I use the Command again to go to a Sub-graph of node of my Sub-graph, let’s call this Sub-Graph B.

Now to the issue. I’ve built a functionality of returning back to my original Graph A for further iterations, this can happen from Sub-graph A, Sub-graph B, or a deeper Sub-graph in theory (This is a simplified version).

If I would have used only a single Sub-graph, then this would be easy, in my tool I’d simply do:

return Command(
    graph=Command.PARENT,
    goto=RouterAgentsNames.GOALS_AGENT,
    update=update_data,
)

But If I’m at Sub-graph B now, Command.PARENT will simply return to Sub-graph A, and Graph A.

I’ve managed to solve this using a “bubble up” version, but I’m not feeling confident that that’s the best practice for this.
That is, I’ve added to each graph/sub graph a node, where the root node is responsible to move to the correct agent. Something like this:

def add_back_to_the_future_up_node(self, is_root: bool, root_target: GeneralAgentsNames | None = None):
    if is_root:
        def root_up_node(state):
            payload = state.get("handoff_updates", {}) or {}
            return Command(
                goto=root_target,
                update={**payload, "handoff_updates": {}},  # fold + clear
            )            # at the top: land on root node
        self.graph_builder.add_node(
            BubbleUpNodeNames.BACK_TO_THE_FUTURE, root_up_node
        )
    else:
        # intermediates: bubble up one more level
        def up_node(state):
            payload = state.get("handoff_updates", {}) or {}
            return Command(
                graph=Command.PARENT,
                goto=BubbleUpNodeNames.BACK_TO_THE_FUTURE,
                update={"handoff_updates": payload},
            )
        self.graph_builder.add_node(
            BubbleUpNodeNames.BACK_TO_THE_FUTURE, up_node
        )

Then in my tool:

return Command(
    graph=Command.PARENT,
    goto=BubbleUpNodeNames.BACK_TO_THE_FUTURE,
    update={"handoff_updates": update_data},
)

That means that when the tool of returning back is called, it will bubble up to the first graph one by one, until it reaches node I defined as root.

I also know that I can use the Command to go directly into the root graph with an ID or a NAME, but that exists the graph invocation and requires a re-invocation of the graph, which is something I’m trying to avoid.

Another option is to decide not to use sub-graphs all together and have the flow with one graph, which will enable me to go to any node I want.

Is my implementation a good idea? Does it have any side effects I don’t see?

Hi @sagi.l ,

Your bubble-up node is a solid pattern and effectively handles the “jump to ancestor (not just immediate parent)” without re-invoking the graph. The Command API only guarantees PARENT (one hop) – there isn’t a built-in “jump to root ancestor” primitive, so a relay node per subgraph is the pragmatic workaround.

An alternative that’s often simpler if the parent is the one that calls the subgraph (so its modeled like a fn call) is by ending the subgraph and letting the parent resume and route to the right node. This can be done in 2 ways

  1. Caller-supplied on_return: the parent passes state[“on_return”] = RouterAgentsNames.GOALS_AGENT. Any early “return” inside the subgraph just sets state[“early_return”]=True and jumps to Command.PARENT. The parent’s next router checks early_return and on_return and jumps directly- this allows you to avoid bubble nodes needed below the first level.

  2. Result-driven routing: subgraphs always terminate at END with a structured result (eg. subgraph_result: {“intent”: “RETURN_TO_ROOT”, “payload”: {…}}), and the parent inspects that to route. This keeps hops minimal and centralizes routing logic in the parent(s).

Hopefully this is helpful and please let me know if you have any additional questions