Files
cleveragents-core/features/acms_scope_resolution.feature
HAL9000 77fef24be6
CI / push-validation (pull_request) Successful in 29s
CI / lint (pull_request) Successful in 42s
CI / build (pull_request) Successful in 40s
CI / helm (pull_request) Successful in 50s
CI / quality (pull_request) Successful in 58s
CI / typecheck (pull_request) Successful in 1m14s
CI / security (pull_request) Successful in 1m22s
CI / unit_tests (pull_request) Successful in 5m36s
CI / docker (pull_request) Successful in 1m39s
CI / integration_tests (pull_request) Successful in 9m15s
CI / coverage (pull_request) Successful in 11m22s
CI / status-check (pull_request) Successful in 4s
feat(acms): implement ACMS pipeline scope resolution and context inheritance for child plans
Introduced PipelineScopeResolver in src/cleveragents/application/services/acms_scope_resolver.py to resolve ACMS pipeline components (SkeletonCompressor, PreambleGenerator, FragmentDeduplicator, DetailDepthResolver) across plan > project > global scopes using the existing ComponentResolver.

Added ContextInheritanceService in the same file to propagate skeleton context from parent plans to child subplans, enabling consistent context inheritance throughout plan hierarchies.

Added a new BDD feature file features/acms_scope_resolution.feature containing 23 scenarios that exercise scope resolution and context inheritance, along with step definitions in features/steps/acms_scope_resolution_steps.py to drive behavior-driven tests.

ISSUES CLOSED: #10016
2026-06-06 11:08:28 -04:00

230 lines
12 KiB
Gherkin

