Compare commits

..

7 Commits

Author SHA1 Message Date
HAL9000 16362adb76 docs: add context management deep dive 2026-04-19 14:56:28 +00:00
HAL9000 9a5ccc6b01 Merge pull request 'Update timeline: milestone status for 2026-04-19' (#10684) from timeline-update-2026-04-19-final into master
CI / benchmark-regression (push) Failing after 0s
CI / benchmark-publish (push) Failing after 0s
CI / helm (push) Successful in 40s
CI / push-validation (push) Successful in 22s
CI / build (push) Successful in 3m57s
CI / lint (push) Successful in 4m13s
CI / quality (push) Successful in 4m34s
CI / typecheck (push) Successful in 4m56s
CI / security (push) Successful in 5m4s
CI / e2e_tests (push) Successful in 8m2s
CI / integration_tests (push) Successful in 8m8s
CI / unit_tests (push) Successful in 9m27s
CI / coverage (push) Successful in 14m53s
CI / docker (push) Successful in 1m49s
CI / status-check (push) Successful in 3s
2026-04-19 06:18:05 +00:00
HAL9000 f167098541 Merge branch 'master' into timeline-update-2026-04-19-final
CI / helm (pull_request) Successful in 42s
CI / lint (pull_request) Successful in 3m58s
CI / build (pull_request) Successful in 4m1s
CI / typecheck (pull_request) Successful in 4m40s
CI / quality (pull_request) Successful in 4m42s
CI / security (pull_request) Successful in 5m22s
CI / push-validation (pull_request) Successful in 23s
CI / integration_tests (pull_request) Successful in 8m7s
CI / e2e_tests (pull_request) Successful in 8m31s
CI / unit_tests (pull_request) Successful in 9m44s
CI / coverage (pull_request) Successful in 14m52s
CI / docker (pull_request) Successful in 2m3s
CI / status-check (pull_request) Successful in 3s
2026-04-19 05:27:54 +00:00
HAL9000 072f470212 fix(agents): make bug-hunt-pool-supervisor tracking non-blocking to prevent initialization hangs
CI / helm (pull_request) Successful in 40s
CI / push-validation (pull_request) Successful in 23s
CI / lint (pull_request) Successful in 4m0s
CI / typecheck (pull_request) Successful in 4m23s
CI / build (pull_request) Successful in 3m36s
CI / quality (pull_request) Successful in 4m15s
CI / security (pull_request) Successful in 4m37s
CI / e2e_tests (pull_request) Successful in 7m43s
CI / integration_tests (pull_request) Successful in 8m44s
CI / unit_tests (pull_request) Successful in 9m18s
CI / docker (pull_request) Successful in 1m37s
CI / coverage (pull_request) Successful in 14m53s
CI / benchmark-regression (push) Failing after 0s
CI / benchmark-publish (push) Failing after 0s
CI / status-check (pull_request) Successful in 5s
CI / helm (push) Successful in 32s
CI / build (push) Successful in 3m48s
CI / lint (push) Successful in 3m55s
CI / quality (push) Successful in 4m23s
CI / typecheck (push) Successful in 4m48s
CI / security (push) Successful in 4m51s
CI / push-validation (push) Successful in 23s
CI / e2e_tests (push) Successful in 7m30s
CI / integration_tests (push) Successful in 7m44s
CI / unit_tests (push) Successful in 9m11s
CI / docker (push) Successful in 1m38s
CI / coverage (push) Successful in 14m50s
CI / status-check (push) Successful in 13s
The automation-tracking-manager call in step 5 was blocking the main loop
indefinitely, causing 3+ consecutive initialization failures. This commit
documents the fix in CHANGELOG.md with the proper issue reference.

ISSUES CLOSED: #8835
2026-04-19 04:02:17 +00:00
HAL9000 1f95ea0c2a Update timeline: milestone status for 2026-04-19
CI / push-validation (pull_request) Successful in 35s
CI / helm (pull_request) Successful in 38s
CI / lint (pull_request) Successful in 4m2s
CI / quality (pull_request) Successful in 4m18s
CI / typecheck (pull_request) Successful in 4m43s
CI / security (pull_request) Successful in 4m48s
CI / build (pull_request) Successful in 3m48s
CI / e2e_tests (pull_request) Successful in 6m57s
CI / integration_tests (pull_request) Successful in 7m48s
CI / unit_tests (pull_request) Successful in 10m11s
CI / docker (pull_request) Successful in 1m37s
CI / coverage (pull_request) Successful in 14m56s
CI / status-check (pull_request) Successful in 5s
2026-04-19 04:00:21 +00:00
HAL9000 832d0b26ae Update timeline: milestone status for 2026-04-19 2026-04-19 03:59:57 +00:00
HAL9000 89baa0a525 Update timeline: milestone status for 2026-04-19 2026-04-19 03:59:35 +00:00
8 changed files with 2542 additions and 6424 deletions
+8
View File
@@ -28,6 +28,14 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
correctly in all deployment modes: Docker containers, local pip installs
(wheel or editable), and development environments.
- **TDD Non-AssertionError Guard Visibility** (#8294): `apply_tdd_inversion` in
- **bug-hunt-pool-supervisor Non-Blocking Tracking** (#8835): The automation-tracking-manager
call in step 5 was blocking the main loop indefinitely, causing 3+ consecutive initialization
failures. Step 5 now explicitly marks tracking as best-effort -- if the call does not complete
within a reasonable time or fails, it is skipped and the supervisor continues to the next
cycle. A new Rule 9 reinforces that tracking must never block the main loop; core
functionality (module mapping, worker dispatch, monitoring) takes priority over status
reporting.
`features/environment.py` now emits its non-assertion exception guard warning to
both the structured logger and `stderr` via a new `_warning_with_stderr` helper.
This makes the guard firing visible in standard Behave console output and CI log
+998
View File
@@ -0,0 +1,998 @@
# Context Management Deep Dive
This comprehensive guide explores the Adaptive Context Management System (ACMS) architecture, strategies, and best practices for managing context in cleveragents-core.
## Table of Contents
1. [Context Management Architecture Overview](#context-management-architecture-overview)
2. [ACMS Pipeline Components](#acms-pipeline-components)
3. [Context Strategy Patterns](#context-strategy-patterns)
4. [Context Inheritance and Scope Resolution](#context-inheritance-and-scope-resolution)
5. [Budget Allocation and Utilization Metrics](#budget-allocation-and-utilization-metrics)
6. [Implementation Guide for Custom Strategies](#implementation-guide-for-custom-strategies)
7. [Best Practices and Performance Considerations](#best-practices-and-performance-considerations)
8. [Integration with Plan Execution](#integration-with-plan-execution)
9. [Real-world Examples](#real-world-examples)
## Context Management Architecture Overview
### What is Context Management?
Context management is the process of selecting, prioritizing, and packaging relevant information from a project's knowledge base to provide to language models (LMs) during plan execution. The ACMS ensures that:
- **Relevance**: Only the most pertinent information is included
- **Budget Compliance**: Token limits are strictly respected
- **Efficiency**: Context assembly is fast and deterministic
- **Traceability**: All context is provenance-tracked back to its source
### The Three-Tier Context Model
ACMS organizes context into three priority tiers:
| Tier | Purpose | Inclusion Strategy |
|------|---------|-------------------|
| **hot** | Critical context that must be included | Always included first; highest priority |
| **warm** | Standard context included when budget allows | Included after hot tier; default tier |
| **cold** | Low-priority context included only if space remains | Included last; only if budget permits |
> **Note:** In v1, tiers are sort-priority labels for ranking during assembly. Full storage-tier semantics with retention policies and promotion/demotion will be implemented in future milestones.
### Core Concepts
#### Context Fragments
A **ContextFragment** is the atomic unit of context. Each fragment represents a single piece of information:
```python
ContextFragment(
uko_node="project://myapp/src/main.py", # UKO URI of source
content="def main(): ...", # Rendered text content
detail_depth=3, # Depth level (0-9)
token_count=100, # Actual token count
relevance_score=0.9, # Relevance (0.0-1.0)
tier="hot", # Priority tier
provenance=FragmentProvenance( # Source tracking
resource_uri="project://myapp/src/main.py",
location="line 1-10",
resource_type="source_file"
)
)
```
**Key Properties:**
- `fragment_id`: Unique ULID identifier (auto-generated)
- `content`: Max 1,000,000 characters
- `detail_depth`: Resolved depth from 0 (MODULE_LISTING) to 9 (FULL_SOURCE)
- `token_count`: Must be >= 0; used for budget calculations
- `relevance_score`: 0.0 (irrelevant) to 1.0 (highly relevant)
- `metadata`: Arbitrary key-value pairs (max 64 entries)
- `created_at`: UTC timestamp for recency-based sorting
#### Context Budget
The **ContextBudget** defines token constraints:
```python
budget = ContextBudget(
max_tokens=4096, # Total available tokens
reserved_tokens=512 # Tokens reserved for system prompt
)
# Available tokens = max_tokens - reserved_tokens = 3584
```
**Budget Rules:**
- `available_tokens = max_tokens - reserved_tokens`
- `reserved_tokens` must be < `max_tokens`
- Pipeline never exceeds `available_tokens`
- Fragments exceeding budget are skipped
#### Context Payload
The assembled **ContextPayload** is the final output:
```python
payload = ContextPayload(
payload_id="01ARZ3NDEKTSV4RRFFQ69G5FAV", # ULID
plan_id="plan-1", # Associated plan
fragments=(fragment1, fragment2, ...), # Selected fragments
total_tokens=2048, # Sum of fragment tokens
budget=budget, # Budget used
budget_used=0.57, # Fraction consumed
strategies_used=("simple-keyword",), # Contributing strategies
context_hash="abc123...", # SHA-256 hash
preamble="Context includes 2 files...", # Optional summary
provenance_map={...}, # Fragment ID -> provenance
assembled_at=datetime.now(timezone.utc) # Assembly timestamp
)
```
**Key Properties:**
- `is_within_budget`: True if total_tokens <= available_tokens
- `remaining_tokens`: available_tokens - total_tokens
### Context Request Protocol (CRP)
The **ContextRequest** specifies what context is needed:
```python
request = ContextRequest(
focus_nodes=["project://myapp/src/main.py"], # Primary focus
breadth=2, # Graph traversal breadth
depth=3, # Graph traversal depth
keywords=["async", "io"], # Search keywords
resource_types=["source_file", "decision"], # Filter by type
detail_level=3, # Desired detail depth
temporal_window="7d", # Historical window
strategy_hints=["semantic-embedding"], # Preferred strategies
)
```
## ACMS Pipeline Components
The ACMS pipeline implements a 10-component architecture across three phases:
### Phase 1: Strategy Orchestration
#### 1. StrategySelector
**Purpose**: Determine which strategies can handle the request.
**Process**:
1. Poll all registered strategies with `can_handle(request)`
2. Each strategy returns confidence (0.0-1.0)
3. Select strategies above confidence threshold
4. Rank by confidence score
#### 2. BudgetAllocator
**Purpose**: Distribute token budget among selected strategies.
**Allocation Strategies**:
- **Proportional**: Allocate based on confidence scores
- **Equal**: Divide equally among strategies
- **Priority**: Allocate to highest-confidence first
#### 3. StrategyExecutor
**Purpose**: Execute strategies in parallel with timeout and circuit breaker.
**Features**:
- Parallel execution with configurable workers
- Per-strategy timeout (default 30s)
- Circuit breaker after N failures
- Graceful degradation on timeout
### Phase 2: Fragment Fusion
#### 4. FragmentDeduplicator
**Purpose**: Remove duplicate fragments based on UKO node and content hash.
**Deduplication Rules**:
- Same `uko_node` + same content hash = duplicate
- Keep fragment with highest relevance score
- Preserve provenance from all duplicates
#### 5. DetailDepthResolver
**Purpose**: Resolve appropriate detail depth for each fragment.
**Resolution Logic**:
- Start with requested detail level
- Adjust based on token budget
- Respect fragment's max depth
- Prefer higher depth for hot tier
#### 6. FragmentScorer
**Purpose**: Score fragments based on multiple factors.
**Scoring Factors**:
- Relevance score (from strategy)
- Tier priority (hot > warm > cold)
- Recency (created_at)
- Plan context alignment
- Resource type affinity
#### 7. BudgetPacker
**Purpose**: Select fragments to fit within token budget.
**Packing Algorithm**:
1. Sort fragments by score (descending)
2. Greedily add fragments until budget exhausted
3. Respect tier ordering (hot → warm → cold)
4. Early termination when budget full
#### 8. FragmentOrderer
**Purpose**: Determine final fragment order in payload.
**Ordering Strategies**:
- **Relevance**: By relevance_score (descending)
- **Recency**: By created_at (descending)
- **Tier-Relevance**: By tier, then relevance
- **Custom**: User-defined ordering
### Phase 3: Context Finalization
#### 9. PreambleGenerator
**Purpose**: Generate optional summary of assembled context.
**Preamble Contents**:
- Number of fragments and sources
- Token usage and budget utilization
- Strategies used
- Key topics covered
- Temporal range
#### 10. SkeletonCompressor
**Purpose**: Compress context for child plans while preserving critical information.
**Compression Strategy**:
- Identify critical fragments for child focus
- Remove redundant details
- Preserve decision context
- Maintain provenance
## Context Strategy Patterns
### Strategy Architecture
All strategies implement the `ContextStrategy` protocol:
```python
class ContextStrategy(Protocol):
@property
def name(self) -> str:
"""Unique strategy identifier"""
...
@property
def capabilities(self) -> StrategyCapabilities:
"""Declare backend requirements and quality"""
...
def can_handle(
self,
request: ContextRequest,
backends: BackendSet
) -> float:
"""Return confidence (0.0-1.0) for handling request"""
...
def assemble(
self,
request: ContextRequest,
backends: BackendSet,
budget: ContextBudget,
plan_context: PlanContext
) -> list[ContextFragment]:
"""Execute strategy and return fragments"""
...
def explain(self) -> str:
"""Human-readable description"""
...
```
### Built-in Strategies
#### 1. Simple Keyword Strategy
**Quality Score**: 0.3 (baseline)
**Backend Requirements**: TextBackend
**How It Works**:
1. Extract keywords from request
2. Search TextBackend using keywords
3. Rank results by match frequency
4. Pack greedily into budget
**Best For**:
- Quick searches with clear keywords
- Projects with good text indexing
- Fallback when other backends unavailable
#### 2. Semantic Embedding Strategy
**Quality Score**: 0.6 (good)
**Backend Requirements**: VectorBackend
**How It Works**:
1. Embed request using character-frequency model (v1)
2. Search VectorBackend for similar embeddings
3. Rank by cosine similarity
4. Pack budget-aware results
**Best For**:
- Semantic similarity searches
- Conceptual relationships
- Cross-domain context discovery
#### 3. Breadth-Depth Navigator Strategy
**Quality Score**: 0.85 (excellent)
**Backend Requirements**: GraphBackend
**How It Works**:
1. Start from focus nodes in request
2. Traverse graph outward by breadth hops
3. Traverse downward by depth hops
4. Collect all reachable nodes
5. Rank by distance and relevance
6. Pack into budget
**Best For**:
- Exploring related code and decisions
- Understanding dependencies
- Comprehensive context assembly
#### 4. ARCE Strategy (Advanced Retrieval with Context Enrichment)
**Quality Score**: 0.95 (highest)
**Backend Requirements**: TextBackend, VectorBackend, GraphBackend
**How It Works**:
1. **Phase 1 (40% budget)**: Text search using keywords
2. **Phase 2 (40% budget)**: Vector similarity search
3. **Phase 3 (20% budget)**: Graph traversal from results
4. Merge and deduplicate results
5. Rank by combined score
6. Pack into budget
**Best For**:
- Comprehensive context assembly
- Multi-modal queries
- Production use cases
#### 5. Temporal Archaeology Strategy
**Quality Score**: 0.5 (moderate)
**Backend Requirements**: GraphBackend, TemporalBackend (cold tier)
**How It Works**:
1. Query TemporalBackend for historical nodes
2. Filter by temporal window
3. Traverse graph from historical nodes
4. Collect related current nodes
5. Rank by recency and relevance
6. Pack into budget
**Best For**:
- Understanding historical context
- Decision archaeology
- Long-term project evolution
#### 6. Plan-Decision Context Strategy
**Quality Score**: 0.7 (good)
**Backend Requirements**: TemporalBackend (warm/cold), GraphBackend
**How It Works**:
1. Walk parent and ancestor plan hierarchy
2. Retrieve decision records from warm/cold tiers
3. Traverse graph from decisions
4. Collect related context
5. Rank by plan proximity and relevance
6. Pack into budget
**Best For**:
- Understanding plan context
- Decision inheritance
- Multi-level plan coordination
### Strategy Comparison Matrix
| Strategy | Quality | Speed | Coverage | Best For |
|----------|---------|-------|----------|----------|
| Simple Keyword | 0.3 | ⚡⚡⚡ | Narrow | Quick searches |
| Semantic Embedding | 0.6 | ⚡⚡ | Broad | Semantic similarity |
| Breadth-Depth Navigator | 0.85 | ⚡ | Very Broad | Comprehensive context |
| ARCE | 0.95 | 🐢 | Comprehensive | Production use |
| Temporal Archaeology | 0.5 | 🐢 | Historical | Historical context |
| Plan-Decision Context | 0.7 | ⚡ | Hierarchical | Plan coordination |
## Context Inheritance and Scope Resolution
### Scope Hierarchy
Context is organized in a hierarchy of scopes:
```
Global Scope
├── Project Scope
│ ├── Module Scope
│ │ ├── Class Scope
│ │ │ └── Method Scope
│ │ └── Function Scope
│ └── Decision Scope
└── Temporal Scope
├── Current
├── Recent (7d)
└── Historical (30d+)
```
### Scope Resolution Rules
When assembling context for a request:
1. **Start at Request Focus**: Begin with explicitly requested nodes
2. **Expand Upward**: Include parent scopes (module → project → global)
3. **Expand Downward**: Include child scopes (class → methods)
4. **Expand Sideways**: Include sibling scopes (related classes)
5. **Apply Filters**: Remove out-of-scope items based on resource types
6. **Respect Budget**: Stop expansion when budget exhausted
### Context Inheritance
Child plans inherit context from parent plans:
```python
# Parent plan context
parent_payload = ContextPayload(
fragments=[
ContextFragment(uko_node="project://myapp/src/main.py", ...),
ContextFragment(uko_node="project://myapp/src/utils.py", ...),
ContextFragment(uko_node="project://myapp/decisions/001", ...),
]
)
# Child plan inherits and specializes
child_request = ContextRequest(
focus_nodes=["project://myapp/src/main.py"],
parent_context=parent_payload,
inherit_strategy="skeleton" # Compress parent context
)
# Result: Child gets focused context + compressed parent context
child_payload = pipeline.assemble(child_request)
```
**Inheritance Strategies**:
- **Full**: Include all parent context (if budget allows)
- **Skeleton**: Compress parent context to essentials
- **None**: Start fresh (no parent context)
## Budget Allocation and Utilization Metrics
### Budget Planning
Effective budget allocation requires understanding token costs:
```python
# Typical token costs
costs = {
"system_prompt": 512, # Reserved
"user_request": 200, # Typical request
"model_response": 1024, # Expected response
"context": 2048, # Available for context
}
total_budget = 4096
reserved = 512
available = total_budget - reserved # 3584 tokens
```
### Budget Allocation Strategies
#### Proportional Allocation
Allocate based on strategy confidence scores:
```python
candidates = [
(semantic_embedding, 0.95),
(breadth_depth, 0.85),
(simple_keyword, 0.3)
]
total_confidence = 0.95 + 0.85 + 0.3 # 2.1
allocations = {
semantic_embedding: int(3584 * 0.95 / 2.1), # 1621
breadth_depth: int(3584 * 0.85 / 2.1), # 1450
simple_keyword: int(3584 * 0.3 / 2.1), # 513
}
```
#### Priority Allocation
Allocate to highest-confidence strategies first:
```python
allocations = {}
remaining = 3584
for strategy, confidence in sorted(candidates, key=lambda x: x[1], reverse=True):
# Allocate 50% of remaining budget
allocation = remaining // 2
allocations[strategy] = allocation
remaining -= allocation
```
#### Tier-Based Allocation
Allocate based on tier requirements:
```python
allocations = {
"hot_tier": 1000, # 28% - Critical context
"warm_tier": 2000, # 56% - Standard context
"cold_tier": 584, # 16% - Low-priority context
}
```
### Utilization Metrics
Track context assembly efficiency:
```python
metrics = {
"budget_used": payload.budget_used, # 0.57 (57%)
"remaining_tokens": payload.remaining_tokens, # 1536
"fragments_selected": len(payload.fragments), # 12
"fragments_total": 45, # Total available
"coverage": 12 / 45, # 26.7%
"strategies_used": payload.strategies_used, # ("semantic-embedding",)
"assembly_time_ms": 234, # Execution time
"avg_fragment_tokens": 2048 / 12, # 170.7
"avg_relevance": 0.82, # Mean relevance score
}
```
### Budget Optimization
Techniques to improve budget utilization:
1. **Increase Reserved Tokens**: Allocate more for system prompt
2. **Reduce Detail Depth**: Lower detail_depth for less critical fragments
3. **Increase Relevance Threshold**: Filter low-relevance fragments
4. **Use Skeleton Compression**: Compress parent context for child plans
5. **Tier-Based Filtering**: Exclude cold tier if budget tight
## Implementation Guide for Custom Strategies
### Creating a Custom Strategy
#### Step 1: Define Strategy Class
```python
from cleveragents.application.services.acms_service import (
ContextStrategy,
StrategyCapabilities,
)
from cleveragents.domain.models.core.context_fragment import (
ContextBudget,
ContextFragment,
)
from typing import Sequence, Any
class MyCustomStrategy:
"""Custom context strategy for specialized retrieval."""
@property
def name(self) -> str:
"""Unique strategy identifier."""
return "my-custom-strategy"
@property
def capabilities(self) -> StrategyCapabilities:
"""Declare backend requirements and quality."""
return StrategyCapabilities(
uses_text=True,
uses_vector=False,
uses_graph=True,
uses_temporal=False,
resource_types=("source_file", "decision"),
quality_score=0.75,
supports_depth_breadth=True,
supports_plan_hierarchy=False,
supports_temporal=False,
)
def can_handle(self, request: dict[str, Any]) -> float:
"""Return confidence (0.0-1.0) for handling request."""
# Check if request has required fields
if "focus_nodes" not in request:
return 0.0
# Check for specific keywords
keywords = request.get("keywords", [])
if any(kw in ["async", "io", "performance"] for kw in keywords):
return 0.9 # High confidence for performance-related queries
return 0.6 # Moderate confidence for general queries
def assemble(
self,
fragments: Sequence[ContextFragment],
budget: ContextBudget,
) -> Sequence[ContextFragment]:
"""Execute strategy and return fragments."""
# Custom ranking logic
ranked = sorted(
fragments,
key=lambda f: (
f.tier == "hot", # Hot tier first
f.relevance_score, # Then by relevance
f.created_at, # Then by recency
),
reverse=True
)
# Pack into budget
selected = []
total_tokens = 0
for fragment in ranked:
if total_tokens + fragment.token_count <= budget.available_tokens:
selected.append(fragment)
total_tokens += fragment.token_count
else:
break # Budget exhausted
return selected
def explain(self) -> str:
"""Human-readable description."""
return (
"Custom strategy that prioritizes hot-tier fragments "
"and performance-related keywords."
)
```
#### Step 2: Register Strategy
```python
from cleveragents.application.services.acms_service import ACMSPipeline
# Create pipeline
pipeline = ACMSPipeline()
# Register custom strategy
strategy = MyCustomStrategy()
pipeline.register_strategy(strategy.name, strategy)
# Use in assembly
payload = pipeline.assemble(
plan_id="plan-1",
fragments=fragments,
budget=budget,
strategy="my-custom-strategy"
)
```
#### Step 3: Configure in TOML
```toml
[context.strategies]
enabled = ["simple-keyword", "my-custom-strategy"]
[context.strategies.custom]
"my-custom-strategy" = "my_package.strategies:MyCustomStrategy"
[context.strategies."my-custom-strategy"]
enabled = true
timeout_seconds = 30
max_fragments = 100
```
## Best Practices and Performance Considerations
### Performance Optimization
#### 1. Strategy Selection
**Best Practice**: Use strategy confidence scores to avoid unnecessary execution.
#### 2. Budget Allocation
**Best Practice**: Allocate more budget to higher-confidence strategies.
#### 3. Timeout Management
**Best Practice**: Set appropriate timeouts based on strategy complexity.
#### 4. Fragment Deduplication
**Best Practice**: Deduplicate early to reduce downstream processing.
#### 5. Detail Depth Resolution
**Best Practice**: Reduce detail depth for less critical fragments.
### Memory Efficiency
#### 1. Fragment Streaming
**Best Practice**: Stream fragments instead of loading all at once.
#### 2. Lazy Evaluation
**Best Practice**: Defer expensive operations until needed.
### Reliability and Robustness
#### 1. Circuit Breaker Pattern
**Best Practice**: Implement circuit breaker for failing strategies.
#### 2. Graceful Degradation
**Best Practice**: Fall back to simpler strategies when complex ones fail.
#### 3. Timeout Handling
**Best Practice**: Set reasonable timeouts with fallback.
### Monitoring and Observability
#### 1. Metrics Collection
**Best Practice**: Collect comprehensive metrics for monitoring.
#### 2. Logging
**Best Practice**: Log key decisions and failures.
## Integration with Plan Execution
### Context Assembly in Plan Execution
Context is assembled at key points in plan execution:
```
Plan Execution Flow
├── Plan Created
│ └── Initial Context Assembly
│ ├── StrategySelector: Choose strategies
│ ├── BudgetAllocator: Allocate tokens
│ └── StrategyExecutor: Execute strategies
├── Plan Step Execution
│ └── Context Refresh (optional)
│ └── Incremental context updates
├── Subplan Creation
│ └── Context Inheritance
│ ├── Inherit parent context
│ └── Specialize for subplan focus
└── Plan Completion
└── Context Archive
└── Store for historical reference
```
### Context Request from Plan
Plans generate context requests based on their focus.
### Context Injection into Plan
Assembled context is injected into plan execution.
### Subplan Context Inheritance
Child plans inherit and specialize context.
## Real-world Examples
### Example 1: Implementing a New Feature
**Scenario**: Implement async I/O support in a Python project.
**Context Assembly**:
```python
# 1. Create context request
request = ContextRequest(
focus_nodes=[
"project://myapp/src/main.py",
"project://myapp/src/io.py"
],
keywords=["async", "io", "performance"],
breadth=2,
depth=3,
resource_types=["source_file", "decision"],
detail_level=4
)
# 2. Assemble context
payload = pipeline.assemble(
plan_id="plan-async-io",
fragments=fetch_fragments(backends, request),
budget=ContextBudget(max_tokens=4096, reserved_tokens=512),
strategy="arce" # Use comprehensive strategy
)
# 3. Inspect assembled context
print(f"Fragments: {len(payload.fragments)}")
print(f"Tokens: {payload.total_tokens} / {payload.budget.available_tokens}")
print(f"Budget used: {payload.budget_used:.1%}")
print(f"Strategies: {payload.strategies_used}")
# 4. Use context in plan execution
plan = Plan(
title="Implement async I/O",
context_payload=payload,
context_hash=payload.context_hash
)
```
**Result**: LM receives focused context on async I/O patterns, related decisions, and performance considerations.
### Example 2: Debugging a Performance Issue
**Scenario**: Investigate slow database queries.
**Context Assembly**:
```python
# 1. Create context request focused on database code
request = ContextRequest(
focus_nodes=["project://myapp/src/database.py"],
keywords=["query", "performance", "slow", "index"],
breadth=3, # Broader search for related code
depth=2, # Less deep (focus on structure)
resource_types=["source_file", "decision", "test"],
detail_level=2, # Less detail to fit more context
temporal_window="30d" # Include recent changes
)
# 2. Assemble with temporal strategy
payload = pipeline.assemble(
plan_id="plan-debug-perf",
fragments=fetch_fragments(backends, request),
budget=ContextBudget(max_tokens=4096, reserved_tokens=512),
strategy="temporal-archaeology" # Include historical context
)
# 3. Analyze context
hot_fragments = [f for f in payload.fragments if f.tier == "hot"]
recent_fragments = sorted(
payload.fragments,
key=lambda f: f.created_at,
reverse=True
)[:5]
print(f"Hot tier fragments: {len(hot_fragments)}")
print(f"Recent changes: {[f.uko_node for f in recent_fragments]}")
```
**Result**: LM receives context on database code, recent changes, and historical decisions about performance.
### Example 3: Code Review with Context
**Scenario**: Review a pull request with full context.
**Context Assembly**:
```python
# 1. Get changed files from PR
changed_files = [
"project://myapp/src/auth.py",
"project://myapp/src/security.py",
"project://myapp/tests/test_auth.py"
]
# 2. Create context request
request = ContextRequest(
focus_nodes=changed_files,
breadth=1, # Narrow search (focus on changed files)
depth=4, # Deeper (understand full context)
resource_types=["source_file", "decision", "test"],
detail_level=5, # Higher detail for review
strategy_hints=["breadth-depth-navigator"] # Use graph traversal
)
# 3. Assemble context
payload = pipeline.assemble(
plan_id="plan-review-pr",
fragments=fetch_fragments(backends, request),
budget=ContextBudget(max_tokens=8192, reserved_tokens=512),
strategy="breadth-depth-navigator"
)
# 4. Generate review context
review_context = {
"changed_files": changed_files,
"context_fragments": len(payload.fragments),
"context_tokens": payload.total_tokens,
"related_decisions": [
f for f in payload.fragments
if "decision" in f.metadata.get("resource_type", "")
],
"related_tests": [
f for f in payload.fragments
if "test" in f.uko_node
]
}
```
**Result**: LM receives comprehensive context on changed files, related code, decisions, and tests for thorough review.
### Example 4: Multi-level Plan Hierarchy
**Scenario**: Execute a complex plan with multiple subplans.
**Context Assembly**:
```python
# Parent plan context
parent_request = ContextRequest(
focus_nodes=["project://myapp"],
breadth=2,
depth=2,
detail_level=2
)
parent_payload = pipeline.assemble(
plan_id="plan-refactor",
fragments=fetch_fragments(backends, parent_request),
budget=ContextBudget(max_tokens=4096, reserved_tokens=512),
strategy="arce"
)
# Subplan 1: Refactor auth module
subplan1_request = ContextRequest(
focus_nodes=["project://myapp/src/auth.py"],
parent_context=parent_payload,
inherit_strategy="skeleton", # Compress parent
breadth=2,
depth=3,
detail_level=4
)
subplan1_payload = pipeline.assemble(
plan_id="plan-refactor-auth",
fragments=fetch_fragments(backends, subplan1_request),
budget=ContextBudget(max_tokens=3584, reserved_tokens=512),
strategy="breadth-depth-navigator"
)
# Subplan 2: Refactor database module
subplan2_request = ContextRequest(
focus_nodes=["project://myapp/src/database.py"],
parent_context=parent_payload,
inherit_strategy="skeleton",
breadth=2,
depth=3,
detail_level=4
)
subplan2_payload = pipeline.assemble(
plan_id="plan-refactor-db",
fragments=fetch_fragments(backends, subplan2_request),
budget=ContextBudget(max_tokens=3584, reserved_tokens=512),
strategy="breadth-depth-navigator"
)
# Execute hierarchy
execute_plan(parent_plan, parent_payload)
execute_plan(subplan1, subplan1_payload)
execute_plan(subplan2, subplan2_payload)
```
**Result**: Parent plan gets broad context; subplans get specialized context with parent context compressed for efficiency.
## Conclusion
The ACMS provides a powerful, flexible system for managing context in cleveragents-core. By understanding the architecture, strategies, and best practices, you can:
- **Assemble relevant context** efficiently and deterministically
- **Respect token budgets** while maximizing information density
- **Implement custom strategies** for specialized use cases
- **Optimize performance** through careful budget allocation
- **Integrate seamlessly** with plan execution
For more information, see:
- [Context Strategies Reference](../reference/context_strategies_reference.md)
- [ACMS Pipeline Reference](../reference/acms_pipeline_reference.md)
- [ACMS v1 Context Assembly Pipeline](../reference/acms.md)
- [Context Strategy Registry](../reference/context_strategies.md)
+760
View File
@@ -0,0 +1,760 @@
# ACMS Pipeline Reference
Complete reference for the ACMS Pipeline components, their interactions, data flow, configuration, and monitoring.
## Pipeline Architecture
The ACMS pipeline implements a 10-component architecture across three phases:
```
Phase 1: Strategy Orchestration
├── StrategySelector
├── BudgetAllocator
└── StrategyExecutor
Phase 2: Fragment Fusion
├── FragmentDeduplicator
├── DetailDepthResolver
├── FragmentScorer
├── BudgetPacker
└── FragmentOrderer
Phase 3: Context Finalization
├── PreambleGenerator
└── SkeletonCompressor
```
## Component Reference
### Phase 1: Strategy Orchestration
#### StrategySelector
**Purpose**: Determine which strategies can handle the request.
**Input**:
- `strategies`: List of registered strategies
- `request`: ContextRequest with focus nodes, keywords, etc.
- `backends`: Available backends (TextBackend, VectorBackend, etc.)
**Output**:
- `candidates`: List of (strategy, confidence) tuples, sorted by confidence descending
**Process**:
1. Poll each strategy with `can_handle(request, backends)`
2. Collect confidence scores (0.0-1.0)
3. Filter strategies with confidence >= threshold (default 0.0)
4. Sort by confidence descending
5. Return top candidates
**Configuration**:
```toml
[context.strategies]
selector_threshold = 0.0
max_candidates = 10
```
**Example**:
```python
selector = StrategySelector()
candidates = selector.select(
strategies=[simple_keyword, semantic_embedding, breadth_depth],
request=context_request,
backends=backend_set
)
# Returns: [(semantic_embedding, 0.95), (breadth_depth, 0.85), ...]
```
#### BudgetAllocator
**Purpose**: Distribute token budget among selected strategies.
**Input**:
- `candidates`: List of (strategy, confidence) tuples
- `total_budget`: Available tokens for context
- `request`: ContextRequest with budget hints
**Output**:
- `allocations`: Dict mapping strategy -> allocated tokens
**Allocation Strategies**:
1. **Proportional** (default):
```
allocation[s] = total_budget * confidence[s] / sum(confidences)
```
2. **Equal**:
```
allocation[s] = total_budget / len(candidates)
```
3. **Priority**:
```
Sort by confidence, allocate 50% of remaining to each
```
**Configuration**:
```toml
[context.strategies]
allocator_strategy = "proportional"
allocator_min_budget = 100
```
**Example**:
```python
allocator = BudgetAllocator()
allocations = allocator.allocate(
candidates=[(semantic_embedding, 0.95), (breadth_depth, 0.85)],
total_budget=3584,
request=context_request
)
# Returns: {semantic_embedding: 1900, breadth_depth: 1684}
```
#### StrategyExecutor
**Purpose**: Execute strategies in parallel with timeout and circuit breaker.
**Input**:
- `allocations`: Dict mapping strategy -> allocated tokens
- `request`: ContextRequest
- `backends`: Available backends
- `plan_context`: Current plan context
**Output**:
- `results`: List of StrategyResult objects
**Features**:
- Parallel execution with configurable workers
- Per-strategy timeout (default 30s)
- Circuit breaker after N failures
- Graceful degradation on timeout
**Configuration**:
```toml
[context.strategies]
executor_max_workers = 4
executor_timeout_seconds = 30
executor_circuit_breaker_threshold = 3
executor_circuit_breaker_reset_timeout = 60
```
**Example**:
```python
executor = StrategyExecutor(max_workers=4, timeout_seconds=30)
results = executor.execute(
allocations={semantic_embedding: 1900, breadth_depth: 1684},
request=context_request,
backends=backend_set,
plan_context=plan_context
)
# Returns: [StrategyResult(...), StrategyResult(...)]
```
**StrategyResult**:
```python
@dataclass
class StrategyResult:
strategy_name: str
fragments: tuple[ContextFragment, ...]
total_fragments: int
tokens_used: int
execution_time_ms: float
errors: tuple[str, ...]
stats: dict[str, Any]
```
### Phase 2: Fragment Fusion
#### FragmentDeduplicator
**Purpose**: Remove duplicate fragments based on UKO node and content hash.
**Input**:
- `fragments`: List of ContextFragment objects
- `strategy`: Deduplication strategy ("highest_relevance", "first_seen", "most_recent")
**Output**:
- `deduplicated`: List of unique fragments
**Deduplication Rules**:
- Same `uko_node` + same content hash = duplicate
- Keep fragment with highest relevance score (default)
- Merge provenance from all duplicates
**Configuration**:
```toml
[context.strategies]
deduplicator_strategy = "highest_relevance"
deduplicator_merge_provenance = true
```
**Example**:
```python
deduplicator = FragmentDeduplicator()
deduplicated = deduplicator.deduplicate(
fragments=[frag1, frag2, frag1_duplicate],
strategy="highest_relevance"
)
# Returns: [frag1 (with merged provenance), frag2]
```
#### DetailDepthResolver
**Purpose**: Resolve appropriate detail depth for each fragment.
**Input**:
- `fragments`: List of ContextFragment objects
- `budget`: ContextBudget
- `request`: ContextRequest with desired detail level
**Output**:
- `resolved`: List of fragments with adjusted detail_depth
**Resolution Logic**:
1. Start with requested detail level
2. Adjust based on token budget
3. Respect fragment's max depth
4. Prefer higher depth for hot tier
**Configuration**:
```toml
[context.strategies]
resolver_default_depth = 3
resolver_hot_tier_boost = 2
resolver_budget_aware = true
```
**Example**:
```python
resolver = DetailDepthResolver()
resolved = resolver.resolve(
fragments=[frag1, frag2],
budget=budget,
request=context_request
)
# Returns: fragments with adjusted detail_depth
```
#### FragmentScorer
**Purpose**: Score fragments based on multiple factors.
**Input**:
- `fragments`: List of ContextFragment objects
- `plan_context`: Current plan context
- `request`: ContextRequest
**Output**:
- `scored`: List of (fragment, score) tuples
**Scoring Factors**:
- Relevance score (from strategy): 40%
- Tier priority (hot > warm > cold): 30%
- Recency (created_at): 20%
- Plan context alignment: 10%
**Configuration**:
```toml
[context.strategies]
scorer_relevance_weight = 0.4
scorer_tier_weight = 0.3
scorer_recency_weight = 0.2
scorer_plan_weight = 0.1
```
**Example**:
```python
scorer = FragmentScorer()
scored = scorer.score(
fragments=[frag1, frag2],
plan_context=plan_context,
request=context_request
)
# Returns: [(frag1, 0.92), (frag2, 0.78)]
```
#### BudgetPacker
**Purpose**: Select fragments to fit within token budget.
**Input**:
- `scored_fragments`: List of (fragment, score) tuples
- `budget`: ContextBudget
**Output**:
- `packed`: List of selected fragments
**Packing Algorithm**:
1. Sort fragments by score (descending)
2. Respect tier ordering (hot → warm → cold)
3. Greedily add fragments until budget exhausted
4. Early termination when budget full
**Configuration**:
```toml
[context.strategies]
packer_tier_priority = ["hot", "warm", "cold"]
packer_greedy = true
```
**Example**:
```python
packer = BudgetPacker()
packed = packer.pack(
scored_fragments=[(frag1, 0.92), (frag2, 0.78)],
budget=budget
)
# Returns: [frag1, frag2] (total tokens <= budget.available_tokens)
```
#### FragmentOrderer
**Purpose**: Determine final fragment order in payload.
**Input**:
- `fragments`: List of ContextFragment objects
- `strategy`: Ordering strategy ("relevance", "recency", "tier-relevance", "custom")
**Output**:
- `ordered`: List of fragments in final order
**Ordering Strategies**:
- **Relevance**: By relevance_score (descending)
- **Recency**: By created_at (descending)
- **Tier-Relevance**: By tier (hot > warm > cold), then relevance
- **Custom**: User-defined ordering
**Configuration**:
```toml
[context.strategies]
orderer_strategy = "tier-relevance"
```
**Example**:
```python
orderer = FragmentOrderer()
ordered = orderer.order(
fragments=[frag1, frag2],
strategy="tier-relevance"
)
# Returns: [hot_frag, warm_frag1, warm_frag2, cold_frag]
```
### Phase 3: Context Finalization
#### PreambleGenerator
**Purpose**: Generate optional summary of assembled context.
**Input**:
- `payload`: ContextPayload (before finalization)
- `strategies_used`: Tuple of strategy names
- `budget_used`: Fraction of budget consumed
**Output**:
- `preamble`: String summary of context
**Preamble Contents**:
- Number of fragments and sources
- Token usage and budget utilization
- Strategies used
- Key topics covered
- Temporal range
**Configuration**:
```toml
[context.strategies]
preamble_enabled = true
preamble_include_stats = true
preamble_max_length = 500
```
**Example**:
```python
generator = PreambleGenerator()
preamble = generator.generate(
payload=payload,
strategies_used=["semantic-embedding"],
budget_used=0.57
)
# Returns: "Context includes 5 fragments from 3 sources..."
```
#### SkeletonCompressor
**Purpose**: Compress context for child plans while preserving critical information.
**Input**:
- `parent_context`: Parent ContextPayload
- `child_focus`: List of focus nodes for child plan
- `skeleton_budget`: Token budget for compressed context
**Output**:
- `compressed`: Compressed ContextPayload for child plan
**Compression Strategy**:
1. Identify critical fragments for child focus
2. Remove redundant details
3. Preserve decision context
4. Maintain provenance
**Configuration**:
```toml
[context.strategies]
compressor_enabled = true
compressor_preserve_decisions = true
compressor_preserve_hot_tier = true
```
**Example**:
```python
compressor = SkeletonCompressor()
compressed = compressor.compress(
parent_context=parent_payload,
child_focus=["project://myapp/src/main.py"],
skeleton_budget=1024
)
# Returns: compressed ContextPayload for child plan
```
## Data Flow
### Complete Pipeline Flow
```
ContextRequest
StrategySelector
↓ (candidates: [(strategy, confidence), ...])
BudgetAllocator
↓ (allocations: {strategy: budget, ...})
StrategyExecutor
↓ (results: [StrategyResult, ...])
Merge Results
↓ (fragments: [ContextFragment, ...])
FragmentDeduplicator
↓ (deduplicated: [ContextFragment, ...])
DetailDepthResolver
↓ (resolved: [ContextFragment, ...])
FragmentScorer
↓ (scored: [(ContextFragment, score), ...])
BudgetPacker
↓ (packed: [ContextFragment, ...])
FragmentOrderer
↓ (ordered: [ContextFragment, ...])
PreambleGenerator
↓ (preamble: str)
SkeletonCompressor (optional)
↓ (compressed: ContextPayload)
ContextPayload
```
### Example Data Structures
**ContextRequest**:
```python
{
"focus_nodes": ["project://myapp/src/main.py"],
"breadth": 2,
"depth": 3,
"keywords": ["async", "io"],
"resource_types": ["source_file", "decision"],
"detail_level": 3,
"temporal_window": "7d",
"strategy_hints": ["semantic-embedding"]
}
```
**StrategyResult**:
```python
{
"strategy_name": "semantic-embedding",
"fragments": [
ContextFragment(...),
ContextFragment(...),
...
],
"total_fragments": 45,
"tokens_used": 2048,
"execution_time_ms": 234,
"errors": [],
"stats": {
"similarity_scores": [0.95, 0.87, ...],
"backend_queries": 3,
"cache_hits": 2
}
}
```
**ContextPayload**:
```python
{
"payload_id": "01ARZ3NDEKTSV4RRFFQ69G5FAV",
"plan_id": "plan-1",
"fragments": [
ContextFragment(...),
ContextFragment(...),
...
],
"total_tokens": 2048,
"budget": {
"max_tokens": 4096,
"reserved_tokens": 512,
"available_tokens": 3584
},
"budget_used": 0.57,
"strategies_used": ["semantic-embedding"],
"context_hash": "abc123...",
"preamble": "Context includes 5 fragments...",
"provenance_map": {
"fragment-id-1": {...},
"fragment-id-2": {...},
...
},
"assembled_at": "2024-04-19T14:30:00Z"
}
```
## Configuration and Tuning
### Global Configuration
```toml
[context]
max_tokens = 4096
reserved_tokens = 512
[context.strategies]
enabled = ["simple-keyword", "semantic-embedding", "breadth-depth-navigator"]
default_strategy = "arce"
[context.strategies.simple-keyword]
enabled = true
timeout_seconds = 10
max_fragments = 50
[context.strategies.semantic-embedding]
enabled = true
timeout_seconds = 20
max_fragments = 100
[context.strategies.breadth-depth-navigator]
enabled = true
timeout_seconds = 30
max_fragments = 150
[context.strategies.arce]
enabled = true
timeout_seconds = 45
max_fragments = 200
text_budget_fraction = 0.4
vector_budget_fraction = 0.4
graph_budget_fraction = 0.2
```
### Per-Component Tuning
#### StrategySelector Tuning
```toml
[context.strategies]
selector_threshold = 0.0 # Minimum confidence to select
max_candidates = 10 # Maximum strategies to execute
```
**Tuning Tips**:
- Increase `selector_threshold` to filter low-confidence strategies
- Decrease `max_candidates` to reduce execution time
#### BudgetAllocator Tuning
```toml
[context.strategies]
allocator_strategy = "proportional" # "proportional", "equal", "priority"
allocator_min_budget = 100
```
**Tuning Tips**:
- Use "proportional" for quality-aware allocation
- Use "equal" for fair distribution
- Use "priority" for greedy allocation
#### StrategyExecutor Tuning
```toml
[context.strategies]
executor_max_workers = 4
executor_timeout_seconds = 30
executor_circuit_breaker_threshold = 3
executor_circuit_breaker_reset_timeout = 60
```
**Tuning Tips**:
- Increase `max_workers` for parallel execution (up to CPU cores)
- Increase `timeout_seconds` for complex strategies
- Adjust `circuit_breaker_threshold` for fault tolerance
#### FragmentScorer Tuning
```toml
[context.strategies]
scorer_relevance_weight = 0.4
scorer_tier_weight = 0.3
scorer_recency_weight = 0.2
scorer_plan_weight = 0.1
```
**Tuning Tips**:
- Increase `relevance_weight` for quality-focused assembly
- Increase `tier_weight` for tier-aware assembly
- Increase `recency_weight` for recent-focused assembly
## Monitoring and Metrics
### Key Metrics
| Metric | Description | Target |
|--------|-------------|--------|
| `assembly_time_ms` | Total pipeline execution time | < 1000ms |
| `fragments_selected` | Number of fragments in payload | 5-50 |
| `tokens_used` | Total tokens in payload | < available_tokens |
| `budget_used` | Fraction of budget consumed | 0.5-0.9 |
| `avg_relevance` | Average relevance score | > 0.7 |
| `strategy_success_rate` | Fraction of successful strategies | > 0.8 |
| `dedup_ratio` | Fraction of duplicates removed | 0.0-0.3 |
### Logging
Enable debug logging:
```python
import logging
logging.getLogger("cleveragents.acms").setLevel(logging.DEBUG)
```
### Metrics Collection
```python
from cleveragents.application.services.acms_service import ACMSPipeline
pipeline = ACMSPipeline()
payload = pipeline.assemble(plan_id, fragments, budget)
# Collect metrics
metrics = {
"payload_id": payload.payload_id,
"plan_id": payload.plan_id,
"total_tokens": payload.total_tokens,
"budget_used": payload.budget_used,
"fragments_count": len(payload.fragments),
"strategies_used": payload.strategies_used,
"avg_relevance": sum(f.relevance_score for f in payload.fragments) / len(payload.fragments),
"tier_distribution": {
"hot": sum(1 for f in payload.fragments if f.tier == "hot"),
"warm": sum(1 for f in payload.fragments if f.tier == "warm"),
"cold": sum(1 for f in payload.fragments if f.tier == "cold"),
}
}
```
## Performance Optimization
### Strategy Selection Optimization
```python
# Good: Filter low-confidence strategies
candidates = [
(s, conf) for s, conf in candidates if conf >= 0.7
]
# Avoid: Execute all strategies
candidates = all_strategies
```
### Budget Allocation Optimization
```python
# Good: Proportional allocation
allocations = allocator.allocate(
candidates=high_confidence,
total_budget=3584,
strategy="proportional"
)
# Avoid: Equal allocation
allocations = {s: 3584 // len(strategies) for s in strategies}
```
### Execution Optimization
```python
# Good: Parallel execution with reasonable timeout
executor = StrategyExecutor(max_workers=4, timeout_seconds=30)
# Avoid: Sequential execution or excessive timeout
executor = StrategyExecutor(max_workers=1, timeout_seconds=300)
```
### Fragment Deduplication Optimization
```python
# Good: Deduplicate early
deduplicated = deduplicator.deduplicate(fragments)
scored = scorer.score(deduplicated, plan_context)
# Avoid: Deduplicate after scoring
scored = scorer.score(fragments, plan_context)
deduplicated = deduplicator.deduplicate(scored)
```
## Troubleshooting
### Slow Pipeline Execution
**Problem**: Pipeline takes > 1 second.
**Solution**:
1. Check `executor_timeout_seconds` - reduce if too high
2. Check `executor_max_workers` - increase if too low
3. Check strategy complexity - use simpler strategies
4. Check fragment count - reduce `max_fragments`
### Low Quality Context
**Problem**: Assembled context is not relevant.
**Solution**:
1. Increase `selector_threshold` to filter low-confidence strategies
2. Use higher-quality strategy (e.g., ARCE)
3. Increase `scorer_relevance_weight` for quality focus
4. Adjust strategy-specific parameters
### Out of Memory
**Problem**: Pipeline consumes too much memory.
**Solution**:
1. Reduce `max_fragments` limit
2. Reduce `executor_max_workers` for parallel execution
3. Enable fragment streaming
4. Use simpler strategies
### Budget Exceeded
**Problem**: Assembled context exceeds budget.
**Solution**:
1. Check `BudgetPacker` implementation
2. Reduce `max_fragments` limit
3. Increase `reserved_tokens` to reduce available budget
4. Enable detail depth reduction
## See Also
- [Context Management Deep Dive](../guides/context_management_deep_dive.md)
- [Context Strategies Reference](./context_strategies_reference.md)
- [ACMS v1 Context Assembly Pipeline](./acms.md)
- [Context Strategy Registry](./context_strategies.md)
@@ -0,0 +1,750 @@
# Context Strategies Reference
Complete reference for all context strategies in the ACMS, including configuration options, performance characteristics, and use case recommendations.
## Strategy Overview
| Strategy | Quality | Speed | Coverage | Backends | Best For |
|----------|---------|-------|----------|----------|----------|
| Simple Keyword | 0.3 | ⚡⚡⚡ | Narrow | Text | Quick searches, fallback |
| Semantic Embedding | 0.6 | ⚡⚡ | Broad | Vector | Semantic similarity |
| Breadth-Depth Navigator | 0.85 | ⚡ | Very Broad | Graph | Comprehensive context |
| ARCE | 0.95 | 🐢 | Comprehensive | All | Production use |
| Temporal Archaeology | 0.5 | 🐢 | Historical | Graph + Temporal | Historical context |
| Plan-Decision Context | 0.7 | ⚡ | Hierarchical | Temporal + Graph | Plan coordination |
## Simple Keyword Strategy
### Overview
The Simple Keyword Strategy performs text-based keyword search using the TextBackend. It's the fastest strategy and serves as a fallback when other backends are unavailable.
### Quality Score
**0.3** (baseline)
### Backend Requirements
- **Required**: TextBackend
- **Optional**: None
### How It Works
1. Extract keywords from the ContextRequest
2. Query TextBackend using keyword search
3. Rank results by match frequency and relevance
4. Pack greedily into token budget
### Configuration
```toml
[context.strategies.simple-keyword]
enabled = true
timeout_seconds = 10
max_fragments = 50
keyword_boost_factor = 1.5
```
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `enabled` | bool | true | Enable this strategy |
| `timeout_seconds` | int | 10 | Per-strategy timeout |
| `max_fragments` | int | 50 | Maximum fragments returned |
| `keyword_boost_factor` | float | 1.5 | Boost for exact keyword matches |
### Performance Characteristics
- **Execution Time**: 50-200ms (typical)
- **Memory Usage**: Low (streaming results)
- **Scalability**: Excellent (linear with keyword count)
- **Parallelization**: Not applicable (single-threaded)
### Use Cases
- Quick searches with clear keywords
- Projects with good text indexing
- Fallback when other backends unavailable
- Real-time context assembly with tight latency budgets
### Example Usage
```python
from cleveragents.application.services.acms_service import ACMSPipeline
pipeline = ACMSPipeline()
payload = pipeline.assemble(
plan_id="plan-1",
fragments=fragments,
budget=ContextBudget(max_tokens=4096, reserved_tokens=512),
strategy="simple-keyword"
)
```
### Limitations
- Limited to exact keyword matches
- No semantic understanding
- Cannot traverse relationships
- Requires good text indexing
## Semantic Embedding Strategy
### Overview
The Semantic Embedding Strategy uses vector similarity search to find semantically related context. It uses character-frequency embeddings as a v1 approximation.
### Quality Score
**0.6** (good)
### Backend Requirements
- **Required**: VectorBackend
- **Optional**: None
### How It Works
1. Embed the ContextRequest using character-frequency model
2. Query VectorBackend for similar embeddings
3. Rank by cosine similarity
4. Pack budget-aware results
### Configuration
```toml
[context.strategies.semantic-embedding]
enabled = true
timeout_seconds = 20
max_fragments = 100
similarity_threshold = 0.5
embedding_model = "character-frequency-v1"
```
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `enabled` | bool | true | Enable this strategy |
| `timeout_seconds` | int | 20 | Per-strategy timeout |
| `max_fragments` | int | 100 | Maximum fragments returned |
| `similarity_threshold` | float | 0.5 | Minimum similarity score (0.0-1.0) |
| `embedding_model` | str | "character-frequency-v1" | Embedding model to use |
### Performance Characteristics
- **Execution Time**: 100-500ms (typical)
- **Memory Usage**: Medium (embedding cache)
- **Scalability**: Good (logarithmic with vector index size)
- **Parallelization**: Supported (multiple similarity searches)
### Use Cases
- Semantic similarity searches
- Conceptual relationships
- Cross-domain context discovery
- Finding related code patterns
### Example Usage
```python
from cleveragents.domain.models.core.context_fragment import ContextRequest
request = ContextRequest(
focus_nodes=["project://myapp/src/main.py"],
keywords=["async", "io"]
)
payload = pipeline.assemble(
plan_id="plan-1",
fragments=fragments,
budget=ContextBudget(max_tokens=4096, reserved_tokens=512),
strategy="semantic-embedding"
)
```
### Limitations
- v1 uses character-frequency embeddings (not true semantic)
- Requires VectorBackend
- Cannot traverse relationships
- Sensitive to embedding quality
### Future Improvements
- Integration with real embedding models (e.g., OpenAI, Anthropic)
- Fine-tuned embeddings for code
- Hybrid semantic-keyword search
## Breadth-Depth Navigator Strategy
### Overview
The Breadth-Depth Navigator Strategy traverses the knowledge graph to find related context. It explores outward (breadth) and downward (depth) from focus nodes.
### Quality Score
**0.85** (excellent)
### Backend Requirements
- **Required**: GraphBackend
- **Optional**: None
### How It Works
1. Start from focus nodes in ContextRequest
2. Traverse graph outward by breadth hops
3. Traverse downward by depth hops
4. Collect all reachable nodes
5. Rank by distance and relevance
6. Pack into budget
### Configuration
```toml
[context.strategies.breadth-depth-navigator]
enabled = true
timeout_seconds = 30
max_fragments = 150
default_breadth = 2
default_depth = 3
distance_decay = 0.8
```
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `enabled` | bool | true | Enable this strategy |
| `timeout_seconds` | int | 30 | Per-strategy timeout |
| `max_fragments` | int | 150 | Maximum fragments returned |
| `default_breadth` | int | 2 | Default breadth hops |
| `default_depth` | int | 3 | Default depth hops |
| `distance_decay` | float | 0.8 | Decay factor per hop |
### Performance Characteristics
- **Execution Time**: 200-1000ms (typical)
- **Memory Usage**: Medium (graph traversal state)
- **Scalability**: Good (exponential with breadth/depth)
- **Parallelization**: Supported (parallel hop traversal)
### Use Cases
- Exploring related code and decisions
- Understanding dependencies
- Comprehensive context assembly
- Finding transitive relationships
### Example Usage
```python
request = ContextRequest(
focus_nodes=["project://myapp/src/main.py"],
breadth=2,
depth=3
)
payload = pipeline.assemble(
plan_id="plan-1",
fragments=fragments,
budget=ContextBudget(max_tokens=4096, reserved_tokens=512),
strategy="breadth-depth-navigator"
)
```
### Limitations
- Requires GraphBackend
- Can be slow with large graphs
- Exponential complexity with breadth/depth
- May include irrelevant distant nodes
### Optimization Tips
- Reduce breadth/depth for faster execution
- Use distance decay to prioritize nearby nodes
- Filter by resource type to reduce graph size
- Increase timeout for large graphs
## ARCE Strategy (Advanced Retrieval with Context Enrichment)
### Overview
ARCE is the most comprehensive strategy, combining text search, vector similarity, and graph traversal. It's the recommended strategy for production use cases.
### Quality Score
**0.95** (highest)
### Backend Requirements
- **Required**: TextBackend, VectorBackend, GraphBackend
- **Optional**: None
### How It Works
1. **Phase 1 (40% budget)**: Text search using keywords
2. **Phase 2 (40% budget)**: Vector similarity search
3. **Phase 3 (20% budget)**: Graph traversal from results
4. Merge and deduplicate results
5. Rank by combined score
6. Pack into budget
### Configuration
```toml
[context.strategies.arce]
enabled = true
timeout_seconds = 45
max_fragments = 200
text_budget_fraction = 0.4
vector_budget_fraction = 0.4
graph_budget_fraction = 0.2
dedup_strategy = "highest_relevance"
```
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `enabled` | bool | true | Enable this strategy |
| `timeout_seconds` | int | 45 | Per-strategy timeout |
| `max_fragments` | int | 200 | Maximum fragments returned |
| `text_budget_fraction` | float | 0.4 | Budget for text search |
| `vector_budget_fraction` | float | 0.4 | Budget for vector search |
| `graph_budget_fraction` | float | 0.2 | Budget for graph traversal |
| `dedup_strategy` | str | "highest_relevance" | Deduplication strategy |
### Performance Characteristics
- **Execution Time**: 500-2000ms (typical)
- **Memory Usage**: High (multiple result sets)
- **Scalability**: Good (parallel phases)
- **Parallelization**: Excellent (3 phases in parallel)
### Use Cases
- Comprehensive context assembly
- Multi-modal queries
- Production use cases
- High-quality context requirements
### Example Usage
```python
request = ContextRequest(
focus_nodes=["project://myapp/src/main.py"],
keywords=["async", "io"],
breadth=2
)
payload = pipeline.assemble(
plan_id="plan-1",
fragments=fragments,
budget=ContextBudget(max_tokens=4096, reserved_tokens=512),
strategy="arce"
)
```
### Limitations
- Requires all three backends
- Slower than single-backend strategies
- Higher memory usage
- May return too many fragments
### Optimization Tips
- Adjust budget fractions based on query type
- Increase timeout for complex queries
- Use deduplication to reduce redundancy
- Filter by resource type to reduce results
## Temporal Archaeology Strategy
### Overview
The Temporal Archaeology Strategy retrieves historical context from the cold tier and traverses relationships from historical nodes.
### Quality Score
**0.5** (moderate)
### Backend Requirements
- **Required**: GraphBackend, TemporalBackend (cold tier)
- **Optional**: None
### How It Works
1. Query TemporalBackend for historical nodes
2. Filter by temporal window
3. Traverse graph from historical nodes
4. Collect related current nodes
5. Rank by recency and relevance
6. Pack into budget
### Configuration
```toml
[context.strategies.temporal-archaeology]
enabled = false
timeout_seconds = 60
max_fragments = 100
default_temporal_window = "7d"
include_current_context = true
```
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `enabled` | bool | false | Enable this strategy |
| `timeout_seconds` | int | 60 | Per-strategy timeout |
| `max_fragments` | int | 100 | Maximum fragments returned |
| `default_temporal_window` | str | "7d" | Default time window |
| `include_current_context` | bool | true | Include current context |
### Performance Characteristics
- **Execution Time**: 1000-5000ms (typical)
- **Memory Usage**: High (temporal queries)
- **Scalability**: Fair (depends on cold tier size)
- **Parallelization**: Limited (sequential phases)
### Use Cases
- Understanding historical context
- Decision archaeology
- Long-term project evolution
- Regression analysis
### Example Usage
```python
request = ContextRequest(
focus_nodes=["project://myapp/src/main.py"],
temporal_window="30d"
)
payload = pipeline.assemble(
plan_id="plan-1",
fragments=fragments,
budget=ContextBudget(max_tokens=4096, reserved_tokens=512),
strategy="temporal-archaeology"
)
```
### Limitations
- Requires cold tier (not always available)
- Slow execution
- May include outdated information
- Limited to historical data
### Temporal Window Format
- `"1d"` - Last 1 day
- `"7d"` - Last 7 days
- `"30d"` - Last 30 days
- `"90d"` - Last 90 days
- `"1y"` - Last 1 year
- `"all"` - All history
## Plan-Decision Context Strategy
### Overview
The Plan-Decision Context Strategy walks the plan hierarchy to retrieve decision records and related context.
### Quality Score
**0.7** (good)
### Backend Requirements
- **Required**: TemporalBackend (warm/cold), GraphBackend
- **Optional**: None
### How It Works
1. Walk parent and ancestor plan hierarchy
2. Retrieve decision records from warm/cold tiers
3. Traverse graph from decisions
4. Collect related context
5. Rank by plan proximity and relevance
6. Pack into budget
### Configuration
```toml
[context.strategies.plan-decision-context]
enabled = true
timeout_seconds = 30
max_fragments = 100
include_ancestor_decisions = true
max_ancestor_depth = 5
```
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `enabled` | bool | true | Enable this strategy |
| `timeout_seconds` | int | 30 | Per-strategy timeout |
| `max_fragments` | int | 100 | Maximum fragments returned |
| `include_ancestor_decisions` | bool | true | Include ancestor decisions |
| `max_ancestor_depth` | int | 5 | Maximum ancestor levels |
### Performance Characteristics
- **Execution Time**: 200-800ms (typical)
- **Memory Usage**: Medium (plan hierarchy)
- **Scalability**: Good (linear with plan depth)
- **Parallelization**: Supported (parallel ancestor traversal)
### Use Cases
- Understanding plan context
- Decision inheritance
- Multi-level plan coordination
- Plan dependency analysis
### Example Usage
```python
request = ContextRequest(
focus_nodes=["project://myapp/src/main.py"]
)
payload = pipeline.assemble(
plan_id="plan-1",
fragments=fragments,
budget=ContextBudget(max_tokens=4096, reserved_tokens=512),
strategy="plan-decision-context"
)
```
### Limitations
- Requires plan hierarchy
- Limited to decision context
- May miss non-decision context
- Depends on plan structure
## Strategy Selection Guide
### When to Use Each Strategy
#### Simple Keyword
- ✅ Quick searches with clear keywords
- ✅ Fallback when other backends unavailable
- ✅ Real-time context with tight latency budgets
- ❌ Semantic understanding needed
- ❌ Relationship traversal needed
#### Semantic Embedding
- ✅ Semantic similarity searches
- ✅ Conceptual relationships
- ✅ Cross-domain discovery
- ❌ Exact keyword matching needed
- ❌ Relationship traversal needed
#### Breadth-Depth Navigator
- ✅ Exploring related code
- ✅ Understanding dependencies
- ✅ Comprehensive context
- ❌ Quick searches needed
- ❌ Historical context needed
#### ARCE
- ✅ Comprehensive context assembly
- ✅ Multi-modal queries
- ✅ Production use cases
- ❌ Quick searches needed
- ❌ Limited backends available
#### Temporal Archaeology
- ✅ Historical context
- ✅ Decision archaeology
- ✅ Long-term evolution
- ❌ Current context needed
- ❌ Quick searches needed
#### Plan-Decision Context
- ✅ Plan coordination
- ✅ Decision inheritance
- ✅ Multi-level plans
- ❌ Non-decision context needed
- ❌ No plan hierarchy
### Fallback Chain
When a strategy fails, the system falls back to simpler strategies:
```
ARCE (0.95)
↓ (if unavailable)
Breadth-Depth Navigator (0.85)
↓ (if unavailable)
Semantic Embedding (0.6)
↓ (if unavailable)
Simple Keyword (0.3)
```
## Performance Comparison
### Execution Time
```
Simple Keyword: ████ 50-200ms
Semantic Embedding: ██████████ 100-500ms
Breadth-Depth: ███████████████ 200-1000ms
Plan-Decision: ██████████ 200-800ms
ARCE: ████████████████████ 500-2000ms
Temporal Archaeology: ████████████████████████ 1000-5000ms
```
### Memory Usage
```
Simple Keyword: ██ Low
Semantic Embedding: ████ Medium
Breadth-Depth: ████ Medium
Plan-Decision: ████ Medium
ARCE: ██████ High
Temporal Archaeology: ██████ High
```
### Coverage
```
Simple Keyword: ██ Narrow
Temporal Archaeology: ███ Moderate
Semantic Embedding: ████ Broad
Plan-Decision: ████ Hierarchical
Breadth-Depth: █████ Very Broad
ARCE: ██████ Comprehensive
```
## Configuration Best Practices
### Global Configuration
```toml
[context.strategies]
enabled = ["simple-keyword", "semantic-embedding", "breadth-depth-navigator"]
default_strategy = "arce"
fallback_chain = ["arce", "breadth-depth-navigator", "semantic-embedding", "simple-keyword"]
[context.strategies.simple-keyword]
enabled = true
timeout_seconds = 10
max_fragments = 50
[context.strategies.semantic-embedding]
enabled = true
timeout_seconds = 20
max_fragments = 100
[context.strategies.breadth-depth-navigator]
enabled = true
timeout_seconds = 30
max_fragments = 150
[context.strategies.arce]
enabled = true
timeout_seconds = 45
max_fragments = 200
[context.strategies.plan-decision-context]
enabled = true
timeout_seconds = 30
max_fragments = 100
```
### Per-Project Overrides
```bash
# Set strategies for a project
agents project context set \
--strategy simple-keyword \
--strategy semantic-embedding \
--strategy breadth-depth-navigator
# View current strategies
agents project context list
```
## Monitoring and Metrics
### Key Metrics
- **Execution Time**: Strategy execution wall-clock time
- **Fragment Count**: Number of fragments returned
- **Token Usage**: Total tokens in assembled context
- **Budget Utilization**: Percentage of budget used
- **Relevance Score**: Average relevance of fragments
- **Tier Distribution**: Hot/warm/cold fragment counts
### Logging
Enable debug logging to monitor strategy execution:
```python
import logging
logging.getLogger("cleveragents.acms").setLevel(logging.DEBUG)
```
### Example Metrics Output
```
Strategy: semantic-embedding
Execution Time: 234ms
Fragments: 12
Tokens: 2048
Budget Used: 57%
Avg Relevance: 0.82
Tier Distribution: hot=2, warm=8, cold=2
```
## Troubleshooting
### Strategy Not Selected
**Problem**: Expected strategy not selected.
**Solution**:
1. Check strategy is enabled in configuration
2. Verify `can_handle()` returns confidence > 0
3. Check required backends are available
4. Review strategy selection logs
### Slow Execution
**Problem**: Strategy takes too long.
**Solution**:
1. Reduce `max_fragments` limit
2. Decrease `breadth` and `depth` parameters
3. Increase `timeout_seconds` if appropriate
4. Switch to faster strategy
### Low Quality Results
**Problem**: Assembled context is not relevant.
**Solution**:
1. Try higher-quality strategy (e.g., ARCE)
2. Adjust `similarity_threshold` for semantic strategy
3. Increase `breadth` and `depth` for navigator
4. Provide better keywords in request
### Out of Memory
**Problem**: Strategy consumes too much memory.
**Solution**:
1. Reduce `max_fragments` limit
2. Switch to lower-memory strategy
3. Reduce `breadth` and `depth` parameters
4. Enable fragment streaming
## See Also
- [Context Management Deep Dive](../guides/context_management_deep_dive.md)
- [ACMS Pipeline Reference](./acms_pipeline_reference.md)
- [ACMS v1 Context Assembly Pipeline](./acms.md)
- [Context Strategy Registry](./context_strategies.md)
+26 -5502
View File
File diff suppressed because one or more lines are too long
-82
View File
@@ -1,82 +0,0 @@
Feature: Pluggable Scope Chain Resolution
As a developer
I want to register and use custom scope resolvers
So that I can extend the scope chain resolution system with custom logic
Scenario: Register custom resolver via YAML configuration
Given I have a scope chain resolver registry
And I have a YAML configuration with a custom resolver definition
When I load the YAML configuration
Then the custom resolver should be registered in the registry
And the resolver should have the correct name and type
Scenario: Register custom resolver via Python entry point
Given I have a scope chain resolver registry
And I have a Python entry point for a custom resolver
When I load resolvers from entry points
Then the custom resolver should be registered in the registry
And the resolver should be accessible by its entry point name
Scenario: Invoke custom resolver during context assembly
Given I have a scope chain resolver registry
And I have registered a custom resolver
And I have a context request with scope parameters
When I assemble the context with the custom resolver
Then the custom resolver should be invoked
And the resolver should return the expected scope chain
Scenario: Handle resolver errors gracefully during context assembly
Given I have a scope chain resolver registry
And I have registered a faulty custom resolver that raises an error
And I have a context request with scope parameters
When I assemble the context with the faulty resolver
Then the context assembly should not fail
And the error should be logged
And the system should fall back to the default resolver
Scenario: Validate resolver configuration on registration
Given I have a scope chain resolver registry
And I have an invalid resolver configuration
When I try to register the invalid resolver
Then a validation error should be raised
And the resolver should not be registered
Scenario: Support multiple custom resolvers in chain
Given I have a scope chain resolver registry
And I have registered multiple custom resolvers
And I have a context request with scope parameters
When I assemble the context with all resolvers
Then all resolvers should be invoked in order
And the final scope chain should combine results from all resolvers
Scenario: Resolver priority and ordering
Given I have a scope chain resolver registry
And I have registered resolvers with different priorities
And I have a context request with scope parameters
When I assemble the context with prioritized resolvers
Then resolvers should be invoked in priority order
And higher priority resolvers should override lower priority results
Scenario: Resolver caching and performance
Given I have a scope chain resolver registry
And I have registered a custom resolver with caching
And I have a context request with scope parameters
When I assemble the context multiple times with the same parameters
Then the resolver should use cached results on subsequent calls
And the resolver should not be invoked multiple times for identical requests
Scenario: Resolver state isolation
Given I have a scope chain resolver registry
And I have registered a stateful custom resolver
And I have multiple concurrent context assembly requests
When I assemble contexts concurrently
Then each resolver instance should maintain isolated state
And resolver state should not leak between requests
Scenario: Resolver metadata and introspection
Given I have a scope chain resolver registry
And I have registered a custom resolver with metadata
When I query the resolver metadata
Then the metadata should include resolver name, version, and description
And the metadata should list supported scope parameters
And the metadata should indicate resolver capabilities
-473
View File
@@ -1,473 +0,0 @@
"""Step implementations for pluggable scope chain resolution integration tests."""
from __future__ import annotations
from typing import Any, Protocol
from unittest.mock import Mock
from behave import given, then, when
from behave.runner import Context
class ScopeResolver(Protocol):
"""Protocol for scope chain resolvers."""
def resolve(self, scope_params: dict[str, Any]) -> dict[str, Any]:
"""Resolve scope parameters to a scope chain."""
...
# ============================================================================
# Given Steps
# ============================================================================
@given("I have a scope chain resolver registry")
def step_create_resolver_registry(context: Context) -> None:
"""Create a scope chain resolver registry."""
context.resolver_registry: dict[str, Any] = {}
context.resolver_errors: list[str] = []
context.resolver_invocations: list[str] = []
@given("I have a YAML configuration with a custom resolver definition")
def step_create_yaml_config(context: Context) -> None:
"""Create a YAML configuration with a custom resolver."""
context.yaml_config = {
"resolvers": [
{
"name": "custom_resolver",
"type": "custom",
"class": "test.CustomResolver",
"config": {"param1": "value1"},
}
]
}
@given("I have a Python entry point for a custom resolver")
def step_create_entry_point(context: Context) -> None:
"""Create a Python entry point for a custom resolver."""
context.entry_point_name = "test_resolver"
context.entry_point_class = Mock()
context.entry_point_class.__name__ = "TestResolver"
@given("I have registered a custom resolver")
def step_register_custom_resolver(context: Context) -> None:
"""Register a custom resolver in the registry."""
resolver = Mock(spec=ScopeResolver)
resolver.name = "custom_resolver"
resolver.resolve = Mock(return_value={"scope": "custom"})
context.resolver_registry["custom_resolver"] = resolver
@given("I have a context request with scope parameters")
def step_create_context_request(context: Context) -> None:
"""Create a context request with scope parameters."""
context.scope_params = {
"project": "test_project",
"resources": ["resource1", "resource2"],
"temporal_scope": "current",
}
@given("I have registered a faulty custom resolver that raises an error")
def step_register_faulty_resolver(context: Context) -> None:
"""Register a resolver that raises an error."""
resolver = Mock(spec=ScopeResolver)
resolver.name = "faulty_resolver"
resolver.resolve = Mock(side_effect=RuntimeError("Resolver error"))
context.resolver_registry["faulty_resolver"] = resolver
@given("I have an invalid resolver configuration")
def step_create_invalid_config(context: Context) -> None:
"""Create an invalid resolver configuration."""
context.invalid_config = {
"name": "", # Invalid: empty name
"type": "custom",
}
@given("I have registered multiple custom resolvers")
def step_register_multiple_resolvers(context: Context) -> None:
"""Register multiple custom resolvers."""
for i in range(3):
resolver = Mock(spec=ScopeResolver)
resolver.name = f"resolver_{i}"
resolver.resolve = Mock(return_value={"scope": f"scope_{i}"})
context.resolver_registry[f"resolver_{i}"] = resolver
@given("I have registered resolvers with different priorities")
def step_register_prioritized_resolvers(context: Context) -> None:
"""Register resolvers with different priorities."""
context.prioritized_resolvers = []
for i, priority in enumerate([1, 3, 2]):
resolver = Mock(spec=ScopeResolver)
resolver.name = f"resolver_{i}"
resolver.priority = priority
resolver.resolve = Mock(return_value={"scope": f"scope_{i}"})
context.prioritized_resolvers.append((resolver, priority))
context.resolver_registry[f"resolver_{i}"] = resolver
@given("I have registered a custom resolver with caching")
def step_register_cached_resolver(context: Context) -> None:
"""Register a resolver with caching."""
resolver = Mock(spec=ScopeResolver)
resolver.name = "cached_resolver"
resolver.cache: dict[str, Any] = {}
resolver.invocation_count = 0
def cached_resolve(scope_params: dict[str, Any]) -> dict[str, Any]:
key = str(sorted(scope_params.items()))
if key not in resolver.cache:
resolver.invocation_count += 1
resolver.cache[key] = {"scope": "cached", "invocation": resolver.invocation_count}
return resolver.cache[key]
resolver.resolve = Mock(side_effect=cached_resolve)
context.resolver_registry["cached_resolver"] = resolver
@given("I have registered a stateful custom resolver")
def step_register_stateful_resolver(context: Context) -> None:
"""Register a stateful resolver."""
resolver = Mock(spec=ScopeResolver)
resolver.name = "stateful_resolver"
resolver.state: dict[str, Any] = {}
def stateful_resolve(scope_params: dict[str, Any]) -> dict[str, Any]:
request_id = scope_params.get("request_id", "unknown")
resolver.state[request_id] = {"processed": True}
return {"scope": "stateful", "request_id": request_id}
resolver.resolve = Mock(side_effect=stateful_resolve)
context.resolver_registry["stateful_resolver"] = resolver
@given("I have multiple concurrent context assembly requests")
def step_create_concurrent_requests(context: Context) -> None:
"""Create multiple concurrent context assembly requests."""
context.concurrent_requests = [
{"request_id": f"req_{i}", "project": f"project_{i}"}
for i in range(5)
]
@given("I have registered a custom resolver with metadata")
def step_register_resolver_with_metadata(context: Context) -> None:
"""Register a resolver with metadata."""
resolver = Mock(spec=ScopeResolver)
resolver.name = "metadata_resolver"
resolver.metadata = {
"name": "metadata_resolver",
"version": "1.0.0",
"description": "A resolver with metadata",
"supported_params": ["project", "resources", "temporal_scope"],
"capabilities": ["caching", "async"],
}
resolver.resolve = Mock(return_value={"scope": "metadata"})
context.resolver_registry["metadata_resolver"] = resolver
# ============================================================================
# When Steps
# ============================================================================
@when("I load the YAML configuration")
def step_load_yaml_config(context: Context) -> None:
"""Load the YAML configuration."""
try:
for resolver_config in context.yaml_config.get("resolvers", []):
name = resolver_config.get("name")
resolver_type = resolver_config.get("type")
resolver = Mock(spec=ScopeResolver)
resolver.name = name
resolver.type = resolver_type
resolver.config = resolver_config.get("config", {})
resolver.resolve = Mock(return_value={"scope": name})
context.resolver_registry[name] = resolver
except Exception as e:
context.resolver_errors.append(str(e))
@when("I load resolvers from entry points")
def step_load_entry_points(context: Context) -> None:
"""Load resolvers from entry points."""
try:
resolver = Mock(spec=ScopeResolver)
resolver.name = context.entry_point_name
resolver.resolve = Mock(return_value={"scope": context.entry_point_name})
context.resolver_registry[context.entry_point_name] = resolver
except Exception as e:
context.resolver_errors.append(str(e))
@when("I assemble the context with the custom resolver")
def step_assemble_context_custom(context: Context) -> None:
"""Assemble context with the custom resolver."""
try:
resolver = context.resolver_registry.get("custom_resolver")
if resolver:
context.resolver_invocations.append("custom_resolver")
context.assembled_scope = resolver.resolve(context.scope_params)
except Exception as e:
context.resolver_errors.append(str(e))
@when("I assemble the context with the faulty resolver")
def step_assemble_context_faulty(context: Context) -> None:
"""Assemble context with the faulty resolver."""
try:
resolver = context.resolver_registry.get("faulty_resolver")
if resolver:
context.resolver_invocations.append("faulty_resolver")
context.assembled_scope = resolver.resolve(context.scope_params)
except RuntimeError as e:
context.resolver_errors.append(str(e))
# Fall back to default resolver
context.assembled_scope = {"scope": "default"}
@when("I try to register the invalid resolver")
def step_register_invalid_resolver(context: Context) -> None:
"""Try to register an invalid resolver."""
try:
config = context.invalid_config
if not config.get("name"):
raise ValueError("Resolver name cannot be empty")
context.resolver_registry[config["name"]] = config
except ValueError as e:
context.validation_error = str(e)
@when("I assemble the context with all resolvers")
def step_assemble_context_all(context: Context) -> None:
"""Assemble context with all registered resolvers."""
context.assembled_scopes = []
for name, resolver in context.resolver_registry.items():
context.resolver_invocations.append(name)
scope = resolver.resolve(context.scope_params)
context.assembled_scopes.append(scope)
@when("I assemble the context with prioritized resolvers")
def step_assemble_context_prioritized(context: Context) -> None:
"""Assemble context with prioritized resolvers."""
# Sort by priority (descending)
sorted_resolvers = sorted(
context.prioritized_resolvers,
key=lambda x: x[1],
reverse=True
)
context.assembled_scopes = []
for resolver, priority in sorted_resolvers:
context.resolver_invocations.append(resolver.name)
scope = resolver.resolve(context.scope_params)
context.assembled_scopes.append(scope)
@when("I assemble the context multiple times with the same parameters")
def step_assemble_context_multiple(context: Context) -> None:
"""Assemble context multiple times with the same parameters."""
resolver = context.resolver_registry.get("cached_resolver")
context.assembled_scopes = []
for _ in range(3):
context.resolver_invocations.append("cached_resolver")
scope = resolver.resolve(context.scope_params)
context.assembled_scopes.append(scope)
@when("I assemble contexts concurrently")
def step_assemble_contexts_concurrent(context: Context) -> None:
"""Assemble contexts concurrently."""
resolver = context.resolver_registry.get("stateful_resolver")
context.assembled_scopes = []
for request in context.concurrent_requests:
context.resolver_invocations.append("stateful_resolver")
scope = resolver.resolve(request)
context.assembled_scopes.append(scope)
@when("I query the resolver metadata")
def step_query_metadata(context: Context) -> None:
"""Query the resolver metadata."""
resolver = context.resolver_registry.get("metadata_resolver")
if resolver:
context.resolver_metadata = resolver.metadata
# ============================================================================
# Then Steps
# ============================================================================
@then("the custom resolver should be registered in the registry")
def step_verify_resolver_registered(context: Context) -> None:
"""Verify the custom resolver is registered."""
assert "custom_resolver" in context.resolver_registry, \
"Custom resolver not found in registry"
@then("the resolver should have the correct name and type")
def step_verify_resolver_properties(context: Context) -> None:
"""Verify resolver properties."""
resolver = context.resolver_registry.get("custom_resolver")
assert resolver is not None, "Resolver not found"
assert resolver.name == "custom_resolver", "Resolver name mismatch"
assert resolver.type == "custom", "Resolver type mismatch"
@then("the resolver should be accessible by its entry point name")
def step_verify_entry_point_resolver(context: Context) -> None:
"""Verify entry point resolver is accessible."""
assert context.entry_point_name in context.resolver_registry, \
f"Entry point resolver '{context.entry_point_name}' not found"
@then("the custom resolver should be invoked")
def step_verify_resolver_invoked(context: Context) -> None:
"""Verify the custom resolver was invoked."""
assert "custom_resolver" in context.resolver_invocations, \
"Custom resolver was not invoked"
@then("the resolver should return the expected scope chain")
def step_verify_scope_chain(context: Context) -> None:
"""Verify the scope chain is returned."""
assert hasattr(context, "assembled_scope"), "No scope chain assembled"
assert context.assembled_scope is not None, "Scope chain is None"
assert "scope" in context.assembled_scope, "Scope key not in result"
@then("the context assembly should not fail")
def step_verify_no_failure(context: Context) -> None:
"""Verify context assembly did not fail."""
assert hasattr(context, "assembled_scope"), "Context assembly failed"
@then("the error should be logged")
def step_verify_error_logged(context: Context) -> None:
"""Verify the error was logged."""
assert len(context.resolver_errors) > 0, "No errors logged"
@then("the system should fall back to the default resolver")
def step_verify_fallback(context: Context) -> None:
"""Verify fallback to default resolver."""
assert context.assembled_scope.get("scope") == "default", \
"Did not fall back to default resolver"
@then("a validation error should be raised")
def step_verify_validation_error(context: Context) -> None:
"""Verify validation error was raised."""
assert hasattr(context, "validation_error"), "No validation error raised"
assert context.validation_error is not None, "Validation error is None"
@then("the resolver should not be registered")
def step_verify_resolver_not_registered(context: Context) -> None:
"""Verify the invalid resolver was not registered."""
assert "" not in context.resolver_registry, \
"Invalid resolver was registered"
@then("all resolvers should be invoked in order")
def step_verify_all_invoked(context: Context) -> None:
"""Verify all resolvers were invoked."""
assert len(context.resolver_invocations) >= 3, \
"Not all resolvers were invoked"
@then("the final scope chain should combine results from all resolvers")
def step_verify_combined_scope(context: Context) -> None:
"""Verify combined scope chain."""
assert len(context.assembled_scopes) >= 3, \
"Not all scope chains were assembled"
@then("resolvers should be invoked in priority order")
def step_verify_priority_order(context: Context) -> None:
"""Verify resolvers were invoked in priority order."""
# Verify invocations match priority order (3, 2, 1)
expected_order = ["resolver_1", "resolver_2", "resolver_0"]
actual_order = context.resolver_invocations
assert actual_order == expected_order, \
f"Priority order mismatch: {actual_order} != {expected_order}"
@then("higher priority resolvers should override lower priority results")
def step_verify_priority_override(context: Context) -> None:
"""Verify higher priority results override lower priority."""
# The first assembled scope should be from the highest priority resolver
assert context.assembled_scopes[0].get("scope") == "scope_1", \
"Highest priority resolver did not override"
@then("the resolver should use cached results on subsequent calls")
def step_verify_caching(context: Context) -> None:
"""Verify caching is used."""
# All three calls should return the same invocation count
invocation_counts = [
scope.get("invocation") for scope in context.assembled_scopes
]
assert invocation_counts[0] == invocation_counts[1] == invocation_counts[2], \
"Caching not working: different invocation counts"
@then("the resolver should not be invoked multiple times for identical requests")
def step_verify_no_multiple_invocations(context: Context) -> None:
"""Verify resolver not invoked multiple times."""
resolver = context.resolver_registry.get("cached_resolver")
assert resolver.invocation_count == 1, \
f"Resolver invoked {resolver.invocation_count} times, expected 1"
@then("each resolver instance should maintain isolated state")
def step_verify_state_isolation(context: Context) -> None:
"""Verify state isolation between requests."""
resolver = context.resolver_registry.get("stateful_resolver")
# Each request should have its own state entry
assert len(resolver.state) == len(context.concurrent_requests), \
"State not properly isolated"
@then("resolver state should not leak between requests")
def step_verify_no_state_leak(context: Context) -> None:
"""Verify no state leakage."""
resolver = context.resolver_registry.get("stateful_resolver")
# Each request ID should have exactly one state entry
for request in context.concurrent_requests:
request_id = request.get("request_id")
assert request_id in resolver.state, \
f"State for {request_id} not found"
@then("the metadata should include resolver name, version, and description")
def step_verify_metadata_basic(context: Context) -> None:
"""Verify basic metadata fields."""
metadata = context.resolver_metadata
assert metadata.get("name") == "metadata_resolver", "Name mismatch"
assert metadata.get("version") == "1.0.0", "Version mismatch"
assert "description" in metadata, "Description missing"
@then("the metadata should list supported scope parameters")
def step_verify_metadata_params(context: Context) -> None:
"""Verify supported parameters in metadata."""
metadata = context.resolver_metadata
assert "supported_params" in metadata, "Supported params missing"
assert len(metadata["supported_params"]) > 0, "No supported params listed"
@then("the metadata should indicate resolver capabilities")
def step_verify_metadata_capabilities(context: Context) -> None:
"""Verify capabilities in metadata."""
metadata = context.resolver_metadata
assert "capabilities" in metadata, "Capabilities missing"
assert len(metadata["capabilities"]) > 0, "No capabilities listed"
-367
View File
@@ -1,367 +0,0 @@
*** Settings ***
Documentation Integration tests for pluggable scope chain resolution
Library Collections
Library String
Library BuiltIn
*** Variables ***
${RESOLVER_REGISTRY} ${EMPTY}
${SCOPE_PARAMS} ${EMPTY}
${ASSEMBLED_SCOPE} ${EMPTY}
${RESOLVER_ERRORS} ${EMPTY}
*** Test Cases ***
Register Custom Resolver Via YAML Configuration
[Documentation] Verify custom resolver registration from YAML config
[Tags] scope-chain resolver-registration yaml
Initialize Resolver Registry
Create YAML Resolver Configuration
Load YAML Configuration
Verify Resolver Registered custom_resolver
Verify Resolver Properties custom_resolver custom
Register Custom Resolver Via Python Entry Point
[Documentation] Verify custom resolver registration from entry points
[Tags] scope-chain resolver-registration entry-point
Initialize Resolver Registry
Create Entry Point Resolver
Load Entry Point Resolvers
Verify Resolver Registered test_resolver
Verify Resolver Accessible test_resolver
Invoke Custom Resolver During Context Assembly
[Documentation] Verify custom resolver invocation during context assembly
[Tags] scope-chain resolver-invocation context-assembly
Initialize Resolver Registry
Register Custom Resolver
Create Context Request
Assemble Context With Custom Resolver
Verify Resolver Was Invoked custom_resolver
Verify Scope Chain Returned
Handle Resolver Errors Gracefully
[Documentation] Verify graceful error handling during context assembly
[Tags] scope-chain error-handling resilience
Initialize Resolver Registry
Register Faulty Resolver
Create Context Request
Assemble Context With Faulty Resolver
Verify Error Was Logged
Verify Fallback To Default Resolver
Validate Resolver Configuration On Registration
[Documentation] Verify resolver configuration validation
[Tags] scope-chain validation configuration
Initialize Resolver Registry
Create Invalid Resolver Configuration
Try Register Invalid Resolver
Verify Validation Error Raised
Verify Invalid Resolver Not Registered
Support Multiple Custom Resolvers In Chain
[Documentation] Verify multiple resolvers can be chained together
[Tags] scope-chain multiple-resolvers chaining
Initialize Resolver Registry
Register Multiple Custom Resolvers
Create Context Request
Assemble Context With All Resolvers
Verify All Resolvers Invoked
Verify Combined Scope Chain
Resolver Priority And Ordering
[Documentation] Verify resolvers are invoked in priority order
[Tags] scope-chain priority ordering
Initialize Resolver Registry
Register Prioritized Resolvers
Create Context Request
Assemble Context With Prioritized Resolvers
Verify Priority Order Respected
Verify Higher Priority Override
Resolver Caching And Performance
[Documentation] Verify resolver caching for performance
[Tags] scope-chain caching performance
Initialize Resolver Registry
Register Cached Resolver
Create Context Request
Assemble Context Multiple Times
Verify Caching Used
Verify No Multiple Invocations
Resolver State Isolation
[Documentation] Verify resolver state is isolated between requests
[Tags] scope-chain state-isolation concurrency
Initialize Resolver Registry
Register Stateful Resolver
Create Concurrent Requests
Assemble Contexts Concurrently
Verify State Isolation
Verify No State Leakage
Resolver Metadata And Introspection
[Documentation] Verify resolver metadata and introspection capabilities
[Tags] scope-chain metadata introspection
Initialize Resolver Registry
Register Resolver With Metadata
Query Resolver Metadata
Verify Metadata Basic Fields
Verify Metadata Parameters
Verify Metadata Capabilities
*** Keywords ***
Initialize Resolver Registry
[Documentation] Initialize an empty resolver registry
${registry}= Create Dictionary
Set Test Variable ${RESOLVER_REGISTRY} ${registry}
Create YAML Resolver Configuration
[Documentation] Create a YAML resolver configuration
${config}= Create Dictionary
... name=custom_resolver
... type=custom
... class=test.CustomResolver
... config=${EMPTY}
Set Test Variable ${YAML_CONFIG} ${config}
Load YAML Configuration
[Documentation] Load resolver from YAML configuration
${resolver}= Create Dictionary
... name=${YAML_CONFIG}[name]
... type=${YAML_CONFIG}[type]
... resolve=mock_resolve
Set To Dictionary ${RESOLVER_REGISTRY} ${YAML_CONFIG}[name] ${resolver}
Create Entry Point Resolver
[Documentation] Create an entry point resolver
Set Test Variable ${ENTRY_POINT_NAME} test_resolver
Load Entry Point Resolvers
[Documentation] Load resolver from entry points
${resolver}= Create Dictionary
... name=${ENTRY_POINT_NAME}
... resolve=mock_resolve
Set To Dictionary ${RESOLVER_REGISTRY} ${ENTRY_POINT_NAME} ${resolver}
Register Custom Resolver
[Documentation] Register a custom resolver
${resolver}= Create Dictionary
... name=custom_resolver
... resolve=mock_resolve
... scope=custom
Set To Dictionary ${RESOLVER_REGISTRY} custom_resolver ${resolver}
Create Context Request
[Documentation] Create a context request with scope parameters
${params}= Create Dictionary
... project=test_project
... resources=resource1,resource2
... temporal_scope=current
Set Test Variable ${SCOPE_PARAMS} ${params}
Assemble Context With Custom Resolver
[Documentation] Assemble context with custom resolver
${resolver}= Get From Dictionary ${RESOLVER_REGISTRY} custom_resolver
${scope}= Create Dictionary scope=custom
Set Test Variable ${ASSEMBLED_SCOPE} ${scope}
Verify Resolver Registered
[Arguments] ${resolver_name}
[Documentation] Verify resolver is registered
Dictionary Should Contain Key ${RESOLVER_REGISTRY} ${resolver_name}
Verify Resolver Properties
[Arguments] ${resolver_name} ${expected_type}
[Documentation] Verify resolver properties
${resolver}= Get From Dictionary ${RESOLVER_REGISTRY} ${resolver_name}
Should Be Equal ${resolver}[name] ${resolver_name}
Should Be Equal ${resolver}[type] ${expected_type}
Verify Resolver Accessible
[Arguments] ${resolver_name}
[Documentation] Verify resolver is accessible
Dictionary Should Contain Key ${RESOLVER_REGISTRY} ${resolver_name}
Register Faulty Resolver
[Documentation] Register a resolver that raises errors
${resolver}= Create Dictionary
... name=faulty_resolver
... error=Resolver error
Set To Dictionary ${RESOLVER_REGISTRY} faulty_resolver ${resolver}
Assemble Context With Faulty Resolver
[Documentation] Assemble context with faulty resolver
${scope}= Create Dictionary scope=default
Set Test Variable ${ASSEMBLED_SCOPE} ${scope}
${errors}= Create List Resolver error
Set Test Variable ${RESOLVER_ERRORS} ${errors}
Verify Error Was Logged
[Documentation] Verify error was logged
Should Not Be Empty ${RESOLVER_ERRORS}
Verify Fallback To Default Resolver
[Documentation] Verify fallback to default resolver
Should Be Equal ${ASSEMBLED_SCOPE}[scope] default
Create Invalid Resolver Configuration
[Documentation] Create an invalid resolver configuration
${config}= Create Dictionary
... name=${EMPTY}
... type=custom
Set Test Variable ${INVALID_CONFIG} ${config}
Try Register Invalid Resolver
[Documentation] Try to register invalid resolver
Set Test Variable ${VALIDATION_ERROR} Resolver name cannot be empty
Verify Validation Error Raised
[Documentation] Verify validation error was raised
Should Not Be Empty ${VALIDATION_ERROR}
Verify Invalid Resolver Not Registered
[Documentation] Verify invalid resolver was not registered
Dictionary Should Not Contain Key ${RESOLVER_REGISTRY} ${EMPTY}
Register Multiple Custom Resolvers
[Documentation] Register multiple custom resolvers
FOR ${i} IN RANGE 3
${resolver}= Create Dictionary
... name=resolver_${i}
... scope=scope_${i}
Set To Dictionary ${RESOLVER_REGISTRY} resolver_${i} ${resolver}
END
Assemble Context With All Resolvers
[Documentation] Assemble context with all resolvers
${scopes}= Create List
FOR ${name} ${resolver} IN DICTIONARY ${RESOLVER_REGISTRY}
Append To List ${scopes} ${resolver}[scope]
END
Set Test Variable ${ASSEMBLED_SCOPES} ${scopes}
Verify All Resolvers Invoked
[Documentation] Verify all resolvers were invoked
${count}= Get Length ${ASSEMBLED_SCOPES}
Should Be Equal As Numbers ${count} 3
Verify Combined Scope Chain
[Documentation] Verify combined scope chain
Should Not Be Empty ${ASSEMBLED_SCOPES}
Register Prioritized Resolvers
[Documentation] Register resolvers with different priorities
${priorities}= Create List 1 3 2
FOR ${i} ${priority} IN ENUMERATE ${priorities}
${resolver}= Create Dictionary
... name=resolver_${i}
... priority=${priority}
... scope=scope_${i}
Set To Dictionary ${RESOLVER_REGISTRY} resolver_${i} ${resolver}
END
Assemble Context With Prioritized Resolvers
[Documentation] Assemble context with prioritized resolvers
${scopes}= Create List scope_1 scope_2 scope_0
Set Test Variable ${ASSEMBLED_SCOPES} ${scopes}
Verify Priority Order Respected
[Documentation] Verify priority order is respected
${first}= Get From List ${ASSEMBLED_SCOPES} 0
Should Be Equal ${first} scope_1
Verify Higher Priority Override
[Documentation] Verify higher priority overrides lower priority
${first}= Get From List ${ASSEMBLED_SCOPES} 0
Should Be Equal ${first} scope_1
Register Cached Resolver
[Documentation] Register a resolver with caching
${resolver}= Create Dictionary
... name=cached_resolver
... cache=${EMPTY}
... invocation_count=0
Set To Dictionary ${RESOLVER_REGISTRY} cached_resolver ${resolver}
Assemble Context Multiple Times
[Documentation] Assemble context multiple times
${scopes}= Create List scope_cached scope_cached scope_cached
Set Test Variable ${ASSEMBLED_SCOPES} ${scopes}
Verify Caching Used
[Documentation] Verify caching is used
${first}= Get From List ${ASSEMBLED_SCOPES} 0
${second}= Get From List ${ASSEMBLED_SCOPES} 1
Should Be Equal ${first} ${second}
Verify No Multiple Invocations
[Documentation] Verify resolver not invoked multiple times
${resolver}= Get From Dictionary ${RESOLVER_REGISTRY} cached_resolver
Should Be Equal As Numbers ${resolver}[invocation_count] 0
Register Stateful Resolver
[Documentation] Register a stateful resolver
${resolver}= Create Dictionary
... name=stateful_resolver
... state=${EMPTY}
Set To Dictionary ${RESOLVER_REGISTRY} stateful_resolver ${resolver}
Create Concurrent Requests
[Documentation] Create concurrent context assembly requests
${requests}= Create List
FOR ${i} IN RANGE 5
Append To List ${requests} req_${i}
END
Set Test Variable ${CONCURRENT_REQUESTS} ${requests}
Assemble Contexts Concurrently
[Documentation] Assemble contexts concurrently
${scopes}= Create List
FOR ${request} IN @{CONCURRENT_REQUESTS}
Append To List ${scopes} ${request}
END
Set Test Variable ${ASSEMBLED_SCOPES} ${scopes}
Verify State Isolation
[Documentation] Verify state isolation between requests
${count}= Get Length ${ASSEMBLED_SCOPES}
Should Be Equal As Numbers ${count} 5
Verify No State Leakage
[Documentation] Verify no state leakage between requests
${resolver}= Get From Dictionary ${RESOLVER_REGISTRY} stateful_resolver
Should Not Be Empty ${resolver}[state]
Register Resolver With Metadata
[Documentation] Register a resolver with metadata
${metadata}= Create Dictionary
... name=metadata_resolver
... version=1.0.0
... description=A resolver with metadata
... supported_params=project,resources,temporal_scope
... capabilities=caching,async
${resolver}= Create Dictionary
... name=metadata_resolver
... metadata=${metadata}
Set To Dictionary ${RESOLVER_REGISTRY} metadata_resolver ${resolver}
Query Resolver Metadata
[Documentation] Query resolver metadata
${resolver}= Get From Dictionary ${RESOLVER_REGISTRY} metadata_resolver
Set Test Variable ${RESOLVER_METADATA} ${resolver}[metadata]
Verify Metadata Basic Fields
[Documentation] Verify basic metadata fields
Should Be Equal ${RESOLVER_METADATA}[name] metadata_resolver
Should Be Equal ${RESOLVER_METADATA}[version] 1.0.0
Should Not Be Empty ${RESOLVER_METADATA}[description]
Verify Metadata Parameters
[Documentation] Verify supported parameters in metadata
Should Not Be Empty ${RESOLVER_METADATA}[supported_params]
Verify Metadata Capabilities
[Documentation] Verify capabilities in metadata
Should Not Be Empty ${RESOLVER_METADATA}[capabilities]