Skip to content

[DX] GAP-08: Inner ReAct agent tool results are inaccessible in outer graph HITL nodes — pattern not covered in samples #1515

@AlexBizon

Description

@AlexBizon

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:

  1. An outer LangGraph graph with a HITL node
  2. An inner ReAct agent that calls tools
  3. How to surface inner agent tool results to the outer graph state for use in HITL task data
  4. The tool_results pattern 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions