Python: preserve mixed-pause recovery invariants - #8518
RongJie G (CorgiBoyG) wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
🟡 Changes recommended
Cross-turn equal-result correlation can remain permanently pending, and malformed restored error counters can weaken the configured failure limit.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Adds reliable recovery for mixed approval/Host pause batches after provider invalidation.
Changes:
- Persists and replays provider outboxes and invocation budgets.
- Improves Host-result correlation and conflict handling.
- Adds specifications, guidance, and regression coverage.
File summaries
| File | Description |
|---|---|
python/packages/core/agent_framework/_tools.py |
Implements outbox recovery, budget restoration, and correlation logic. |
python/packages/core/tests/core/test_function_invocation_logic.py |
Adds recovery and correlation tests. |
python/packages/core/AGENTS.md |
Documents the outbox invariant. |
docs/specs/004-python-function-calling-loop.md |
Updates function-loop requirements. |
docs/decisions/0041-handle-provider-invalidated-responses.md |
Records the invalidation recovery decision. |
Review details
- Files reviewed: 5/5 changed files
- Comments generated: 2
- Review effort level: Balanced
💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.
| if len(replay_indexes) == 1: | ||
| item_index = replay_indexes[0] | ||
| observed_preexisting_indexes.add(item_index) | ||
| elif len(unanswered_indexes) == 1: | ||
| item_index = unanswered_indexes[0] |
There was a problem hiding this comment.
Confirmed with a regression test that stages the identified equal-payload result in one turn and supplies the id-less result in a later turn while retaining the full transcript. The initial fix of preferring the unanswered slot was still too broad because it could misassign a historical id-less replay. The matcher now treats a preexisting response as an id-less replay only when the stored response itself has no occurrence ID; otherwise the later id-less result fills the sole unanswered occurrence. The focused cross-turn and existing same-turn/replay cases pass.
| action = outbox.get("action") | ||
| if action not in {"continue", "stop"}: | ||
| raise RuntimeError("The pending mixed-batch provider outbox contains an invalid action.") | ||
| return _FunctionProcessingResult( | ||
| errors_in_a_row=int(outbox.get("errors_in_a_row", 0) or 0), |
There was a problem hiding this comment.
Confirmed with parameterized regression cases for a negative integer, bool, string, and float. Restored errors_in_a_row is now accepted only when it is a non-boolean, non-negative integer, and validation happens before replaying the outbox. A separate compatibility test verifies that an older outbox with the field missing still restores the documented default of zero.
cbb86a0 to
01b024f
Compare
|
Copilot review |
Motivation & Context
A mixed batch containing approval-required and Host-owned calls can finish local execution before the provider accepts the result-delivery request. If that request is invalidated, clearing the completed batch immediately loses the only replayable copy even though the approved local tool has already run.
PR #8449 bounded stateless pause-response ownership to user turns and rejected direct conflicting occurrence-identified Host results. This change builds on that merged work and closes the remaining #8436 gaps: stateful identified/id-less correlation, handling of extra Host replays, and recovery when provider result delivery is invalidated.
Description & Review Guide
What are the major changes?
call_idis reused.What is the impact of these changes?
call_idbatches no longer remain pending or forward contradictory/orphaned Host results.What do you want reviewers to focus on?
Related Issue
Fixes #8436
Built on #8449, which has already been merged.
Validation
Run from
python/:uv run pytest packages/core/tests -quv run pytest packages/ag-ui/tests -q(1367 passed, 14 skipped)uv run pytest packages/declarative/tests -quv run pytest packages/foundry_hosting/tests -quv run ruff check packages/core/agent_framework/_tools.py packages/core/tests/core/test_function_invocation_logic.pyuv run ruff format --check packages/core/agent_framework/_tools.py packages/core/tests/core/test_function_invocation_logic.pyuv run pyright packages/openai packages/core/agent_framework packages/core/tests/core/test_function_invocation_logic.py(0 errors, 0 warnings)git diff --checkContribution Checklist
breaking changelabel (or add "[BREAKING]" to the title prefix, before or after any language prefix) — a workflow keeps the label and title prefix in sync automatically.