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
- Configure a
StrandsAgent with a session_manager_provider (so session_manager is not None), leaving replay_history_into_strands at its default True.
- Have the frontend execute a tool and return an AG-UI
ToolMessage with error set.
- 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
Describe the Bug
Follow-up to #2306, split out of the review of #2317.
#2317 carries AG-UI's
ToolMessage.erroronto Bedrock'stoolResult.statusin_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_historyhas exactly one caller, gated atagent.py:1040:When a
session_manager_provideris configured, the model reads the persisted Strands history rather than a rebuilt one. ThosetoolResultblocks are created by the proxy tool withstatushardcoded to"success"(client_proxy_tool.py:58-59):and reconciliation rewrites only the content, never the status (
session_reconcile.py:138):There is also no channel to carry the flag through:
pending_resultsis typedMapping[str, str](session_reconcile.py:56) — nativetoolUseId→ 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
StrandsAgentwith asession_manager_provider(sosession_manager is not None), leavingreplay_history_into_strandsat its defaultTrue.ToolMessagewitherrorset.toolResultblock the model receives on the follow-up turn:contentis the real result text, butstatusis"success".The same run with no
session_manager_providertakes thereplay_historybranch and correctly reportsstatus: "error"after #2317.Expected Behavior
A client-reported tool failure should reach the model as
status: "error"regardless of whether asession_manageris configured. That likely means widening the reconcile channel to carry the flag alongside the text — e.g.pending_resultsbecoming a mapping to(text, is_error)rather thanstr, with_correct_messagewritingtool_result["status"]when it rewritestool_result["content"].Why this is lower priority than it sounds
The default configuration is unaffected:
replay_history_into_strandsdefaults toTrue(config.py:122) andsession_managerdefaults toNone, so out-of-the-box deployments take the branch #2317 fixes. This gap is specific to deployments that configure persistence viasession_manager_provider.Environment