Skip to content

Conversation

@github-actions
Copy link

@github-actions github-actions bot commented Dec 17, 2025

Problem

When agents perform many tool operations (e.g., web browsing with screenshots), message history grows rapidly and the agent loop slows down. Currently, ConversationManager.apply_management() only runs at the end of execution in the finally block, causing performance degradation during long-running agent loops.

Solution

Add per_turn parameter to SlidingWindowConversationManager that enables proactive message management during the agent loop by leveraging the hooks system.

Resolves #3

Public API Changes

SlidingWindowConversationManager

Before:

manager = SlidingWindowConversationManager(
    window_size=40,
    should_truncate_results=True
)

After:

manager = SlidingWindowConversationManager(
    window_size=40,
    should_truncate_results=True,
    per_turn=True  # or per_turn=5 for every 5 model calls
)
# Can be changed dynamically
manager.per_turn = 3

Parameter Options

  • per_turn=False (default): Current behavior - management only at end
  • per_turn=True: Apply management before every model call
  • per_turn=N (int): Apply management before every N model calls

HookProvider Protocol

Added @runtime_checkable to HookProvider protocol to enable proper isinstance checks.

Implementation Approach

  • SlidingWindowConversationManager implements HookProvider.register_hooks() and subscribes to BeforeModelCallEvent
  • Agent auto-registers conversation_manager as hook provider if it implements the protocol
  • Callback checks per_turn dynamically (not at registration) to support runtime changes
  • State persistence includes model_call_count for session restoration
  • Finally block still calls apply_management() for robustness

Impact

Benefits:

  • Prevents performance degradation in tool-heavy workflows
  • Reduces token costs for long-running agents
  • Enables fine-tuned control over context management frequency

Tradeoffs:

  • More aggressive management removes conversation history the agent uses for decision-making
  • Requires understanding agent's context needs to choose appropriate per_turn value

Backward Compatibility

✓ Default per_turn=False maintains current behavior
✓ No breaking changes
✓ Other conversation managers (Summarizing, Null) unaffected

Enable SlidingWindowConversationManager to apply message management during
the agent loop execution by implementing per_turn functionality.

Changes:
- Add per_turn parameter (bool | int, default: False) to control when
  message management is applied
- Implement register_hooks method to register AfterToolCallEvent callback
- Add _on_after_tool_call callback to apply management based on per_turn
- Update get_state and restore_from_session to persist _tool_call_count
- Auto-register conversation_manager as hook in Agent.__init__ if it
  implements register_hooks method
- Add comprehensive test suite with 25 test cases

Resolves #3
Args:
event: The after tool call event containing the agent and tool execution details.
"""
self._tool_call_count += 1
Copy link
Owner

Choose a reason for hiding this comment

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

Track model calls instead of tool calls, since you could have N tools execute per "turn"

Copy link
Author

Choose a reason for hiding this comment

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

Good point! Will change to track model calls using AfterModelCallEvent instead.

self.hooks.add_hook(self._session_manager)

# Check if conversation_manager can provide hooks
if hasattr(self.conversation_manager, "register_hooks"):
Copy link
Owner

Choose a reason for hiding this comment

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

No, check if it derives from HookProvider

Copy link
Author

Choose a reason for hiding this comment

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

Will update to use isinstance check with HookProvider.

Copy link
Owner

Choose a reason for hiding this comment

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

We have tests for the conversation manager (search for test_sliding_window_conversation_manager_with_tool_results_truncated) move these to be in ther

Copy link
Author

Choose a reason for hiding this comment

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

Will move tests to existing test_conversation_manager.py file.

Optional list of messages to prepend to the agent's messages.
"""
result = super().restore_from_session(state)
self._tool_call_count = state.get("_tool_call_count", 0)
Copy link
Owner

Choose a reason for hiding this comment

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

We don't have to use an underscore since we own the implementation

Copy link
Author

Choose a reason for hiding this comment

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

Will remove underscore prefix from model_call_count.

@zastrowm
Copy link
Owner

/strands

Changes based on PR review feedback:
- Track model calls instead of tool calls using AfterModelCallEvent
- Rename counter from _tool_call_count to model_call_count (remove underscore)
- Use hasattr/callable check instead of isinstance for HookProvider protocol
- Move tests from standalone file to existing test_conversation_manager.py

All 40 tests passing (15 existing + 25 new).
- True: Apply management after every model call
- int (e.g., 3): Apply management after every N model calls
When to use per_turn: If your agent performs many tool operations in loops
Copy link
Owner

Choose a reason for hiding this comment

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

Make this much more concise. Just talk about how this can reduce the context that the agent receives which can speed up agents and/or reduce costs, at the expense of removing context that the agent uses to guide the next step

Copy link
Author

Choose a reason for hiding this comment

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

Will simplify to focus on context reduction benefits vs tradeoffs.

Copy link
Owner

Choose a reason for hiding this comment

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

You didn't make this more concise

@zastrowm
Copy link
Owner

/strands

Changes:
- Mark HookProvider as @runtime_checkable and use isinstance check in Agent
- Change from AfterModelCallEvent to BeforeModelCallEvent for better timing
- Move per_turn check to callback for dynamic changes
- Rename model_call_count (removed underscore)
- Simplify per_turn docstring to focus on context reduction tradeoffs
- Convert test classes to concise function-style tests matching existing patterns
- Elevate all imports to top of file
- Remove implementation detail tests and focus on behavior
- Add test for dynamic per_turn changes
- Remove test_per_turn_updated.log file

All 24 tests passing (15 existing + 9 new concise per_turn tests)
@zastrowm
Copy link
Owner

/strands

assert mock.call_count >= 1


def test_per_turn_integer_manages_every_n_calls():
Copy link
Owner

Choose a reason for hiding this comment

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

Up above, we have a bunch of tests that use data-driven test cases.

Many of these tests overlap with that - Rewrite the tests that make sense as additional test-cases

@zastrowm
Copy link
Owner

/strands

Changes:
- Make per_turn docstring much more concise (2 sentences vs 5)
- Use BeforeModelCallEvent instead of AfterModelCallEvent for better timing
- Move per_turn check to callback (always register, check in callback for dynamic changes)
- Mark HookProvider as @runtime_checkable and use isinstance in Agent
- Convert tests to data-driven parametrized style matching existing patterns
- Consolidate 9 function tests into 2 parametrized tests + 2 focused tests
- Remove implementation detail tests (hook registration checks)
- Elevate imports to top of file (remove local imports)
- Remove test log file

All 27 tests passing (15 existing + 12 new per_turn tests)
@zastrowm
Copy link
Owner

/strands update the PR description according to the guidelines

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] Applying ConversationManager during the Agent Loop Execution (on specific Hook Events)

3 participants