Skip to content

fix(aws-strands): forward all parallel frontend tool results in continuation runs - #2218

Open
FriedhelmWS wants to merge 2 commits into
ag-ui-protocol:mainfrom
FriedhelmWS:tool
Open

fix(aws-strands): forward all parallel frontend tool results in continuation runs#2218
FriedhelmWS wants to merge 2 commits into
ag-ui-protocol:mainfrom
FriedhelmWS:tool

Conversation

@FriedhelmWS

Copy link
Copy Markdown

Problem

When a single assistant turn issues multiple parallel frontend tool calls, the client collects all results and sends them back in one continuation run (multiple role=tool messages). The user_message construction — which tells the model what the frontend tools returned — scans input_data.messages in reverse but breaks after the first role=tool message found. Only one result reaches the model; the rest are silently dropped.

Symptom: the model acknowledges one tool result and states it is "still waiting" for the others, even though all results were delivered in the request payload.

Root Cause

for msg in reversed(input_data.messages):
    if msg.role == "tool" ...:
        user_message = f"{tool_name} returned: {result_text}"
        ...
        break  # ← stops after ONE result

Fix

Replace the break-after-first with a collect-all loop that gathers every consecutive trailing role=tool message, then joins them (newline-separated, original order preserved):

_result_parts = []
for msg in reversed(input_data.messages):
    if msg.role == "tool" ...:
        _result_parts.append(f"{tool_name} returned: {result_text}")
    else:
        break
user_message = "\n".join(reversed(_result_parts))

Scanning still stops at the first non-tool message to avoid reaching into earlier conversation history.

Behavior

Scenario Before After
Single tool result (common case) One result forwarded Identical — one collected part, join is a no-op
N parallel tool results Only the last (reversed-first) forwarded All N forwarded, original order
Unresolvable tool name Terminates scan, drops remaining Skipped with warning, scan continues
Empty result text Synthetic "executed successfully" message Same fallback, per result

@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Python Preview Packages

Version 0.0.0.dev1785969516 published to TestPyPI.

Warning: These packages are built from contributor code that may not yet have been vetted for correctness or security. Install at your own risk and do not use in production.

Install with uv

Add the TestPyPI index to your pyproject.toml:

[[tool.uv.index]]
name = "testpypi"
url = "https://test.pypi.org/simple/"
explicit = true

Then install the packages you need:

# Core SDK
uv add 'ag-ui-protocol==0.0.0.dev1785969516' --index testpypi

# Integrations (each already depends on the matching ag-ui-protocol preview)
uv add 'ag-ui-langgraph==0.0.0.dev1785969516' --index testpypi
uv add 'ag-ui-crewai==0.0.0.dev1785969516' --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.dev1785969516' --index testpypi
uv add 'ag_ui_adk==0.0.0.dev1785969516' --index testpypi
uv add 'ag_ui_strands==0.0.0.dev1785969516' --index testpypi

Install with pip

pip install \
  --index-url https://test.pypi.org/simple/ \
  --extra-index-url https://pypi.org/simple/ \
  ag-ui-protocol==0.0.0.dev1785969516

Use --extra-index-url https://pypi.org/simple/ so pip can resolve
transitive dependencies (pydantic, fastapi, etc.) from real PyPI.


Commit: 9ba84c9

@pkg-pr-new

pkg-pr-new Bot commented Aug 5, 2026

Copy link
Copy Markdown

Open in StackBlitz

@ag-ui/a2a-middleware

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/a2a-middleware@2218

@ag-ui/a2ui-middleware

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/a2ui-middleware@2218

@ag-ui/event-throttle-middleware

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/event-throttle-middleware@2218

@ag-ui/mcp-apps-middleware

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/mcp-apps-middleware@2218

@ag-ui/mcp-middleware

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/mcp-middleware@2218

@ag-ui/a2a

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/a2a@2218

@ag-ui/adk

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/adk@2218

