Skip to content

fix(agent): enforce tool-result adjacency after compression - #36157

Closed
konsisumer wants to merge 1 commit into
NousResearch:mainfrom
konsisumer:fix/context-compressor-tool-ordering
Closed

fix(agent): enforce tool-result adjacency after compression#36157
konsisumer wants to merge 1 commit into
NousResearch:mainfrom
konsisumer:fix/context-compressor-tool-ordering

Conversation

@konsisumer

Copy link
Copy Markdown
Contributor

What does this PR do?

Fixes a crash-level context compression correctness bug where a role: "tool" message could survive compaction even when it was no longer directly adjacent to its originating assistant tool_calls message. That invalid ordering triggers provider-side HTTP 400 errors and can force session resets.

Related Issue

Fixes #20883

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (non-breaking change that adds functionality)
  • 🔒 Security fix
  • 📝 Documentation update
  • ✅ Tests (adding or improving test coverage)
  • ♻️ Refactor (no behavior change)
  • 🎯 New skill (bundled or hub)

Changes Made

  • agent/context_compressor.py: hardened _sanitize_tool_pairs() with a strict ordering pass that only keeps tool results when they are in the immediate assistant tool-results block and match pending tool_call_ids; misordered tool results are dropped with logging.
  • tests/agent/test_context_compressor.py: added regression test test_sanitizer_drops_tool_results_not_adjacent_to_calling_assistant covering the boundary/order violation case.

How to Test

  1. Run the focused regression tests:
    • pytest tests/agent/test_context_compressor.py -q -k "sanitizer_drops_tool_results_not_adjacent_to_calling_assistant or sanitizer_matches_responses_call_id_when_id_differs" --timeout=60
  2. Validate lint on touched files:
    • ruff check agent/context_compressor.py tests/agent/test_context_compressor.py
  3. Optional broader verification in a full dev environment:
    • pytest tests/agent/test_context_compressor.py -q -x --timeout=60

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):, feat(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've run pytest tests/ -q and all tests pass
  • I've added tests for my changes (required for bug fixes, strongly encouraged for features)
  • I've tested on my platform: macOS (darwin-arm64)

Documentation & Housekeeping

  • I've updated relevant documentation (README, docs/, docstrings) — or N/A
  • I've updated cli-config.yaml.example if I added/changed config keys — or N/A
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — or N/A
  • I've considered cross-platform impact (Windows, macOS) per the compatibility guide — or N/A
  • I've updated tool descriptions/schemas if I changed tool behavior — or N/A

What platforms tested on

  • macOS on darwin-arm64 (local)

For New Skills

Not applicable.

Screenshots / Logs

  • pytest tests/agent/test_context_compressor.py -q -k "sanitizer_drops_tool_results_not_adjacent_to_calling_assistant or sanitizer_matches_responses_call_id_when_id_differs" --timeout=602 passed, 90 deselected
  • ruff check agent/context_compressor.py tests/agent/test_context_compressor.pyAll checks passed!
  • python scripts/check-windows-footguns.py ran without finding blocking patterns in changed files.

@daimon-nous daimon-nous Bot added type/bug Something isn't working P1 High — major feature broken, no workaround comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint labels Jun 1, 2026
@daimon-nous

daimon-nous Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Related to open PR #24374, which also addresses #20883 by enforcing tool-call adjacency but in the API sanitizer (_sanitize_api_messages()) rather than the compressor's _sanitize_tool_pairs(). Maintainers may want to reconcile scope to avoid double-coverage.

@tonydwb tonydwb left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review Summary

Verdict: Approved

Review

fix(agent): enforce tool-result adjacency after compression

Important correctness fix for context compression. Key observations:

  • Problem: Context compression could leave orphaned role: "tool" messages that were no longer adjacent to their originating tool_calls assistant message. This invalid ordering triggers provider-side HTTP 400 errors.
  • Fix: Hardened _sanitize_tool_pairs() with a strict ordering pass — only keeps tool results that are in the immediate assistant tool-results block and match pending tool_call_ids. Misordered tool results are dropped with logging.
  • Scope: 2 files, well-focused.
  • Tests: Regression test covering the boundary/order violation case.

Looks Good

  • Addresses a crash-level bug (#20883)
  • Conservative fix (drops orphaned tool results with logging)
  • Well-tested

Reviewed by Hermes Agent

@konsisumer

Copy link
Copy Markdown
Contributor Author

Thanks @daimon-nous for flagging this. I looked up #24374 and you're right — it's open and addresses #20883 as well, but at a different layer:

  • fix: enforce tool-call adjacency in API sanitizer #24374 (_sanitize_api_messages() in the API sanitizer): catches invalid tool-result ordering as a last-resort guard before each API call.
  • This PR (_sanitize_tool_pairs() in the context compressor): prevents the invalid ordering from being produced during compression in the first place.

These two layers are complementary (defense-in-depth), but I recognize that maintainers may prefer a single canonical fix location. A few options:

  1. Keep both — compressor prevents creation, API sanitizer catches any stragglers. Redundant but belt-and-suspenders.
  2. Prefer this PR — fix at the source (compression) is semantically cleaner; fix: enforce tool-call adjacency in API sanitizer #24374 could then be narrowed or closed.
  3. Prefer fix: enforce tool-call adjacency in API sanitizer #24374 — API-sanitizer fix is the right chokepoint; close this PR in favor of that one.

This PR is currently labeled P1 while #24374 is P2. If maintainers want to close this in favor of #24374 or vice versa, please advise and I will act accordingly.

@teknium1 teknium1 added sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-caching Sweeper risk: may break/degrade prompt caching or cache-key stability (invariant) sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jun 21, 2026
@konsisumer

Copy link
Copy Markdown
Contributor Author

Closing: referenced_issue_closed. all referenced issues closed: [20883]

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

Labels

comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint P1 High — major feature broken, no workaround sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-caching Sweeper risk: may break/degrade prompt caching or cache-key stability (invariant) sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: Context compression produces invalid tool_call/tool message ordering → HTTP 400 crash

3 participants