Files
cleveragents-core/features/acms_pipeline_phase3.feature
T
aditya 137d040c4d
CI / benchmark-publish (pull_request) Has been skipped
CI / build (pull_request) Successful in 18s
CI / helm (pull_request) Successful in 22s
CI / quality (pull_request) Successful in 56s
CI / lint (pull_request) Successful in 3m21s
CI / typecheck (pull_request) Successful in 4m0s
CI / security (pull_request) Successful in 4m19s
CI / unit_tests (pull_request) Successful in 9m32s
CI / docker (pull_request) Successful in 1m22s
CI / coverage (pull_request) Successful in 12m27s
CI / e2e_tests (pull_request) Successful in 20m3s
CI / integration_tests (pull_request) Successful in 21m20s
CI / status-check (pull_request) Successful in 1s
CI / benchmark-regression (pull_request) Successful in 54m56s
feat(acms): implement DepthReductionCompressor for skeleton compression
Add a production skeleton compressor that re-renders inherited fragments to overview depths via the UKO detail-level map chain, fits the result within the configured skeleton budget, and wires the pipeline default to the new compressor.

Address prior review feedback by extracting the render visitors into a dedicated module, restoring projected metadata to native runtime types, constraining builtin component resolution with an allowlist, and keeping child-context inheritance compatible with CRP context fragments for the Robot integration path.

Reproduced the Forgejo lint job in a clean python:3.13-slim container with the CI commands All checks passed! and 1740 files already formatted; both passed, so the earlier lint failure appears to have been transient runner behavior rather than a source-level defect.

ISSUES CLOSED: #919
2026-04-01 06:16:41 +00:00

479 lines
25 KiB
Gherkin

