Skip to content

Python: [BREAKING] Fix harness before-strategy compaction under per-service-call persistence#7055

Open
westey-m wants to merge 3 commits into
microsoft:mainfrom
westey-m:python-harness-compaction-ordering
Open

Python: [BREAKING] Fix harness before-strategy compaction under per-service-call persistence#7055
westey-m wants to merge 3 commits into
microsoft:mainfrom
westey-m:python-harness-compaction-ordering

Conversation

@westey-m

Copy link
Copy Markdown
Contributor

Motivation & Context

create_harness_agent enables per-service-call history persistence
(require_per_service_call_history_persistence=True). With that setting the agent
skips HistoryProvider.before_run, so the before-phase compaction — which was wired
as a CompactionProvider.before_run hook — only ever saw an empty context and never
ran. As a result the harness had no working before_strategy (per-model-call)
compaction, so long histories were sent to the model uncompacted.

This PR makes before-strategy compaction actually run, on the full persisted history,
per model call.

Description & Review Guide

  • What are the major changes?

    • Wire the before-strategy as the agent's compaction_strategy chat option instead
      of a CompactionProvider.before_run hook. It now runs inside
      BaseChatClient.get_response — per model call, inner of
      PerServiceCallHistoryPersistingMiddleware (which has already loaded the full
      history into the outgoing messages) and outer of the leaf client.
      _assemble_compaction now returns a (before_strategy, after_provider) tuple; the
      after-strategy stays on a CompactionProvider (its after_run compacts persisted
      history in place). create_harness_agent passes the before-strategy and tokenizer
      to the Agent.
    • Default both compaction phases to the same shared ContextWindowCompactionStrategy
      when token params are provided (previously the after phase defaulted to
      ToolResultCompactionStrategy). Caller-supplied before_compaction_strategy /
      after_compaction_strategy still win per phase.
    • Tests: split-phase defaults, an integration regression proving the before-strategy
      truncates the loaded history under per-service-call persistence, a multi-turn
      coherence check, and a test asserting the exact chat-client middleware execution
      order: Function Invocation Loop → MessageInjection → PerServiceCall → Compaction →
      Leaf ChatClient.
  • What is the impact of these changes?

    • The harness before-strategy now compacts the full persisted history on every model
      call, keeping requests within the context budget inside the function-calling loop.
    • Breaking (experimental harness): the default after-run compaction changes from
      tool-result eviction (ToolResultCompactionStrategy) to full context-window
      compaction (ContextWindowCompactionStrategy). Callers who relied on the previous
      default can pass an explicit after_compaction_strategy.
  • What do you want reviewers to focus on?

    • The compaction wiring in create_harness_agent / _assemble_compaction and the
      guaranteed middleware ordering.

Related Issue

Closes #7011

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 10, 2026 15:27
@westey-m westey-m marked this pull request as ready for review July 10, 2026 15:28
@giles17 giles17 added python Usage: [Issues, PRs], Target: Python breaking change Usage: [PRs], Target: all PRs that introduce changes that are not backward compatible labels Jul 10, 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 long-standing harness bug where “before-strategy” (per-model-call) compaction never executed under require_per_service_call_history_persistence=True, causing long persisted histories to be sent to the model without token-budget compaction. It does so by moving the before-phase compaction into the per-model-call chat pipeline (via the agent’s compaction_strategy option) while keeping after-phase compaction as a post-turn CompactionProvider.

Changes:

  • Refactors harness compaction assembly to return (before_strategy, after_provider) and wires the before-strategy through Agent(compaction_strategy=...) so it runs inside BaseChatClient.get_response on the fully loaded per-call history.
  • Changes the default after-phase strategy (when token params are provided) to ContextWindowCompactionStrategy (matching the before-phase), instead of ToolResultCompactionStrategy.
  • Adds regression/integration tests proving before-compaction truncates loaded history under per-service-call persistence, preserves multi-turn coherence, and enforces an expected chat-client middleware execution order.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
python/packages/core/agent_framework/_harness/_agent.py Rewires harness compaction so before-phase runs per model call via agent compaction_strategy, and aligns default before/after strategies under token params.
python/packages/core/tests/core/test_harness_agent.py Adds tests covering split-phase defaults, regression for per-service-call persistence, multi-turn coherence, and middleware execution order.

Comment thread python/packages/core/agent_framework/_harness/_agent.py Outdated
@github-actions

github-actions Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Python Test Coverage

Python Test Coverage Report •
FileStmtsMissCoverMissing
packages/core/agent_framework/_harness
   _agent.py116496%203, 572–573, 575
TOTAL44135526488% 

Python Unit Test Overview

Tests Skipped Failures Errors Time
8846 33 💤 0 ❌ 0 🔥 2m 10s ⏱️

@github-actions github-actions Bot 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.

Automated Code Review

Reviewers: 5 | Confidence: 90%

✓ Correctness

The PR correctly separates before-strategy compaction from the CompactionProvider and wires it as the agent's compaction_strategy option, which runs inside BaseChatClient.get_response per model call. The tokenizer flows correctly through the system: ContextWindowCompactionStrategy stores its own tokenizer internally (with CharacterEstimatorTokenizer default), so even when the external tokenizer is None, compaction works properly via TokenBudgetComposedStrategy's annotate_token_counts call. The shared default_strategy instance is safe to reuse across both phases since it is stateless (no mutable instance state across calls). The or operator usage for resolving strategies is equivalent to is None checks for CompactionStrategy objects which are always truthy. All tests verify the correct behavior.

✓ Security Reliability

The PR correctly rewires before-strategy compaction from a CompactionProvider.before_run hook (which was a no-op under per-service-call persistence) to the agent's compaction_strategy option that runs per model call inside BaseChatClient.get_response. The shared ContextWindowCompactionStrategy instance is functionally stateless between calls (its internal TokenBudgetComposedStrategy instances annotate fresh each invocation), so reuse across both phases is safe. No security vulnerabilities, resource leaks, injection risks, or unhandled failure modes found. Input validation for token params is performed upstream in create_harness_agent before _assemble_compaction is called.

✓ Test Coverage

The test coverage for this PR is thorough and well-structured. The PR adds four substantial new tests covering: split-phase wiring, regression for the core bug (#7011), multi-turn history coherence, and middleware execution order. Existing tests were appropriately updated to reflect the new agent.compaction_strategy attribute. The only minor gap is that the pre-existing test_create_harness_agent_custom_after_strategy_enables_compaction_without_tokens test was not updated to also assert agent.compaction_strategy is None, which would symmetrically verify that providing only an after-strategy doesn't accidentally wire anything as the agent-level compaction_strategy.

✓ Failure Modes

No operational failure modes found. The refactoring correctly separates before-strategy (per model call via agent compaction_strategy) from after-strategy (post-turn via CompactionProvider). The shared ContextWindowCompactionStrategy instance is stateless, the token annotation is self-contained within TokenBudgetComposedStrategy, and exclusion flag leakage between phases is intentional and safe under skip_excluded=False semantics.

✓ Design Approach

The new before-compaction placement is correct for local-history mode, but it still does not solve the stated harness bug for clients/runs that keep history server-side. In that path the per-service middleware never reloads provider history into messages, so wiring before_compaction as the agent-level compaction_strategy still leaves historical turns uncompacted.


Automated review by westey-m's agents

Comment thread python/packages/core/agent_framework/_harness/_agent.py
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking change Usage: [PRs], Target: all PRs that introduce changes that are not backward compatible python Usage: [Issues, PRs], Target: Python

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Python: [Feature]: Add before_strategy compaction support in create_harness_agent

4 participants