-
Notifications
You must be signed in to change notification settings - Fork 612
Description
Description
In my use case, I sometimes need to jump to a specific node using a Command with a goto.
I expected that calling goto would ignore the existing edges from the current node and continue execution only from the specified node onward.
Expected behavior:
__START__ -> A -> B -> C -> D -> __END__
// If node A returns Command({ goto: 'C' }), I expected:
__START__ -> A ---goto---> C -> D -> __END__ // B is skipped
However, what actually happens is that the goto does not cancel the existing transition.
Instead, it enqueues the target node in parallel while the normal edge (A -> B) still executes.
The result is a duplicated flow starting from the goto destination:
__START__ -> A -> B -> C -> D -> __END__
\
---goto---> C -> D -> __END__
It took quite a while to debug this because it looked like my nodes were being called multiple times before I realized this is how goto works with edges..
If this behavior is intentional, it would be very helpful to clarify this in the documentation. I initially assumed goto was a “jump” command rather than a “branch”.
DX Improvement Ideas
Provide a flag or configuration option to indicate that goto should cancel the current node’s outgoing edges before proceeding.
Minimal Reproducible Example
import { Annotation, Command } from "@langchain/langgraph";
const StateAnnotation = Annotation.Root({
foo: Annotation<string>({
reducer: (o, n) => o + "|" + n,
}),
});
const nodeA = async (state: typeof StateAnnotation.State) => {
console.log("Called A", state);
console.log("calling command to go nodeC");
return new Command({
update: { foo: "a" },
goto: "nodeC",
});
};
const nodeB = async (state: typeof StateAnnotation.State) => {
console.log("Called B", state);
return { foo: "b" };
};
const nodeC = async (state: typeof StateAnnotation.State) => {
console.log("Called C", state);
return { foo: "c" };
};
const nodeD = async (state: typeof StateAnnotation.State) => {
console.log("Called D", state);
return { foo: "d" };
};
import { StateGraph } from "@langchain/langgraph";
export const graph = new StateGraph(StateAnnotation)
.addNode("nodeA", nodeA, { ends: ["nodeC", "nodeA"] })
.addNode("nodeB", nodeB)
.addNode("nodeC", nodeC)
.addNode("nodeD", nodeD)
.addEdge("__start__", "nodeA")
.addEdge("nodeA", "nodeB")
.addEdge("nodeB", "nodeC")
.addEdge("nodeC", "nodeD")
.addEdge("nodeD", "__end__")
.compile();Output
Called A { foo: 'i' }
calling command to go nodeC
Called B { foo: 'i|a' }
Called C { foo: 'i|a' }
Called C { foo: 'i|a|b|c' }
Called D { foo: 'i|a|b|c' }
Called D { foo: 'i|a|b|c|c|d' }
Note that both C and D are executed twice.