@phase3 @acms @acms_pipeline_phase3
Feature: ACMS Pipeline Phase 3 — Context Finalization and Advanced Strategies
As a CleverAgents developer
I want production-grade Phase 3 pipeline components and advanced strategies
So that context assembly orders fragments coherently, generates provenance preambles,
and supports ARCE, temporal archaeology, and plan decision context strategies
# ===========================================================================
# RelevanceCoherenceOrderer
# ===========================================================================
@orderer
Scenario: Order fragments by relevance while grouping related nodes
Given the following phase3 fragments:
| uko_node | content | score | tokens | depth |
| project://app/io.py | io module | 0.9 | 20 | 3 |
| project://app/main.py | main entry | 0.5 | 15 | 3 |
| project://lib/util.py | utility fn | 0.7 | 25 | 3 |
| project://app/net.py | network | 0.8 | 10 | 3 |
When I order the fragments with RelevanceCoherenceOrderer
Then the first ordered fragment should have uko_node "project://app/io.py"
And fragments from the same group should be adjacent
@orderer
Scenario: Order with empty fragment list
Given an empty phase3 fragment list
When I order the fragments with RelevanceCoherenceOrderer
Then 0 fragments should remain after ordering
@orderer
Scenario: Order single fragment returns it unchanged
Given the following phase3 fragments:
| uko_node | content | score | tokens | depth |
| project://app/main.py | hello | 0.5 | 10 | 3 |
When I order the fragments with RelevanceCoherenceOrderer
Then 1 fragment should remain after ordering
@orderer
Scenario: Order preserves all fragments
Given the following phase3 fragments:
| uko_node | content | score | tokens | depth |
| project://app/a.py | alpha | 0.3 | 10 | 3 |
| project://app/b.py | beta | 0.9 | 10 | 5 |
| project://app/c.py | gamma | 0.6 | 10 | 1 |
When I order the fragments with RelevanceCoherenceOrderer
Then 3 fragments should remain after ordering
@orderer
Scenario: Higher-depth fragments ordered after overview in same group
Given the following phase3 fragments:
| uko_node | content | score | tokens | depth |
| project://app/main.py | detail | 0.8 | 10 | 9 |
| project://app/main.py | brief | 0.8 | 10 | 1 |
When I order the fragments with RelevanceCoherenceOrderer
Then 2 fragments should remain after ordering
# ===========================================================================
# ProvenancePreambleGenerator
# ===========================================================================
@preamble
Scenario: Generate provenance preamble with strategy contributions
Given the following phase3 fragments with strategy sources:
| uko_node | content | score | tokens | depth | strategy_source |
| project://app/main.py | hello | 0.8 | 100 | 3 | relevance |
| project://app/io.py | world | 0.6 | 50 | 5 | relevance |
| project://lib/util.py | util | 0.7 | 75 | 2 | arce |
When I generate a preamble with ProvenancePreambleGenerator
Then the preamble should contain "Strategy Contributions"
And the preamble should contain "relevance"
And the preamble should contain "arce"
And the preamble should contain "Confidence"
And the preamble should contain "Tier Distribution"
And the preamble should contain "Depth Distribution"
@preamble
Scenario: Generate preamble with confidence indicators
Given the following phase3 fragments:
| uko_node | content | score | tokens | depth |
| project://app/main.py | hello | 0.9 | 50 | 3 |
| project://app/io.py | world | 0.3 | 50 | 5 |
When I generate a preamble with ProvenancePreambleGenerator
Then the preamble should contain "avg=0.600"
And the preamble should contain "min=0.300"
And the preamble should contain "max=0.900"
@preamble
Scenario: Generate preamble returns None for empty fragments
Given an empty phase3 fragment list
When I generate a preamble with ProvenancePreambleGenerator
Then the preamble should be None
@preamble
Scenario: Preamble identifies coverage gaps for missing tiers
Given the following phase3 fragments:
| uko_node | content | score | tokens | depth |
| project://app/main.py | hello | 0.8 | 50 | 3 |
When I generate a preamble with ProvenancePreambleGenerator
Then the preamble should contain "Coverage Gaps"
And the preamble should contain "Missing tiers"
@preamble
Scenario: Preamble includes UKO node count
Given the following phase3 fragments:
| uko_node | content | score | tokens | depth |
| project://app/a.py | alpha | 0.8 | 50 | 3 |
| project://app/b.py | beta | 0.7 | 50 | 5 |
| project://lib/c.py | gamma | 0.6 | 50 | 2 |
When I generate a preamble with ProvenancePreambleGenerator
Then the preamble should contain "UKO Nodes: 3"
# ===========================================================================
# DepthReductionCompressor
# ===========================================================================
@compressor
Scenario: DepthReductionCompressor reduces Python fragments to inherited overview levels
Given the following phase3 skeleton fragments:
| uko_node | content | score | tokens | depth | domain | resource_type |
| project://src/app/main.py | def main(): return runner() | 0.9 | 120 | 7 | | python-source |
| project://src/app/util.py | class Util: pass | 0.6 | 90 | 5 | uko-py: | python-source |
When I compress with DepthReductionCompressor and budget 40
Then all compressed fragments should have detail depth at most 1
And compressed fragments should fit within budget 40
And the compressed fragments should include skeleton level "MODULE_GRAPH"
And a compressed fragment should contain "[MODULE_GRAPH]: symbols=main"
@compressor
Scenario: DepthReductionCompressor uses document detail levels for markdown fragments
Given the following phase3 skeleton fragments:
| uko_node | content | score | tokens | depth | domain | resource_type |
| project://docs/specification.md | # Spec\n## Overview\n## Details | 0.8 | 100 | 8 | | markdown |
When I compress with DepthReductionCompressor and budget 20
Then all compressed fragments should have detail depth at most 1
And compressed fragments should fit within budget 20
And the compressed fragments should include skeleton level "TABLE_OF_CONTENTS_L1"
And a compressed fragment should contain "Overview; Details"
@compressor
Scenario: DepthReductionCompressor returns no fragments for zero budget
Given the following phase3 skeleton fragments:
| uko_node | content | score | tokens | depth | domain | resource_type |
| project://src/app/a.py | def alpha(): pass | 0.7 | 80 | 6 | uko-py: | python-source |
When I compress with DepthReductionCompressor and budget 0
Then 0 fragments should remain after compression
@compressor
Scenario: DepthReductionCompressor keeps the highest-relevance fragment when budget is tight
Given the following phase3 skeleton fragments:
| uko_node | content | score | tokens | depth | domain | resource_type |
| project://src/app/main.py | def main(): return run | 0.9 | 120 | 7 | uko-py: | python-source |
| project://src/app/aux.py | def aux(): return slow | 0.3 | 120 | 7 | uko-py: | python-source |
When I compress with DepthReductionCompressor and budget 9
Then 1 fragments should remain after compression
And compressed fragments should fit within budget 9
And a compressed fragment should contain "main.py"
@compressor
Scenario: DepthReductionCompressor renders data fragments with CSV-like summary
Given the following phase3 skeleton fragments:
| uko_node | content | score | tokens | depth | domain | resource_type |
| project://db/schema.sql | users, orders, products, shipments | 0.7 | 80 | 5 | uko-data: | sql |
When I compress with DepthReductionCompressor and budget 30
Then all compressed fragments should have detail depth at most 1
And compressed fragments should fit within budget 30
And a compressed fragment should contain "schema.sql"
@compressor
Scenario: DepthReductionCompressor renders infrastructure fragments
Given the following phase3 skeleton fragments:
| uko_node | content | score | tokens | depth | domain | resource_type |
| project://deploy/main.tf | resource "aws_instance" "web" {} | 0.8 | 90 | 6 | uko-infra: | terraform |
When I compress with DepthReductionCompressor and budget 30
Then all compressed fragments should have detail depth at most 1
And compressed fragments should fit within budget 30
And a compressed fragment should contain "main.tf"
@compressor
Scenario: DepthReductionCompressor renders generic fragments at overview depth
Given the following phase3 skeleton fragments:
| uko_node | content | score | tokens | depth | domain | resource_type |
| uko:some/resource | generic resource | 0.6 | 80 | 4 | uko: | unknown |
When I compress with DepthReductionCompressor and budget 30
Then all compressed fragments should have detail depth at most 1
And compressed fragments should fit within budget 30
@compressor
Scenario: DepthReductionCompressor preserves fragment already at target depth
Given the following phase3 skeleton fragments:
| uko_node | content | score | tokens | depth | domain | resource_type |
| project://src/app/main.py | brief view | 0.9 | 10 | 0 | uko-py: | python-source |
When I compress with DepthReductionCompressor and budget 100
Then 1 fragments should remain after compression
And a compressed fragment should contain "brief view"
@compressor
Scenario: DepthReductionCompressor clips content when all renderings exceed budget
Given the following phase3 skeleton fragments:
| uko_node | content | score | tokens | depth | domain | resource_type |
| project://src/app/main.py | def main(): return very_long_function_name_placeholder() | 0.9 | 200 | 7 | uko-py: | python-source |
When I compress with DepthReductionCompressor and budget 4
Then 1 fragments should remain after compression
And compressed fragments should fit within budget 4
@compressor
Scenario: DepthReductionCompressor resolves detail map from provenance URI prefix
Given the following phase3 skeleton fragments:
| uko_node | content | score | tokens | depth | domain | resource_type |
| uko-doc:my/design-doc | # Design Overview | 0.7 | 80 | 5 | | |
When I compress with DepthReductionCompressor and budget 30
Then all compressed fragments should have detail depth at most 1
@compressor
Scenario: DepthReductionCompressor resolves detail map from resource_type
Given the following phase3 skeleton fragments:
| uko_node | content | score | tokens | depth | domain | resource_type |
| project://data/schema | CREATE TABLE users (id INTEGER) | 0.6 | 80 | 4 | | database |
When I compress with DepthReductionCompressor and budget 30
Then all compressed fragments should have detail depth at most 1
@compressor
Scenario: DepthReductionCompressor handles minimal content fragments
Given the following phase3 skeleton fragments:
| uko_node | content | score | tokens | depth | domain | resource_type |
| project://src/app/empty.py | x | 0.5 | 1 | 3 | uko-py: | python-source |
When I compress with DepthReductionCompressor and budget 10
Then compressed fragments should fit within budget 10
@compressor
Scenario: DepthReductionCompressor resolves TypeScript and Java from file extension
Given the following phase3 skeleton fragments:
| uko_node | content | score | tokens | depth | domain | resource_type |
| project://src/app.ts | export function main() {} | 0.8 | 80 | 6 | | |
| project://src/Main.java | public class Main {} | 0.7 | 80 | 6 | | |
When I compress with DepthReductionCompressor and budget 60
Then compressed fragments should fit within budget 60
@compressor
Scenario: DepthReductionCompressor resolves Rust from file extension
Given the following phase3 skeleton fragments:
| uko_node | content | score | tokens | depth | domain | resource_type |
| project://src/main.rs | fn main() {} | 0.8 | 80 | 6 | | |
When I compress with DepthReductionCompressor and budget 30
Then compressed fragments should fit within budget 30
@compressor
Scenario: DepthReductionCompressor resolves infra from YAML extension
Given the following phase3 skeleton fragments:
| uko_node | content | score | tokens | depth | domain | resource_type |
| project://k8s/deploy.yaml | apiVersion: apps/v1 | 0.7 | 80 | 5 | | |
When I compress with DepthReductionCompressor and budget 30
Then compressed fragments should fit within budget 30
@compressor
Scenario: DepthReductionCompressor detects doc resource_type
Given the following phase3 skeleton fragments:
| uko_node | content | score | tokens | depth | domain | resource_type |
| project://guides/getting-started | Welcome to the docs | 0.6 | 80 | 5 | | markdown |
When I compress with DepthReductionCompressor and budget 30
Then compressed fragments should fit within budget 30
@compressor
Scenario: DepthReductionCompressor detects infra resource_type
Given the following phase3 skeleton fragments:
| uko_node | content | score | tokens | depth | domain | resource_type |
| project://infra/cluster | kubernetes deployment manifest | 0.6 | 80 | 5 | | kubernetes |
When I compress with DepthReductionCompressor and budget 30
Then compressed fragments should fit within budget 30
# ===========================================================================
# ArceStrategy
# ===========================================================================
@arce
Scenario: ARCE ranks fragments with iterative refinement
Given an ArceStrategy with max_iterations 3
And the following strategy3 fragments:
| uko_node | content | score | tokens | depth |
| project://app/io.py | async IO handler | 0.7 | 20 | 5 |
| project://app/main.py | main application entry | 0.5 | 15 | 3 |
| project://lib/util.py | utility library | 0.3 | 25 | 2 |
And a strategy3 budget with max_tokens 1000 and reserved_tokens 0
When I assemble with the ArceStrategy
Then fragments should be returned by arce strategy
And the arce result should respect the budget
@arce
Scenario: ARCE returns empty for empty input
Given an ArceStrategy with max_iterations 5
And an empty strategy3 fragment list
And a strategy3 budget with max_tokens 1000 and reserved_tokens 0
When I assemble with the ArceStrategy
Then 0 fragments should be returned by arce strategy
@arce
Scenario: ARCE respects budget constraint
Given an ArceStrategy with max_iterations 3
And the following strategy3 fragments:
| uko_node | content | score | tokens | depth |
| project://app/a.py | alpha | 0.9 | 100 | 5 |
| project://app/b.py | beta | 0.7 | 100 | 3 |
| project://app/c.py | gamma | 0.5 | 100 | 2 |
And a strategy3 budget with max_tokens 250 and reserved_tokens 0
When I assemble with the ArceStrategy
Then at most 2 fragments should be returned by arce strategy
@arce
Scenario: ARCE can_handle returns 0.95
Given an ArceStrategy with max_iterations 5
When I check can_handle on ArceStrategy
Then the arce confidence should be 0.95
@arce
Scenario: ARCE reports capabilities
Given an ArceStrategy with max_iterations 5
Then the ArceStrategy name should be "arce"
And the ArceStrategy should support semantic search
@arce
Scenario: ARCE explain returns description
Given an ArceStrategy with max_iterations 5
Then the ArceStrategy explain should contain "Adaptive Recursive"
@arce
Scenario: ARCE iteration limit prevents unbounded refinement
Given an ArceStrategy with max_iterations 2
And the following strategy3 fragments:
| uko_node | content | score | tokens | depth |
| project://app/a.py | alpha content | 0.9 | 50 | 5 |
| project://app/b.py | beta content | 0.7 | 50 | 3 |
And a strategy3 budget with max_tokens 1000 and reserved_tokens 0
When I assemble with the ArceStrategy
Then fragments should be returned by arce strategy
# ===========================================================================
# TemporalArchaeologyStrategy
# ===========================================================================
@temporal
Scenario: TemporalArchaeology prioritises cold-tier fragments
Given a TemporalArchaeologyStrategy
And the following strategy3 fragments with tiers:
| uko_node | content | score | tokens | depth | tier |
| project://app/old.py | archived | 0.5 | 20 | 3 | cold |
| project://app/new.py | recent | 0.9 | 15 | 3 | hot |
And a strategy3 budget with max_tokens 1000 and reserved_tokens 0
When I assemble with the TemporalArchaeologyStrategy
Then the first temporal result fragment should have uko_node "project://app/old.py"
@temporal
Scenario: TemporalArchaeology returns empty for empty input
Given a TemporalArchaeologyStrategy
And an empty strategy3 fragment list
And a strategy3 budget with max_tokens 1000 and reserved_tokens 0
When I assemble with the TemporalArchaeologyStrategy
Then 0 fragments should be returned by temporal strategy
@temporal
Scenario: TemporalArchaeology can_handle returns 0.5
Given a TemporalArchaeologyStrategy
When I check can_handle on TemporalArchaeologyStrategy
Then the temporal confidence should be 0.5
@temporal
Scenario: TemporalArchaeology reports capabilities
Given a TemporalArchaeologyStrategy
Then the TemporalArchaeologyStrategy name should be "temporal-archaeology"
@temporal
Scenario: TemporalArchaeology explain returns description
Given a TemporalArchaeologyStrategy
Then the TemporalArchaeologyStrategy explain should contain "Historical"
@temporal
Scenario: TemporalArchaeology respects budget
Given a TemporalArchaeologyStrategy
And the following strategy3 fragments with tiers:
| uko_node | content | score | tokens | depth | tier |
| project://app/a.py | alpha | 0.9 | 100 | 3 | cold |
| project://app/b.py | beta | 0.7 | 100 | 3 | cold |
| project://app/c.py | gamma | 0.5 | 100 | 3 | warm |
And a strategy3 budget with max_tokens 250 and reserved_tokens 0
When I assemble with the TemporalArchaeologyStrategy
Then at most 2 fragments should be returned by temporal strategy
# ===========================================================================
# PlanDecisionContextStrategy
# ===========================================================================
@plan_decision
Scenario: PlanDecisionContext prioritises warm-tier fragments
Given a PlanDecisionContextStrategy
And the following strategy3 fragments with tiers:
| uko_node | content | score | tokens | depth | tier |
| project://app/plan.py | decision | 0.7 | 20 | 3 | warm |
| project://app/new.py | new code | 0.9 | 15 | 3 | hot |
| project://app/old.py | archived | 0.5 | 25 | 3 | cold |
And a strategy3 budget with max_tokens 1000 and reserved_tokens 0
When I assemble with the PlanDecisionContextStrategy
Then the first plan_decision result should have uko_node "project://app/plan.py"
@plan_decision
Scenario: PlanDecisionContext returns empty for empty input
Given a PlanDecisionContextStrategy
And an empty strategy3 fragment list
And a strategy3 budget with max_tokens 1000 and reserved_tokens 0
When I assemble with the PlanDecisionContextStrategy
Then 0 fragments should be returned by plan_decision strategy
@plan_decision
Scenario: PlanDecisionContext can_handle returns 0.7
Given a PlanDecisionContextStrategy
When I check can_handle on PlanDecisionContextStrategy
Then the plan_decision confidence should be 0.7
@plan_decision
Scenario: PlanDecisionContext reports capabilities
Given a PlanDecisionContextStrategy
Then the PlanDecisionContextStrategy name should be "plan-decision-context"
@plan_decision
Scenario: PlanDecisionContext explain returns description
Given a PlanDecisionContextStrategy
Then the PlanDecisionContextStrategy explain should contain "decision"
@plan_decision
Scenario: PlanDecisionContext respects budget
Given a PlanDecisionContextStrategy
And the following strategy3 fragments with tiers:
| uko_node | content | score | tokens | depth | tier |
| project://app/a.py | alpha | 0.9 | 100 | 3 | warm |
| project://app/b.py | beta | 0.7 | 100 | 3 | cold |
| project://app/c.py | gamma | 0.5 | 100 | 3 | hot |
And a strategy3 budget with max_tokens 250 and reserved_tokens 0
When I assemble with the PlanDecisionContextStrategy
Then at most 2 fragments should be returned by plan_decision strategy
# ===========================================================================
# Pipeline Registration
# ===========================================================================
@registration
Scenario: Register ArceStrategy with pipeline
Given an ACMS pipeline for phase3 strategy tests
When I register ArceStrategy with the pipeline
Then the phase3 pipeline should have strategy "arce"
@registration
Scenario: Register all Phase 3 strategies with pipeline
Given an ACMS pipeline for phase3 strategy tests
When I register all phase3 strategies with the pipeline
Then the phase3 pipeline should have strategy "arce"
And the phase3 pipeline should have strategy "temporal-archaeology"
And the phase3 pipeline should have strategy "plan-decision-context"
# ===========================================================================
# Pipeline Integration — DI injection of Phase 3 components
# ===========================================================================
@pipeline_integration
Scenario: Inject RelevanceCoherenceOrderer into pipeline
Given the ACMS pipeline with a RelevanceCoherenceOrderer
And the following phase3 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 phase3 pipeline
Then the phase3 pipeline output should contain 2 fragments
@pipeline_integration
Scenario: Inject ProvenancePreambleGenerator into pipeline
Given the ACMS pipeline with a ProvenancePreambleGenerator
And the following phase3 fragments:
| uko_node | content | score | tokens | depth |
| project://app/main.py | hello | 0.8 | 10 | 3 |
When I assemble context through the phase3 pipeline
Then the phase3 pipeline output should have a preamble
And the phase3 pipeline preamble should contain "Context Assembly Provenance"