@ag-ui/ag2

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/ag2@2218

@ag-ui/agno

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/agno@2218

@ag-ui/aws-strands

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/aws-strands@2218

@ag-ui/claude-agent-sdk

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/claude-agent-sdk@2218

@ag-ui/claude-managed-agents

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/claude-managed-agents@2218

@ag-ui/crewai

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/crewai@2218

@ag-ui/langchain

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/langchain@2218

@ag-ui/langgraph

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/langgraph@2218

@ag-ui/llamaindex

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/llamaindex@2218

@ag-ui/mastra

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/mastra@2218

@ag-ui/pydantic-ai

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/pydantic-ai@2218

@ag-ui/vercel-ai-sdk

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/vercel-ai-sdk@2218

@ag-ui/watsonx

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/watsonx@2218

@ag-ui/a2ui-toolkit

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/a2ui-toolkit@2218

create-ag-ui-app

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/create-ag-ui-app@2218

@ag-ui/client

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/client@2218

@ag-ui/core

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/core@2218

@ag-ui/encoder

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/encoder@2218

@ag-ui/proto

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/proto@2218

commit: 4f80954

@contextablemark

Copy link
Copy Markdown
Contributor

Thanks for this, and apologies for the delay. The diagnosis is right, the fix is right, and it collides with nothing. Of the changes currently in flight on this file it's the cleanest, and I'd like it to be the first to land. One thing blocks it.

Confirmed

Verified against current main: merges with zero conflicts, and also zero against the halt-path branch we have in flight (mark/strands-py-halt-stops-loop). Your region — the inbound continuation prompt around agent.py:761-794 — is genuinely disjoint from that work, from #2172's _build_strands_history changes, and from #2286's resume branch.

Blocker: an existing test asserts the old behaviour

tests/test_session_manager.py:767, in test_partially_resolvable_turn_falls_back_to_legacy:

assert instance.stream_prompts == ["approve returned: R2"]

That test sends two frontend results (R1 and R2) and asserts only the second reaches the model — the exact truncation you're removing. After your change the prompt becomes "approve returned: R1\napprove returned: R2", so this test fails and the PR can't land as-is.

Worth being careful rather than just re-baselining it: the fallback behaviour that test guards is still correct and deliberate; when not every non-void result resolves to a native id, the adapter deliberately streams a synthetic user message instead of None. Only the "one-result" part was wrong. So the change is to the expected string alone:

assert instance.stream_prompts == ["approve returned: R1\napprove returned: R2"]

leaving the rest of the test intact.

Two requests

Please keep the rationale comments. The diff removes about fifteen lines that record why the code forwards the real result rather than a hardcoded success string — "silently breaking HITL — the model was told the tool executed successfully with no return value regardless of what the human actually returned" — and why an unresolved tool name deliberately left the message empty. Your behavior preserves both, but losing the reasoning is how that HITL bug could get reintroduced by the next person. It's fine if they get shortened, but I'd rather they not be dropped.

And one test. You're touching test_session_manager.py anyway to unblock the merge, so a case sending two resolvable frontend results and asserting both appear is maybe ten lines and would stop this regressing. If someone were to mistakenly add a break back inside the if, it would regress, which is just the sort of thing that the test would catch.

Again, sorry for the delay, but this particular file has a lot of activity on it at the moment, so I've just been trying to make sure that everything lands in the right order. At the moment, three other changes are converging on this file: #2172 (@YiyuanMiao) on the parallel-tool-call halt path, #2286 (@ciolo) adding the interrupt round-trip, and a halt-path fix of ours we've held back from opening because two of its hunks overlap #2172. Yours is the only one that overlaps nobody, which is why I'd like it to go first.

Fix the assertion and add a test and I'll approve or I'm happy to push both to your branch if that's easier than a round trip.

@FriedhelmWS

Copy link
Copy Markdown
Author

Thanks @contextablemark, comments addressed, could you please review, cheers!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants