Skip to content

Python: omit failed Foundry turns from conversation chat history - #7637

Open
Shikhar Goel (sgoel2be24-cyber) wants to merge 1 commit into
microsoft:mainfrom
sgoel2be24-cyber:fix/foundry-skip-failed-conversation-persist
Open

Python: omit failed Foundry turns from conversation chat history#7637
Shikhar Goel (sgoel2be24-cyber) wants to merge 1 commit into
microsoft:mainfrom
sgoel2be24-cyber:fix/foundry-skip-failed-conversation-persist

Conversation

@sgoel2be24-cyber

@sgoel2be24-cyber Shikhar Goel (sgoel2be24-cyber) commented Aug 13, 2026

Copy link
Copy Markdown

Motivation & Context

When a hosted Foundry agent is used with conversation_id, a failed turn still stored its input items on the agentserver response/conversation store. The next request on that conversation replayed them via get_history(). Azure OpenAI does not keep failed input on the conversation.

Fixes #7630

Description & Review Guide

  • Let ResponsesAgentServerHost resolve and validate its configured/default response store before wrapping it, preserving hosted and local persistence defaults plus the resilient-background guard.
  • For synchronous non-background requests, determine the terminal result before the provider's initial create so failed input items never enter conversation history.
  • Keep existing response updates atomic at the provider level instead of using delete-then-create.
  • Reverted the earlier “skip MAF session persist on failure” change; that was the wrong store.
  • Tests in python/packages/foundry_hosting/tests/test_responses.py cover the HTTP conversation repro, provider behavior, default persistent storage, and the resilience guard.

Please focus on: failed synchronous turns stay out of get_history(), successful turns still persist, non-conversation runs are unchanged, and the base host's storage behavior is preserved.

Related Issue

Fixes #7630

Contribution Checklist

  • The code builds clean without any errors or warnings
  • All unit tests pass, and I have added new tests where possible
  • The PR follows the Contribution Guidelines
  • This PR is linked to an issue and there is no other open PR for this issue (see Related Issue above).
  • This is not a breaking change.

Assistance: drafted with an AI coding agent and reviewed before opening.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@agent-framework-automation agent-framework-automation Bot added the python Usage: [Issues, PRs], Target: Python label Aug 13, 2026
@sgoel2be24-cyber

Copy link
Copy Markdown
Author

@microsoft-github-policy-service agree

@cecheta

Copy link
Copy Markdown
Member

Unfortunately this doesn't fix the issue, as the problem is that the messages get saved to the chat history, not the session.

@sgoel2be24-cyber

Copy link
Copy Markdown
Author

You're right — the first commit skipped the wrong store.

The agentserver response provider was persisting input items for failed turns, so the next request on the same conversation replayed them via chat history (get_history()), not the MAF session.

24ebe8b wraps the response store so failed turns persist without input items (including the in_progress → failed update path). A follow-up request on the same conversation no longer sees the bad function_call_output. Tests cover both the store wrapper and the HTTP conversation repro from the issue.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@sgoel2be24-cyber Shikhar Goel (sgoel2be24-cyber) changed the title Python: skip Foundry session persist after a failed conversation turn Python: omit failed Foundry turns from conversation chat history Aug 14, 2026
Comment thread python/packages/foundry_hosting/agent_framework_foundry_hosting/_responses.py Outdated
Comment thread python/packages/foundry_hosting/agent_framework_foundry_hosting/_responses.py Outdated
@sgoel2be24-cyber
Shikhar Goel (sgoel2be24-cyber) force-pushed the fix/foundry-skip-failed-conversation-persist branch from 9c43deb to 4b6b97e Compare August 22, 2026 11:59
@github-actions

Copy link
Copy Markdown
Contributor

Python Test Coverage

Python Test Coverage Report •
FileStmtsMissCoverMissing
packages/foundry_hosting/agent_framework_foundry_hosting
   _responses.py88212186%116–118, 234, 299–300, 314, 317–318, 329, 451, 465, 474, 551, 626, 641–644, 708, 758, 820, 832, 848–849, 854–857, 861–863, 868, 876, 879, 890, 935, 945, 956–958, 972–974, 977, 1011–1014, 1019, 1022, 1042–1043, 1139, 1210–1212, 1216, 1219–1222, 1226–1228, 1233–1237, 1241–1243, 1250–1251, 1254–1257, 1263, 1266–1272, 1282, 1288, 1292, 1316, 1325–1326, 1330, 1572, 1584, 2036–2037, 2041, 2086, 2088, 2090, 2092, 2096, 2104, 2107–2111, 2113, 2123, 2127, 2172, 2174, 2176–2179, 2187, 2189
TOTAL48125449690% 

Python Unit Test Overview

Tests Skipped Failures Errors Time
9718 36 💤 0 ❌ 0 🔥 2m 43s ⏱️


The agentserver orchestrator persists input items for every stored response,
including ``status=failed``. Conversation history then replays those items on
the next turn, which is the #7630 failure mode. Azure OpenAI does not keep

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we do not need to refer to issues in docstrings, especially on private methods

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed locally: the private provider docstring no longer refers to the issue number. I will include this with the design revision once the streaming ownership question is confirmed.


async def create_response(
self,
response: Any,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we be more specific here, I don't love a bunch of Any definitions, then we might as well not type at all...

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed locally: create_response and update_response now use ResponseObject, Iterable[OutputItem] | None, Iterable[str] | None, and PlatformContext | None; the failed-response helper is typed to ResponseObject as well. Strict package Pyright and all five test type-checkers pass. I will include this with the design revision once the streaming ownership question is confirmed.

Comment on lines +597 to +609
async for event in events:
buffered.append(event)
except Exception as ex:
handler_error = ex
failed = handler_error is not None or any(
_response_field(event, "type") == "response.failed" for event in buffered
)
if store and failed:
self._failed_sync_response_ids.add(response_id)
for event in buffered:
yield event
if handler_error is not None:
raise handler_error

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this concerns me, because we are first exhausting the stream and then checking and yielding, I would prefer if we can yield directly because this will slow down the whole response.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks—agreed that exhausting the handler stream before yielding is undesirable.

The constraint I ran into is that agentserver persists the input items during the initial response.created / in_progress write, while the terminal failure is only known later. ResponseProviderProtocol.update_response() cannot atomically clear those input references. Yielding directly with the current wrapper would therefore reintroduce the failed-conversation-history bug, and the earlier delete-then-create approach was removed because it was non-atomic.

Would you prefer this behavior to be fixed in azure-ai-agentserver-responses—for example, by atomically excluding or clearing input references for failed responses—or should the provider protocol expose an operation that this host can use?

I will address the docstring and concrete typing comments regardless.

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

Labels

python Usage: [Issues, PRs], Target: Python

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Python: [Bug]: Foundry Hosting - User messages added to conversation chat history for failed requests

5 participants