@acms @acms_core_pipeline Feature: ACMS Core Pipeline Components As a CleverAgents developer I want core ACMS pipeline components implemented So that the pipeline can select strategies, allocate budgets, score fragments, pack within constraints, and order for optimal LLM consumption # =========================================================================== # ActorPhaseStrategySelector # =========================================================================== @strategy_selector Scenario: Select strategies with no actor type or plan phase Given a set of core pipeline strategies When I select strategies with no actor_type or plan_phase Then all core strategies with positive confidence should be returned And the core strategies should be sorted by confidence descending @strategy_selector Scenario: Select strategies boosts preferred strategies for planner actor Given a set of core pipeline strategies When I select strategies with actor_type "planner" only Then the "relevance" strategy should have boosted confidence @strategy_selector Scenario: Select strategies boosts preferred strategies for executor actor Given a set of core pipeline strategies When I select strategies with actor_type "executor" only Then the "tiered" strategy should have boosted confidence @strategy_selector Scenario: Select strategies boosts preferred strategies for strategize phase Given a set of core pipeline strategies When I select strategies with plan_phase "strategize" only Then the "relevance" strategy should have boosted confidence @strategy_selector Scenario: Select strategies applies both actor and phase boosts Given a set of core pipeline strategies When I select strategies with actor_type "planner" and plan_phase "strategize" Then the "relevance" strategy should have maximum boosted confidence @strategy_selector Scenario: Select strategies excludes zero-confidence strategies Given a set of core pipeline strategies including a zero-confidence strategy When I select strategies with no actor_type or plan_phase Then the core zero-confidence strategy should not be in the results @strategy_selector Scenario: Select strategies with empty strategy list returns empty Given an empty strategy list When I select strategies with no actor_type or plan_phase Then 0 strategies should be selected @strategy_selector Scenario: Confidence is clamped to 1.0 after boost Given a strategy with confidence 0.9 When I select strategies with actor_type "planner" and plan_phase "strategize" Then the strategy confidence should not exceed 1.0 # =========================================================================== # SpecBudgetAllocator # =========================================================================== @budget_allocator Scenario: Allocate budget to empty candidates returns empty Given an empty candidate list When I allocate budget 1000 with SpecBudgetAllocator Then 0 allocations should be returned @budget_allocator Scenario: Allocate budget to single candidate gives full budget Given a single candidate with confidence 0.8 When I allocate budget 1000 with SpecBudgetAllocator Then 1 allocation should be returned And the single allocation should receive 1000 tokens @budget_allocator Scenario: Allocate budget proportionally to two candidates Given two candidates with equal confidence 0.5 When I allocate budget 1000 with SpecBudgetAllocator Then 2 allocations should be returned And the total allocated tokens should equal 1000 @budget_allocator Scenario: Allocate budget uses spec formula with quality scores Given two candidates with different quality scores When I allocate budget 1000 with SpecBudgetAllocator Then the higher quality candidate should receive more tokens @budget_allocator Scenario: Allocate budget with zero total weight falls back to equal split Given two candidates with zero confidence When I allocate budget 1000 with SpecBudgetAllocator Then 2 allocations should be returned And the total allocated tokens should equal 1000 @budget_allocator Scenario: Allocate budget with min_useful_budget excludes small candidates Given three candidates where one would receive very few tokens When I allocate budget 100 with SpecBudgetAllocator and min_useful_budget 30 Then the small candidate should be excluded from allocations @budget_allocator Scenario: Allocate budget total never exceeds budget Given three candidates with varying confidence When I allocate budget 500 with SpecBudgetAllocator Then the total allocated tokens should equal 500 # =========================================================================== # RelevanceRecencyPriorityScorer # =========================================================================== @scorer Scenario: Score empty fragment list returns empty Given an empty core pipeline fragment list When I score the fragments with RelevanceRecencyPriorityScorer Then 0 core scored fragments should be returned @scorer Scenario: Score single fragment preserves metadata Given a core pipeline fragment with relevance 0.8 When I score the fragments with RelevanceRecencyPriorityScorer Then the scored fragment should have _original_relevance metadata "0.8" @scorer Scenario: Score produces composite from relevance recency and priority Given a core pipeline fragment with relevance 0.8 and priority 0.9 When I score the fragments with RelevanceRecencyPriorityScorer Then the scored fragment composite score should be between 0.0 and 1.0 @scorer Scenario: Score is deterministic for identical inputs Given two identical core pipeline fragments When I score both fragment sets with RelevanceRecencyPriorityScorer Then both scored fragments should have identical composite scores @scorer Scenario: Score recency normalises across fragment timestamps Given two core pipeline fragments with different timestamps When I score the fragments with RelevanceRecencyPriorityScorer Then the newer fragment should have higher recency score @scorer Scenario: Score with same timestamps gives recency 1.0 Given two core pipeline fragments with identical timestamps When I score the fragments with RelevanceRecencyPriorityScorer Then all scored fragments should have recency score 1.0 @scorer Scenario: Score composite is clamped to 1.0 Given a core pipeline fragment with maximum relevance and priority When I score the fragments with RelevanceRecencyPriorityScorer Then the scored fragment composite score should be at most 1.0 @scorer Scenario: Score composite is clamped to 0.0 Given a core pipeline fragment with zero relevance and zero priority When I score the fragments with RelevanceRecencyPriorityScorer Then the scored fragment composite score should be at least 0.0 @scorer Scenario: Score with custom weights uses configured weights Given a core pipeline fragment with relevance 1.0 and priority 0.0 And scorer configured with relevance_weight 1.0 recency_weight 0.0 priority_weight 0.0 When I score the fragments with custom RelevanceRecencyPriorityScorer Then the scored fragment composite score should be 1.0 @scorer Scenario: Score extracts priority from metadata Given a core pipeline fragment with metadata priority "0.9" When I score the fragments with RelevanceRecencyPriorityScorer Then the scored fragment should have _score_priority metadata near "0.9" @scorer Scenario: Score defaults priority to 0.5 when not in metadata Given a core pipeline fragment with no priority metadata When I score the fragments with RelevanceRecencyPriorityScorer Then the scored fragment should have _score_priority metadata "0.5" # =========================================================================== # ConstrainedKnapsackPacker # =========================================================================== @packer Scenario: Pack empty fragment list returns empty Given an empty core pipeline fragment list And a core pipeline budget with max_tokens 1000 and reserved_tokens 0 When I pack the fragments with ConstrainedKnapsackPacker Then 0 core packed fragments should be returned @packer Scenario: Pack fragments within token budget Given the following core pipeline fragments: | uko_node | content | score | tokens | depth | | project://app/main.py | alpha | 0.9 | 100 | 3 | | project://app/io.py | beta | 0.7 | 100 | 3 | | project://app/util.py | gamma | 0.5 | 100 | 3 | And a core pipeline budget with max_tokens 250 and reserved_tokens 0 When I pack the fragments with ConstrainedKnapsackPacker Then 2 core packed fragments should be returned And the core packed total tokens should be at most 250 @packer Scenario: Pack respects max_file_size constraint Given the following core pipeline fragments: | uko_node | content | score | tokens | depth | | project://app/main.py | short content | 0.9 | 10 | 3 | | project://app/io.py | this is a much longer text | 0.7 | 20 | 3 | And a core pipeline budget with max_tokens 1000 and reserved_tokens 0 And a ConstrainedKnapsackPacker with max_file_size 15 When I pack the fragments with ConstrainedKnapsackPacker Then 1 core packed fragment should be returned And the core packed fragment uko_node should be "project://app/main.py" @packer Scenario: Pack respects max_total_size constraint Given the following core pipeline fragments: | uko_node | content | score | tokens | depth | | project://app/main.py | hello | 0.9 | 10 | 3 | | project://app/io.py | world | 0.7 | 10 | 3 | | project://app/util.py | extra | 0.5 | 10 | 3 | And a core pipeline budget with max_tokens 1000 and reserved_tokens 0 And a ConstrainedKnapsackPacker with max_total_size 4 When I pack the fragments with ConstrainedKnapsackPacker Then 0 core packed fragments should be returned @packer Scenario: Pack prefers higher scored fragments Given the following core pipeline fragments: | uko_node | content | score | tokens | depth | | project://app/main.py | alpha | 0.3 | 100 | 3 | | project://app/io.py | beta | 0.9 | 100 | 3 | And a core pipeline budget with max_tokens 150 and reserved_tokens 0 When I pack the fragments with ConstrainedKnapsackPacker Then 1 core packed fragment should be returned And the core packed fragment uko_node should be "project://app/io.py" @packer Scenario: Pack with context_view uses view constraints Given the following core pipeline fragments: | uko_node | content | score | tokens | depth | | project://app/main.py | hello | 0.9 | 10 | 3 | | project://app/io.py | world | 0.7 | 10 | 3 | And a core pipeline budget with max_tokens 1000 and reserved_tokens 0 And a ConstrainedKnapsackPacker with context_view max_total_size 4 When I pack the fragments with ConstrainedKnapsackPacker Then 0 core packed fragments should be returned @packer Scenario: Pack with budget=0 returns empty Given the following core pipeline fragments: | uko_node | content | score | tokens | depth | | project://app/main.py | hello | 0.9 | 10 | 3 | And a core pipeline budget with max_tokens 1 and reserved_tokens 0 When I pack the fragments with ConstrainedKnapsackPacker Then 0 core packed fragments should be returned # =========================================================================== # PriorityCoherenceOrderer # =========================================================================== @orderer Scenario: Order empty fragment list returns empty Given an empty core pipeline fragment list When I order the fragments with PriorityCoherenceOrderer Then 0 core ordered fragments should be returned @orderer Scenario: Order single fragment returns it unchanged Given a core pipeline fragment with relevance 0.8 When I order the fragments with PriorityCoherenceOrderer Then 1 core ordered fragment should be returned @orderer Scenario: Order preserves all fragments Given the following core pipeline fragments: | uko_node | content | score | tokens | depth | | project://app/src/main.py | alpha | 0.9 | 10 | 3 | | project://app/src/io.py | beta | 0.7 | 10 | 3 | | project://lib/util/util.py | gamma | 0.5 | 10 | 3 | When I order the fragments with PriorityCoherenceOrderer Then 3 core ordered fragments should be returned @orderer Scenario: Order places high-priority fragments first Given the following core pipeline fragments with priorities: | uko_node | content | score | tokens | depth | priority | | project://app/src/main.py | alpha | 0.5 | 10 | 3 | 0.9 | | project://app/src/io.py | beta | 0.9 | 10 | 3 | 0.1 | When I order the fragments with PriorityCoherenceOrderer Then the first core ordered fragment should have uko_node "project://app/src/main.py" @orderer Scenario: Order groups related fragments together Given the following core pipeline fragments: | uko_node | content | score | tokens | depth | | project://app/src/main.py | alpha | 0.9 | 10 | 3 | | project://lib/util/util.py | beta | 0.8 | 10 | 3 | | project://app/src/io.py | gamma | 0.7 | 10 | 3 | When I order the fragments with PriorityCoherenceOrderer Then fragments from "project://app" should be adjacent @orderer Scenario: Order with default priority 0.5 falls back to relevance Given the following core pipeline fragments: | uko_node | content | score | tokens | depth | | project://app/src/main.py | alpha | 0.3 | 10 | 3 | | project://lib/util/util.py | beta | 0.9 | 10 | 3 | When I order the fragments with PriorityCoherenceOrderer Then the first core ordered fragment should have uko_node "project://lib/util/util.py" # =========================================================================== # Pipeline Integration # =========================================================================== @pipeline_integration Scenario: Inject ActorPhaseStrategySelector into pipeline Given the ACMS pipeline with an ActorPhaseStrategySelector And the following core pipeline fragments: | uko_node | content | score | tokens | depth | | project://app/main.py | hello | 0.8 | 10 | 3 | When I assemble context through the core pipeline Then the core pipeline output should contain 1 fragment @pipeline_integration Scenario: Inject SpecBudgetAllocator into pipeline Given the ACMS pipeline with a SpecBudgetAllocator And the following core pipeline fragments: | uko_node | content | score | tokens | depth | | project://app/main.py | hello | 0.8 | 10 | 3 | When I assemble context through the core pipeline Then the core pipeline output should contain 1 fragment @pipeline_integration Scenario: Inject RelevanceRecencyPriorityScorer into pipeline Given the ACMS pipeline with a RelevanceRecencyPriorityScorer And the following core pipeline fragments: | uko_node | content | score | tokens | depth | | project://app/main.py | hello | 0.8 | 10 | 3 | When I assemble context through the core pipeline Then the core pipeline output fragments should have updated relevance scores @pipeline_integration Scenario: Inject ConstrainedKnapsackPacker into pipeline Given the ACMS pipeline with a ConstrainedKnapsackPacker And the following core pipeline fragments: | uko_node | content | score | tokens | depth | | project://app/main.py | alpha | 0.9 | 100 | 3 | | project://app/io.py | beta | 0.7 | 100 | 3 | | project://app/util.py | gamma | 0.5 | 100 | 3 | And a core pipeline budget with max_tokens 250 and reserved_tokens 0 When I assemble context through the core pipeline with budget Then the core pipeline output total tokens should be at most 250 @pipeline_integration Scenario: Inject PriorityCoherenceOrderer into pipeline Given the ACMS pipeline with a PriorityCoherenceOrderer And the following core pipeline fragments: | uko_node | content | score | tokens | depth | | project://app/main.py | hello | 0.8 | 10 | 3 | | project://lib/util.py | world | 0.6 | 15 | 5 | When I assemble context through the core pipeline Then the core pipeline output should contain 2 fragments