Summary
ag-ui-langgraph's multi-interrupt resume path is incompatible with LangGraph 1.x. When a thread has more than one pending interrupt (e.g. an AIMessage with several parallel tool calls that each call interrupt()), resuming via RunAgentInput.resume = [ResumeEntry, ...] raises before any interrupt() handler runs:
RuntimeError: When there are multiple pending interrupts, you must specify the interrupt id when resuming.
The single-interrupt path works; only the N>1 path is affected.
Root cause
LangGraphAgent._build_command_from_agui_resume (integrations/langgraph/python/ag_ui_langgraph/agent.py, ~L1181-1195) folds N>1 resume entries into:
Command(resume={"__agui_resume_map__": {interrupt_id: {"status": ..., "payload": ...}, ...}})
That value is passed straight to the graph (agent.py ~L626) with no unwrap. But LangGraph 1.x requires, when multiple interrupts are pending, that the resume dict be keyed by interrupt id (native keyed resume: Command(resume={interrupt_id: value})); a non-id key like "__agui_resume_map__" is rejected. So the documented behavior — README "Resuming via AG-UI standard resume[]" states that for multiple entries each interrupt() returns {"__agui_resume_map__": {...}} and the handler self-selects — can never be reached on LangGraph 1.x: the run errors before the handler executes.
Reproduce
- A LangGraph
create_react_agent (or any graph) whose turn fans out two tool calls, each calling interrupt().
- Resume with
RunAgentInput.resume = [ResumeEntry(interrupt_id=<id0>, status="resolved", payload=...), ResumeEntry(interrupt_id=<id1>, status="resolved", payload=...)].
- Result:
RuntimeError: When there are multiple pending interrupts, you must specify the interrupt id when resuming.
Confirmed on langgraph 1.0.7 with ag-ui-langgraph at commit d2049deb (its pyproject declares langgraph>=0.6.0,<2). I did not pin the exact langgraph version at which the id-keyed requirement was introduced, so I can't say precisely which point in the declared <2 range it starts failing — but it fails across langgraph 1.x. Note also test_interrupt_handling.py observes "Without a real LangGraph id we can't round-trip a resume answer", i.e. the N>1 resume round-trip isn't covered by the existing tests, which is likely why this went unnoticed.
Suggested fix
Have _build_command_from_agui_resume build the framework-native id-keyed resume for the N>1 case (and, uniformly, for the single case):
Command(resume={entry.interrupt_id: <payload-or-cancelled-sentinel> for entry in resume_entries})
so LangGraph routes each pending interrupt() its own value directly.
Behavior-change note: this changes the documented multi-interrupt handler contract — with the native-keyed form, each interrupt() receives its own payload directly, rather than the full __agui_resume_map__ that handlers currently self-select from. That is arguably a simplification (no handler-side map lookup), and it appears to be the only option compatible with LangGraph's id-keyed multi-interrupt resume — but it's a contract change worth a deliberate call + a README update.
Context
Found while adopting #1945's canonical RunFinished.outcome + RunAgentInput.resume[] protocol in a downstream LangGraph agent. We currently work around it by subclassing LangGraphAgent and overriding _build_command_from_agui_resume to emit the native id-keyed form; happy to open a PR against the base method if the behavior change above is acceptable.
Summary
ag-ui-langgraph's multi-interrupt resume path is incompatible with LangGraph 1.x. When a thread has more than one pending interrupt (e.g. anAIMessagewith several parallel tool calls that each callinterrupt()), resuming viaRunAgentInput.resume = [ResumeEntry, ...]raises before anyinterrupt()handler runs:The single-interrupt path works; only the N>1 path is affected.
Root cause
LangGraphAgent._build_command_from_agui_resume(integrations/langgraph/python/ag_ui_langgraph/agent.py, ~L1181-1195) folds N>1 resume entries into:That value is passed straight to the graph (
agent.py~L626) with no unwrap. But LangGraph 1.x requires, when multiple interrupts are pending, that the resume dict be keyed by interrupt id (native keyed resume:Command(resume={interrupt_id: value})); a non-id key like"__agui_resume_map__"is rejected. So the documented behavior — README "Resuming via AG-UI standardresume[]" states that for multiple entries eachinterrupt()returns{"__agui_resume_map__": {...}}and the handler self-selects — can never be reached on LangGraph 1.x: the run errors before the handler executes.Reproduce
create_react_agent(or any graph) whose turn fans out two tool calls, each callinginterrupt().RunAgentInput.resume = [ResumeEntry(interrupt_id=<id0>, status="resolved", payload=...), ResumeEntry(interrupt_id=<id1>, status="resolved", payload=...)].RuntimeError: When there are multiple pending interrupts, you must specify the interrupt id when resuming.Confirmed on langgraph 1.0.7 with
ag-ui-langgraphat commitd2049deb(itspyprojectdeclareslanggraph>=0.6.0,<2). I did not pin the exact langgraph version at which the id-keyed requirement was introduced, so I can't say precisely which point in the declared<2range it starts failing — but it fails across langgraph 1.x. Note alsotest_interrupt_handling.pyobserves "Without a real LangGraph id we can't round-trip a resume answer", i.e. the N>1 resume round-trip isn't covered by the existing tests, which is likely why this went unnoticed.Suggested fix
Have
_build_command_from_agui_resumebuild the framework-native id-keyed resume for the N>1 case (and, uniformly, for the single case):so LangGraph routes each pending
interrupt()its own value directly.Behavior-change note: this changes the documented multi-interrupt handler contract — with the native-keyed form, each
interrupt()receives its own payload directly, rather than the full__agui_resume_map__that handlers currently self-select from. That is arguably a simplification (no handler-side map lookup), and it appears to be the only option compatible with LangGraph's id-keyed multi-interrupt resume — but it's a contract change worth a deliberate call + a README update.Context
Found while adopting #1945's canonical
RunFinished.outcome+RunAgentInput.resume[]protocol in a downstream LangGraph agent. We currently work around it by subclassingLangGraphAgentand overriding_build_command_from_agui_resumeto emit the native id-keyed form; happy to open a PR against the base method if the behavior change above is acceptable.