Skip to content

Python: Preserve non-adjacent function call/result pairs during compaction - #7244

Closed
jstar0 wants to merge 1 commit into
microsoft:mainfrom
jstar0:fix/nonadjacent-tool-pair-compaction
Closed

Python: Preserve non-adjacent function call/result pairs during compaction#7244
jstar0 wants to merge 1 commit into
microsoft:mainfrom
jstar0:fix/nonadjacent-tool-pair-compaction

Conversation

@jstar0

@jstar0 jstar0 commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Motivation & Context

Compaction currently groups only contiguous message spans. When approval or
resume traffic separates an assistant function_call declaration from its tool
function_result, each half receives a different group id. Group-based
strategies can then retain the result while excluding the declaration, producing
provider-invalid model input.

This change keeps non-adjacent call/result pairs atomic without changing the
behavior of pending, unmatched, malformed-order, or ambiguous duplicate calls.

Description & Review Guide

  • What are the major changes? Link result spans to a unique earlier
    declaration span with the same call_id, share the declaration's group id,
    group index, and reasoning metadata, and rewind incremental annotation when a
    matching result is appended later. Regression coverage includes truncation,
    multiple calls, combined result messages, ambiguous ids, and incremental
    append state.
  • What is the impact of these changes? Existing group-based compaction
    strategies now preserve valid function call/result pairs even when unrelated
    messages separate them. Hosted and MCP tool grouping, public APIs, and
    individual compaction strategies are unchanged.
  • What do you want reviewers to focus on? The unique-call_id linking rules,
    especially the intentional fallback to existing grouping for ambiguous or
    malformed sequences, and the incremental re-annotation boundary.

Verification:

uv run pytest packages/core/tests/core/test_compaction.py packages/core/tests/core/test_harness_memory.py -q
uv run poe syntax
uv run poe test -A -m "not integration"
uv run pyright packages/core/agent_framework/_compaction.py packages/core/tests/core/test_compaction.py
uv run ty check packages/core/tests/core/test_compaction.py

Related Issue

Fixes #7212

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. If it is a breaking change, add the breaking change label (or add "[BREAKING]" to the title prefix, before or after any language prefix) - a workflow keeps the label and title prefix in sync automatically.

Copilot AI review requested due to automatic review settings July 21, 2026 19:27
@giles17 giles17 added the python Usage: [Issues, PRs], Target: Python label Jul 21, 2026

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.

Pull request overview

This PR fixes a compaction-grouping bug in the Python core where non-adjacent assistant function_call declarations and tool function_result messages could end up in different groups, allowing group-based strategies to orphan one half and produce provider-invalid model inputs. It extends grouping to pair spans by unique call_id (without changing existing adjacency behavior for ambiguous/invalid cases) and updates incremental annotation so late-appended results rewind to the matching declaration group.

Changes:

  • Add a post-pass in group_messages to link non-adjacent function result spans to a unique earlier declaration span with the same call_id, sharing group_id and reasoning metadata.
  • Update incremental annotation to rewind the re-annotation window to the unique declaration group when a matching result is appended later, and ensure group indices remain collision-free with linked/non-contiguous groups.
  • Add regression tests covering non-adjacent pairing, multi-result/combined-result scenarios, ambiguous duplicates, incremental append behavior, and truncation atomicity.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
python/packages/core/agent_framework/_compaction.py Implements call-id-based linking of non-adjacent function call/result spans and adjusts incremental re-annotation/group index handling to keep linked pairs atomic.
python/packages/core/tests/core/test_compaction.py Adds targeted regression tests validating non-adjacent pairing, ambiguity rules, incremental extension behavior, and truncation atomicity for tool call/result pairs.

@eavanvalkenburg

Copy link
Copy Markdown
Member

please make sure to use the defined PR template @jstar0

@jstar0

jstar0 commented Jul 26, 2026

Copy link
Copy Markdown
Contributor Author

Updated the PR body to use the repository template, including Motivation & Context, Description & Review Guide, Related Issue, verification, and the contribution checklist.

@eavanvalkenburg

Copy link
Copy Markdown
Member

@jstar0, thank you for the non-adjacent function call/result compaction fix. I incorporated your changes into the consolidated draft PR #7345 and preserved your commit attribution there. Closing this PR in favor of the consolidated work.

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]: compaction orphans function_call/result pairs when declaration and result are non-adjacent (group pairing is adjacency-only)

4 participants