fix(langgraph): don't re-emit TOOL_CALL_START on HITL resume#2166
Open
NathanTarbert wants to merge 1 commit into
Open
fix(langgraph): don't re-emit TOOL_CALL_START on HITL resume#2166NathanTarbert wants to merge 1 commit into
NathanTarbert wants to merge 1 commit into
Conversation
OnToolEnd's synthetic TOOL_CALL_START/ARGS/END triple was gated only on the per-run emittedToolCallStartIds Set, which handleStreamEvents resets to a fresh Set on every run(). On a HITL resume (a second run() on the same thread) the Set is empty, so the guard passes and the tool call the frontend already received in the prior run is re-announced. Symptom: parent follow-up text streams in, then disappears when MESSAGES_SNAPSHOT arrives, and the tool call reverts to a loading state. Dedupe against the durable thread history in this.messages (the full conversation the client replays on resume) instead: if the originating assistant message for the tool call is already present, skip the synthetic triple in both the update.messages path and the single-message path. TOOL_CALL_RESULT still fires normally. Fixes #2014
Contributor
Python Preview PackagesVersion
Install with uvAdd the TestPyPI index to your [[tool.uv.index]]
name = "testpypi"
url = "https://test.pypi.org/simple/"
explicit = trueThen install the packages you need: # Core SDK
uv add 'ag-ui-protocol==0.0.0.dev1783807674' --index testpypi
# Integrations (each already depends on the matching ag-ui-protocol preview)
uv add 'ag-ui-langgraph==0.0.0.dev1783807674' --index testpypi
uv add 'ag-ui-crewai==0.0.0.dev1783807674' --index testpypi
# NOTE: ag-ui-agent-spec depends on pyagentspec (git-only, not on PyPI).
# You will need to install pyagentspec separately from its git repo.
uv add 'ag-ui-agent-spec==0.0.0.dev1783807674' --index testpypi
uv add 'ag_ui_adk==0.0.0.dev1783807674' --index testpypi
uv add 'ag_ui_strands==0.0.0.dev1783807674' --index testpypiInstall with pippip install \
--index-url https://test.pypi.org/simple/ \
--extra-index-url https://pypi.org/simple/ \
ag-ui-protocol==0.0.0.dev1783807674
Commit: 5030466 |
@ag-ui/a2a-middleware
@ag-ui/a2ui-middleware
@ag-ui/event-throttle-middleware
@ag-ui/mcp-apps-middleware
@ag-ui/mcp-middleware
@ag-ui/a2a
@ag-ui/adk
@ag-ui/ag2
@ag-ui/agno
@ag-ui/aws-strands
@ag-ui/claude-agent-sdk
@ag-ui/crewai
@ag-ui/langchain
@ag-ui/langgraph
@ag-ui/llamaindex
@ag-ui/mastra
@ag-ui/pydantic-ai
@ag-ui/vercel-ai-sdk
@ag-ui/watsonx
@ag-ui/a2ui-toolkit
create-ag-ui-app
@ag-ui/client
@ag-ui/core
@ag-ui/encoder
@ag-ui/proto
commit: |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #2014
Problem
When a run resumes after a HITL interrupt,
LangGraphAgent.handleStreamEventsresetsthis.emittedToolCallStartIdsto a freshSet().OnToolEnd's syntheticTOOL_CALL_START+TOOL_CALL_ARGS+TOOL_CALL_ENDtriple is gated only on that per-run Set, so on a resume (a secondrun()on the same thread) the Set is empty, the guard passes, and the tool call the frontend already received in the prior run is re-announced.Visible symptoms:
MESSAGES_SNAPSHOTarrives.Fix
Dedupe against the durable thread history already available in
this.messages(set byAbstractAgent.run()frominput.messages). On a HITL resume the client replays the full conversation, so the originating assistant message — carrying the tool call'sidin itstoolCalls— is present. A new same-run tool call is unaffected becauseOnChatModelStreamhas already added its id toemittedToolCallStartIds, so the existing gate fires first; the history lookup only matters in the resume gap.The guard is applied to both synthetic-emit paths in
OnToolEnd(theupdate.messagesCommand path and the single-message path).TOOL_CALL_RESULTstill fires normally in both.This mirrors the approach the reporter validated locally on
0.0.36. (A cleaner long-term option is reading the authoritative checkpoint viaclient.threads.getState, but thethis.messageslookup is sufficient for the full-history-replay / CopilotKit case.)Tests
Added
resume-tool-call-dedup.test.ts(3 cases):TOOL_CALL_START/ARGS/END,TOOL_CALL_RESULTstill fires;update.messagespath → same;START/ARGS/ENDstill emitted (regression guard).TDD-verified: the two resume cases fail against the unpatched
OnToolEndand pass with the fix. The tool-call-adjacent suites (predict-state-e2e,messages-tuple,interrupts) stay green (62 passing together).