Skip to content

[Bug]: aws-strands reports failed frontend tools as success on the session-manager path #2361

Description

@BenTaylorDev

Describe the Bug

Follow-up to #2306, split out of the review of #2317.

#2317 carries AG-UI's ToolMessage.error onto Bedrock's toolResult.status in _build_strands_history (integrations/aws-strands/python/src/ag_ui_strands/agent.py:213), so a client-reported frontend tool failure reaches the model as a failure. That fix is correct, but it only applies on one of the adapter's run paths.

_build_strands_history has exactly one caller, gated at agent.py:1040:

replay_history = (
    self.config.replay_history_into_strands and session_manager is None
)
reconcile_session_results = (
    session_manager is not None
    and self.config.replay_history_into_strands
    and has_nonvoid_frontend_result
)
if replay_history:
    native_history = _build_strands_history(input_data.messages)   # <- #2317's fix applies here
    ...
elif reconcile_session_results:                                     # <- and nowhere in here

When a session_manager_provider is configured, the model reads the persisted Strands history rather than a rebuilt one. Those toolResult blocks are created by the proxy tool with status hardcoded to "success" (client_proxy_tool.py:58-59):

def _proxy_func(tool_use: ToolUse, **_kwargs: Any) -> ToolResult:
    return {
        "toolUseId": tool_use["toolUseId"],
        "status": "success",
        "content": [{"text": PROXY_RESULT_PLACEHOLDER}],
    }

and reconciliation rewrites only the content, never the status (session_reconcile.py:138):

tool_result["content"] = [{"text": pending_results[tool_use_id]}]

There is also no channel to carry the flag through: pending_results is typed Mapping[str, str] (session_reconcile.py:56) — native toolUseId → result text, with no error signal.

Net effect: on the session-manager path, a failed frontend tool call is still asserted to the model as having succeeded — the same bug #2306 describes, on a path the fix does not reach. The legacy branch (stream_async(user_message), history tracked by the session manager) has the same shape.

Steps to Reproduce

  1. Configure a StrandsAgent with a session_manager_provider (so session_manager is not None), leaving replay_history_into_strands at its default True.
  2. Have the frontend execute a tool and return an AG-UI ToolMessage with error set.
  3. Inspect the toolResult block the model receives on the follow-up turn: content is the real result text, but status is "success".

The same run with no session_manager_provider takes the replay_history branch and correctly reports status: "error" after #2317.

Expected Behavior

A client-reported tool failure should reach the model as status: "error" regardless of whether a session_manager is configured. That likely means widening the reconcile channel to carry the flag alongside the text — e.g. pending_results becoming a mapping to (text, is_error) rather than str, with _correct_message writing tool_result["status"] when it rewrites tool_result["content"].

Why this is lower priority than it sounds

The default configuration is unaffected: replay_history_into_strands defaults to True (config.py:122) and session_manager defaults to None, so out-of-the-box deployments take the branch #2317 fixes. This gap is specific to deployments that configure persistence via session_manager_provider.

Environment

integrations/aws-strands/python, as of origin/main
Related: #2306, #2317, #2263

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