Files
cleveragents-core/features/sliding_window_strategy.feature
HAL9000 79305dce63 feat(context): implement SlidingWindowStrategy with configurable window size
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
2026-06-06 11:27:30 -04:00

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