@acms @acms_scope_resolution @scope_resolution
Feature: ACMS Pipeline Scope Resolution and Context Inheritance
As a CleverAgents developer
I want pipeline components to be overridable at global, project, and plan scope
So that context assembly can be customized per-plan or per-project with correct precedence
# ---------------------------------------------------------------------------
# PipelineScopeResolver -- Global scope
# ---------------------------------------------------------------------------
@scope_chain @global
Scenario: Global default skeleton compressor is resolved when no overrides exist
Given a fresh PipelineScopeResolver
And a global default SkeletonCompressor registered
When I resolve the skeleton compressor with no plan or project context
Then the resolved skeleton compressor should be the global default
And the pipeline scope resolution scope should be "global"
@scope_chain @global
Scenario: Global default preamble generator is resolved when no overrides exist
Given a fresh PipelineScopeResolver
And a global default PreambleGenerator registered
When I resolve the preamble generator with no plan or project context
Then the resolved preamble generator should be the global default
And the pipeline scope resolution scope should be "global"
@scope_chain @global
Scenario: Global default deduplicator is resolved when no overrides exist
Given a fresh PipelineScopeResolver
And a global default FragmentDeduplicator registered
When I resolve the deduplicator with no plan or project context
Then the resolved deduplicator should be the global default
And the pipeline scope resolution scope should be "global"
@scope_chain @global
Scenario: Global default depth resolver is resolved when no overrides exist
Given a fresh PipelineScopeResolver
And a global default DetailDepthResolver registered
When I resolve the depth resolver with no plan or project context
Then the resolved depth resolver should be the global default
And the pipeline scope resolution scope should be "global"
# ---------------------------------------------------------------------------
# PipelineScopeResolver -- Project scope
# ---------------------------------------------------------------------------
@scope_chain @project
Scenario: Project override takes precedence over global for skeleton compressor
Given a fresh PipelineScopeResolver
And a global default SkeletonCompressor registered
And a project-level SkeletonCompressor override for project "my-project"
When I resolve the skeleton compressor for project "my-project"
Then the resolved skeleton compressor should be the project override
And the pipeline scope resolution scope should be "project"
@scope_chain @project
Scenario: Project without override falls through to global for skeleton compressor
Given a fresh PipelineScopeResolver
And a global default SkeletonCompressor registered
When I resolve the skeleton compressor for project "other-project"
Then the resolved skeleton compressor should be the global default
And the pipeline scope resolution scope should be "global"
@scope_chain @project
Scenario: Project override takes precedence over global for preamble generator
Given a fresh PipelineScopeResolver
And a global default PreambleGenerator registered
And a project-level PreambleGenerator override for project "proj-a"
When I resolve the preamble generator for project "proj-a"
Then the resolved preamble generator should be the project override
And the pipeline scope resolution scope should be "project"
# ---------------------------------------------------------------------------
# PipelineScopeResolver -- Plan scope (highest priority)
# ---------------------------------------------------------------------------
@scope_chain @plan
Scenario: Plan override takes highest precedence over project and global
Given a fresh PipelineScopeResolver
And a global default SkeletonCompressor registered
And a project-level SkeletonCompressor override for project "my-project"
And a plan-level SkeletonCompressor override for plan "plan-001"
When I resolve the skeleton compressor for plan "plan-001" and project "my-project"
Then the resolved skeleton compressor should be the plan override
And the pipeline scope resolution scope should be "plan"
@scope_chain @plan
Scenario: Plan without override falls through to project then global
Given a fresh PipelineScopeResolver
And a global default SkeletonCompressor registered
And a project-level SkeletonCompressor override for project "my-project"
When I resolve the skeleton compressor for plan "plan-002" and project "my-project"
Then the resolved skeleton compressor should be the project override
And the pipeline scope resolution scope should be "project"
@scope_chain @plan
Scenario: Plan override for deduplicator takes highest precedence
Given a fresh PipelineScopeResolver
And a global default FragmentDeduplicator registered
And a plan-level FragmentDeduplicator override for plan "plan-dedup"
When I resolve the deduplicator for plan "plan-dedup"
Then the resolved deduplicator should be the plan override
And the pipeline scope resolution scope should be "plan"
@scope_chain @plan
Scenario: Plan override for depth resolver takes highest precedence
Given a fresh PipelineScopeResolver
And a global default DetailDepthResolver registered
And a plan-level DetailDepthResolver override for plan "plan-depth"
When I resolve the depth resolver for plan "plan-depth"
Then the resolved depth resolver should be the plan override
And the pipeline scope resolution scope should be "plan"
# ---------------------------------------------------------------------------
# PipelineScopeResolver -- resolution_scope introspection
# ---------------------------------------------------------------------------
@scope_chain @introspection
Scenario: resolution_scope returns global when only global is registered
Given a fresh PipelineScopeResolver
And a global default SkeletonCompressor registered
When I check the resolution scope for SkeletonCompressor with no context
Then the pipeline scope resolution scope should be "global"
@scope_chain @introspection
Scenario: resolution_scope returns plan when plan override exists
Given a fresh PipelineScopeResolver
And a global default SkeletonCompressor registered
And a plan-level SkeletonCompressor override for plan "scope-plan"
When I check the resolution scope for SkeletonCompressor for plan "scope-plan"
Then the pipeline scope resolution scope should be "plan"
# ---------------------------------------------------------------------------
# ContextInheritanceService -- basic inheritance
# ---------------------------------------------------------------------------
@context_inheritance @basic
Scenario: Child plan receives skeleton context from parent payload
Given a PipelineScopeResolver with a global DefaultSkeletonCompressor
And a ContextInheritanceService with default skeleton ratio 0.15
And a parent payload with 3 fragments totaling 1000 tokens
When I inherit context for child plan "01JQTESTPN00000000000000AA" with budget 2000
Then the skeleton fragments should be non-empty
@context_inheritance @basic
Scenario: Empty parent payload produces empty skeleton
Given a PipelineScopeResolver with a global DefaultSkeletonCompressor
And a ContextInheritanceService with default skeleton ratio 0.15
And an empty parent payload
When I inherit context for child plan "01JQTESTPN00000000000000AA" with budget 2000
Then the skeleton fragments should be empty
@context_inheritance @basic
Scenario: Zero skeleton budget produces empty skeleton
Given a PipelineScopeResolver with a global DefaultSkeletonCompressor
And a ContextInheritanceService with default skeleton ratio 0.0
And a parent payload with 3 fragments totaling 1000 tokens
When I inherit context for child plan "01JQTESTPN00000000000000AA" with budget 2000
Then the skeleton fragments should be empty
@context_inheritance @basic
Scenario: inherit_from_fragments compresses raw fragments for child plan
Given a PipelineScopeResolver with a global DefaultSkeletonCompressor
And a ContextInheritanceService with default skeleton ratio 0.15
And parent fragments with 2 fragments totaling 500 tokens
When I inherit from fragments for child plan "01JQTESTPN00000000000000AA" with budget 1000
Then the skeleton fragments should be non-empty
@context_inheritance @basic
Scenario: Empty parent fragments produces empty skeleton from inherit_from_fragments
Given a PipelineScopeResolver with a global DefaultSkeletonCompressor
And a ContextInheritanceService with default skeleton ratio 0.15
And empty parent fragments
When I inherit from fragments for child plan "01JQTESTPN00000000000000AA" with budget 1000
Then the skeleton fragments should be empty
# ---------------------------------------------------------------------------
# ContextInheritanceService -- scope resolution during inheritance
# ---------------------------------------------------------------------------
@context_inheritance @scope_chain
Scenario: Context inheritance uses plan-level compressor when registered
Given a PipelineScopeResolver with a global DefaultSkeletonCompressor
And a plan-level tracking SkeletonCompressor for plan "01JQTESTPN00000000000000AA"
And a ContextInheritanceService with default skeleton ratio 0.15
And a parent payload with 2 fragments totaling 500 tokens
When I inherit context for child plan "01JQTESTPN00000000000000AA" with budget 1000
Then the plan-level tracking compressor should have been called
@context_inheritance @scope_chain
Scenario: Context inheritance uses project-level compressor when registered
Given a PipelineScopeResolver with a global DefaultSkeletonCompressor
And a project-level tracking SkeletonCompressor for project "my-project"
And a ContextInheritanceService with default skeleton ratio 0.15
And a parent payload with 2 fragments totaling 500 tokens
When I inherit context for child plan "01JQTESTPN00000000000000AA" with budget 1000 and project "my-project"
Then the project-level tracking compressor should have been called
# ---------------------------------------------------------------------------
# ContextInheritanceService -- skeleton ratio
# ---------------------------------------------------------------------------
@context_inheritance @skeleton_ratio
Scenario: Custom skeleton ratio governs the skeleton budget
Given a PipelineScopeResolver with a global DefaultSkeletonCompressor
And a ContextInheritanceService with default skeleton ratio 0.15
And a parent payload with 1 fragment of 100 tokens
When I inherit context for child plan "01JQTESTPN00000000000000AA" with budget 1000 and skeleton ratio 0.5
Then the skeleton fragments should be non-empty
@context_inheritance @skeleton_ratio
Scenario: Default skeleton ratio is used when none specified
Given a PipelineScopeResolver with a global DefaultSkeletonCompressor
And a ContextInheritanceService with default skeleton ratio 0.2
Then the service default skeleton ratio should be 0.2
# ---------------------------------------------------------------------------
# ContextInheritanceService -- budget-respecting compressor
# ---------------------------------------------------------------------------
@context_inheritance @budget
Scenario: Tracking compressor respects budget during inheritance
Given a PipelineScopeResolver with a global DefaultSkeletonCompressor
And a plan-level tracking SkeletonCompressor for plan "01JQTESTPN00000000000000AA"
And a ContextInheritanceService with default skeleton ratio 0.15
And a parent payload with 3 fragments totaling 1000 tokens
When I inherit context for child plan "01JQTESTPN00000000000000AA" with budget 2000
Then the plan-level tracking compressor should have been called
And the skeleton fragments total tokens should be at most 300