-
Notifications
You must be signed in to change notification settings - Fork 23
Description
Summary
When a LangGraph agent uses a ReAct sub-agent (via create_react_agent) to invoke tools, the tool results and messages from that inner agent are not accessible in the outer graph's state. Reading state["messages"] in a HITL node (e.g. human_approval) always returns empty ToolMessage results. This pattern is not covered in the existing samples.
Context
This is a LangGraph architecture constraint (not a UiPath SDK bug), but the UiPath samples do not demonstrate the pattern, causing non-obvious failures for developers building agents with HITL + inner ReAct agents.
Root Cause
create_react_agent() creates its own isolated message graph. The tool call results (ToolMessages) are stored in the inner agent's message list, not written back to the outer InvoiceState.messages. When the human_approval node reads state["messages"] to populate the Action Center task form, it finds only the user's initial message — not the tool results.
# In run_agent() — inner agent runs tools, but results stay internal
inner_agent = create_react_agent(llm, tools=[extract_invoice_data, check_vendor_risk, ...])
result = inner_agent.invoke({"messages": [HumanMessage(content=prompt)]})
# result["messages"] contains ToolMessages with tool outputs
# BUT state["messages"] in the outer graph does NOT
# Later in human_approval — state["messages"] has no tool results
task = uipath.tasks.create(
data={
"InvoiceId": ???, # not in state["messages"]
"VendorRiskLevel": ???, # not in state["messages"]
}
)Workaround
Modify run_agent() to return both the decision and the tool results dict explicitly:
def run_agent(state: InvoiceState) -> dict:
result = inner_agent.invoke(...)
# Extract tool results from inner agent's messages
tool_results = {}
for msg in result["messages"]:
if isinstance(msg, ToolMessage):
tool_results.update(json.loads(msg.content))
return {
"decision": result["messages"][-1].content,
"tool_results": tool_results, # explicit field in outer state
}
# In human_approval — read from state["tool_results"]
invoice_data = state["tool_results"].get("invoice_data", {})Suggested Addition to Samples
Add a sample demonstrating:
- An outer LangGraph graph with a HITL node
- An inner ReAct agent that calls tools
- How to surface inner agent tool results to the outer graph state for use in HITL task data
- The
tool_resultspattern as an explicit state field
Impact
- Severity: Medium (documentation/samples gap)
- Every agent combining ReAct tool calling with HITL will hit this
- The silent failure (blank task form) takes significant debugging time to trace back to the inner/outer graph isolation