79305dce63
Implements SlidingWindowStrategy class that satisfies the ContextStrategy
protocol for the ACMS pipeline. The strategy limits token usage by keeping
only the most recent N messages or tokens in context, which is critical for
long-running agent sessions that would otherwise exceed LLM context limits.
Key features:
- Configurable window_size (int) and window_mode ('messages' | 'tokens')
- Messages mode: keeps the most recent window_size non-system fragments
- Tokens mode: keeps the most recent fragments within the token budget
- System prompt preservation: fragments with role='system' are always kept
- Registered in the plugin registry under key 'sliding_window'
- Input validation: window_size must be positive, window_mode must be valid
- Full BDD test coverage with 22 scenarios across all acceptance criteria
ISSUES CLOSED: #9995
252 lines
14 KiB
Gherkin
252 lines
14 KiB
Gherkin
@phase2 @acms @context_strategies @sliding_window
|
|
Feature: SlidingWindowStrategy with configurable window size
|
|
As a CleverAgents developer
|
|
I want a SlidingWindowStrategy that implements the ContextStrategy protocol
|
|
So that long-running agent sessions can limit token usage by keeping only the most recent messages
|
|
|
|
# ===========================================================================
|
|
# Protocol conformance
|
|
# ===========================================================================
|
|
|
|
@sliding_window_protocol
|
|
Scenario: SlidingWindowStrategy satisfies the ContextStrategy protocol
|
|
Given a SlidingWindowStrategy with window_size 10 and window_mode "messages"
|
|
Then the sw strategy should satisfy the ContextStrategy protocol
|
|
And the sw strategy name should be "sliding_window"
|
|
And the sw strategy should have a capabilities object
|
|
|
|
@sliding_window_protocol
|
|
Scenario: SlidingWindowStrategy explain returns a description
|
|
Given a SlidingWindowStrategy with window_size 5 and window_mode "messages"
|
|
Then the sw strategy explain should contain "sliding"
|
|
|
|
# ===========================================================================
|
|
# Messages mode — basic truncation
|
|
# ===========================================================================
|
|
|
|
@sliding_window_messages
|
|
Scenario: Messages mode keeps only the most recent N messages
|
|
Given a SlidingWindowStrategy with window_size 2 and window_mode "messages"
|
|
And the following sliding window fragments:
|
|
| uko_node | content | score | tokens | depth | role |
|
|
| project://app/a.py | message 1 | 0.5 | 10 | 3 | user |
|
|
| project://app/b.py | message 2 | 0.6 | 10 | 3 | user |
|
|
| project://app/c.py | message 3 | 0.7 | 10 | 3 | user |
|
|
And a sliding window budget with max_tokens 1000 and reserved_tokens 0
|
|
When I apply the SlidingWindowStrategy
|
|
Then 2 fragments should be returned by sliding window strategy
|
|
And the sliding window result should contain uko_node "project://app/b.py"
|
|
And the sliding window result should contain uko_node "project://app/c.py"
|
|
And the sliding window result should not contain uko_node "project://app/a.py"
|
|
|
|
@sliding_window_messages
|
|
Scenario: Messages mode with exact boundary keeps all messages
|
|
Given a SlidingWindowStrategy with window_size 3 and window_mode "messages"
|
|
And the following sliding window fragments:
|
|
| uko_node | content | score | tokens | depth | role |
|
|
| project://app/a.py | message 1 | 0.5 | 10 | 3 | user |
|
|
| project://app/b.py | message 2 | 0.6 | 10 | 3 | user |
|
|
| project://app/c.py | message 3 | 0.7 | 10 | 3 | user |
|
|
And a sliding window budget with max_tokens 1000 and reserved_tokens 0
|
|
When I apply the SlidingWindowStrategy
|
|
Then 3 fragments should be returned by sliding window strategy
|
|
|
|
@sliding_window_messages
|
|
Scenario: Messages mode with fewer messages than window size returns all
|
|
Given a SlidingWindowStrategy with window_size 10 and window_mode "messages"
|
|
And the following sliding window fragments:
|
|
| uko_node | content | score | tokens | depth | role |
|
|
| project://app/a.py | message 1 | 0.5 | 10 | 3 | user |
|
|
| project://app/b.py | message 2 | 0.6 | 10 | 3 | user |
|
|
And a sliding window budget with max_tokens 1000 and reserved_tokens 0
|
|
When I apply the SlidingWindowStrategy
|
|
Then 2 fragments should be returned by sliding window strategy
|
|
|
|
@sliding_window_messages
|
|
Scenario: Messages mode with empty context returns empty
|
|
Given a SlidingWindowStrategy with window_size 5 and window_mode "messages"
|
|
And an empty sliding window fragment list
|
|
And a sliding window budget with max_tokens 1000 and reserved_tokens 0
|
|
When I apply the SlidingWindowStrategy
|
|
Then 0 fragments should be returned by sliding window strategy
|
|
|
|
# ===========================================================================
|
|
# Tokens mode — basic truncation
|
|
# ===========================================================================
|
|
|
|
@sliding_window_tokens
|
|
Scenario: Tokens mode keeps most recent messages within token limit
|
|
Given a SlidingWindowStrategy with window_size 25 and window_mode "tokens"
|
|
And the following sliding window fragments:
|
|
| uko_node | content | score | tokens | depth | role |
|
|
| project://app/a.py | message 1 | 0.5 | 10 | 3 | user |
|
|
| project://app/b.py | message 2 | 0.6 | 10 | 3 | user |
|
|
| project://app/c.py | message 3 | 0.7 | 10 | 3 | user |
|
|
And a sliding window budget with max_tokens 1000 and reserved_tokens 0
|
|
When I apply the SlidingWindowStrategy
|
|
Then 2 fragments should be returned by sliding window strategy
|
|
And the sliding window result should contain uko_node "project://app/b.py"
|
|
And the sliding window result should contain uko_node "project://app/c.py"
|
|
And the sliding window result should not contain uko_node "project://app/a.py"
|
|
|
|
@sliding_window_tokens
|
|
Scenario: Tokens mode with exact boundary keeps all messages
|
|
Given a SlidingWindowStrategy with window_size 30 and window_mode "tokens"
|
|
And the following sliding window fragments:
|
|
| uko_node | content | score | tokens | depth | role |
|
|
| project://app/a.py | message 1 | 0.5 | 10 | 3 | user |
|
|
| project://app/b.py | message 2 | 0.6 | 10 | 3 | user |
|
|
| project://app/c.py | message 3 | 0.7 | 10 | 3 | user |
|
|
And a sliding window budget with max_tokens 1000 and reserved_tokens 0
|
|
When I apply the SlidingWindowStrategy
|
|
Then 3 fragments should be returned by sliding window strategy
|
|
|
|
@sliding_window_tokens
|
|
Scenario: Tokens mode with empty context returns empty
|
|
Given a SlidingWindowStrategy with window_size 100 and window_mode "tokens"
|
|
And an empty sliding window fragment list
|
|
And a sliding window budget with max_tokens 1000 and reserved_tokens 0
|
|
When I apply the SlidingWindowStrategy
|
|
Then 0 fragments should be returned by sliding window strategy
|
|
|
|
@sliding_window_tokens
|
|
Scenario: Tokens mode with single large message that fits returns it
|
|
Given a SlidingWindowStrategy with window_size 50 and window_mode "tokens"
|
|
And the following sliding window fragments:
|
|
| uko_node | content | score | tokens | depth | role |
|
|
| project://app/a.py | message 1 | 0.5 | 40 | 3 | user |
|
|
And a sliding window budget with max_tokens 1000 and reserved_tokens 0
|
|
When I apply the SlidingWindowStrategy
|
|
Then 1 fragments should be returned by sliding window strategy
|
|
|
|
# ===========================================================================
|
|
# System prompt preservation
|
|
# ===========================================================================
|
|
|
|
@sliding_window_system_prompt
|
|
Scenario: System prompt is always preserved outside the sliding window in messages mode
|
|
Given a SlidingWindowStrategy with window_size 2 and window_mode "messages"
|
|
And the following sliding window fragments:
|
|
| uko_node | content | score | tokens | depth | role |
|
|
| project://system/prompt | system context | 0.9 | 20 | 5 | system |
|
|
| project://app/a.py | message 1 | 0.5 | 10 | 3 | user |
|
|
| project://app/b.py | message 2 | 0.6 | 10 | 3 | user |
|
|
| project://app/c.py | message 3 | 0.7 | 10 | 3 | user |
|
|
And a sliding window budget with max_tokens 1000 and reserved_tokens 0
|
|
When I apply the SlidingWindowStrategy
|
|
Then 3 fragments should be returned by sliding window strategy
|
|
And the sliding window result should contain uko_node "project://system/prompt"
|
|
And the sliding window result should contain uko_node "project://app/b.py"
|
|
And the sliding window result should contain uko_node "project://app/c.py"
|
|
And the sliding window result should not contain uko_node "project://app/a.py"
|
|
|
|
@sliding_window_system_prompt
|
|
Scenario: System prompt is always preserved outside the sliding window in tokens mode
|
|
Given a SlidingWindowStrategy with window_size 25 and window_mode "tokens"
|
|
And the following sliding window fragments:
|
|
| uko_node | content | score | tokens | depth | role |
|
|
| project://system/prompt | system context | 0.9 | 20 | 5 | system |
|
|
| project://app/a.py | message 1 | 0.5 | 10 | 3 | user |
|
|
| project://app/b.py | message 2 | 0.6 | 10 | 3 | user |
|
|
| project://app/c.py | message 3 | 0.7 | 10 | 3 | user |
|
|
And a sliding window budget with max_tokens 1000 and reserved_tokens 0
|
|
When I apply the SlidingWindowStrategy
|
|
Then 3 fragments should be returned by sliding window strategy
|
|
And the sliding window result should contain uko_node "project://system/prompt"
|
|
And the sliding window result should contain uko_node "project://app/b.py"
|
|
And the sliding window result should contain uko_node "project://app/c.py"
|
|
And the sliding window result should not contain uko_node "project://app/a.py"
|
|
|
|
@sliding_window_system_prompt
|
|
Scenario: Multiple system prompts are all preserved
|
|
Given a SlidingWindowStrategy with window_size 1 and window_mode "messages"
|
|
And the following sliding window fragments:
|
|
| uko_node | content | score | tokens | depth | role |
|
|
| project://system/prompt1 | system context 1 | 0.9 | 20 | 5 | system |
|
|
| project://system/prompt2 | system context 2 | 0.8 | 20 | 5 | system |
|
|
| project://app/a.py | message 1 | 0.5 | 10 | 3 | user |
|
|
| project://app/b.py | message 2 | 0.6 | 10 | 3 | user |
|
|
And a sliding window budget with max_tokens 1000 and reserved_tokens 0
|
|
When I apply the SlidingWindowStrategy
|
|
Then 3 fragments should be returned by sliding window strategy
|
|
And the sliding window result should contain uko_node "project://system/prompt1"
|
|
And the sliding window result should contain uko_node "project://system/prompt2"
|
|
And the sliding window result should contain uko_node "project://app/b.py"
|
|
And the sliding window result should not contain uko_node "project://app/a.py"
|
|
|
|
@sliding_window_system_prompt
|
|
Scenario: Context with only system prompts returns all of them
|
|
Given a SlidingWindowStrategy with window_size 1 and window_mode "messages"
|
|
And the following sliding window fragments:
|
|
| uko_node | content | score | tokens | depth | role |
|
|
| project://system/prompt1 | system context 1 | 0.9 | 20 | 5 | system |
|
|
| project://system/prompt2 | system context 2 | 0.8 | 20 | 5 | system |
|
|
And a sliding window budget with max_tokens 1000 and reserved_tokens 0
|
|
When I apply the SlidingWindowStrategy
|
|
Then 2 fragments should be returned by sliding window strategy
|
|
|
|
# ===========================================================================
|
|
# Budget enforcement
|
|
# ===========================================================================
|
|
|
|
@sliding_window_budget
|
|
Scenario: Strategy respects token budget after sliding window truncation
|
|
Given a SlidingWindowStrategy with window_size 10 and window_mode "messages"
|
|
And the following sliding window fragments:
|
|
| uko_node | content | score | tokens | depth | role |
|
|
| project://app/a.py | message 1 | 0.5 | 100 | 3 | user |
|
|
| project://app/b.py | message 2 | 0.6 | 100 | 3 | user |
|
|
| project://app/c.py | message 3 | 0.7 | 100 | 3 | user |
|
|
And a sliding window budget with max_tokens 250 and reserved_tokens 0
|
|
When I apply the SlidingWindowStrategy
|
|
Then 2 fragments should be returned by sliding window strategy
|
|
|
|
# ===========================================================================
|
|
# can_handle
|
|
# ===========================================================================
|
|
|
|
@sliding_window_can_handle
|
|
Scenario: can_handle returns a positive confidence
|
|
Given a SlidingWindowStrategy with window_size 10 and window_mode "messages"
|
|
When I check can_handle on SlidingWindowStrategy
|
|
Then the sliding window confidence should be greater than 0.0
|
|
|
|
# ===========================================================================
|
|
# Plugin registry registration
|
|
# ===========================================================================
|
|
|
|
@sliding_window_registry
|
|
Scenario: SlidingWindowStrategy is registered under key "sliding_window" in the pipeline
|
|
Given an ACMS pipeline for sliding window tests
|
|
Then the sw pipeline should have strategy "sliding_window"
|
|
|
|
@sliding_window_registry
|
|
Scenario: SlidingWindowStrategy can be registered manually with the pipeline
|
|
Given an ACMS pipeline for sliding window tests
|
|
When I register a custom SlidingWindowStrategy with the pipeline
|
|
Then the sw pipeline should have strategy "custom_sliding_window"
|
|
|
|
# ===========================================================================
|
|
# Configuration validation
|
|
# ===========================================================================
|
|
|
|
@sliding_window_config
|
|
Scenario: window_size must be positive
|
|
When I attempt to create a SlidingWindowStrategy with window_size 0
|
|
Then a ValueError should be raised for sliding window
|
|
|
|
@sliding_window_config
|
|
Scenario: window_mode must be messages or tokens
|
|
When I attempt to create a SlidingWindowStrategy with window_mode "invalid"
|
|
Then a ValueError should be raised for sliding window
|
|
|
|
@sliding_window_config
|
|
Scenario: window_mode messages is valid
|
|
When I create a SlidingWindowStrategy with window_size 5 and window_mode "messages"
|
|
Then no error should be raised for sliding window
|
|
|
|
@sliding_window_config
|
|
Scenario: window_mode tokens is valid
|
|
When I create a SlidingWindowStrategy with window_size 100 and window_mode "tokens"
|
|
Then no error should be raised for sliding window
|