Files
cleveragents-core/docs/reference/acms.md
T
freemo fae438a7a7
CI / benchmark-publish (pull_request) Has been skipped
CI / lint (pull_request) Successful in 15s
CI / build (pull_request) Successful in 15s
CI / quality (pull_request) Successful in 17s
CI / security (pull_request) Successful in 33s
CI / typecheck (pull_request) Successful in 35s
CI / unit_tests (pull_request) Successful in 2m22s
CI / integration_tests (pull_request) Successful in 3m0s
CI / docker (pull_request) Successful in 39s
CI / coverage (pull_request) Successful in 4m21s
CI / lint (push) Successful in 14s
CI / quality (push) Successful in 16s
CI / build (push) Successful in 16s
CI / security (push) Successful in 30s
CI / typecheck (push) Successful in 36s
CI / benchmark-regression (push) Has been skipped
CI / unit_tests (push) Successful in 3m38s
CI / docker (push) Successful in 39s
CI / integration_tests (push) Successful in 4m25s
CI / coverage (push) Successful in 4m19s
CI / benchmark-publish (push) Successful in 15m57s
CI / benchmark-regression (pull_request) Successful in 28m50s
refactor(acms): unify ContextFragment model hierarchies by extending CRP base types
Core domain types (FragmentProvenance, ContextFragment, ContextBudget,
ContextPayload) now extend their CRP counterparts via Pydantic v2
inheritance, ensuring isinstance compatibility across the model
hierarchy.

Key changes:
- CRP base types made frozen=True (no consumer mutates them)
- CRP AssembledContext fields changed from list to tuple (frozen consistency)
- Core types extend CRP bases: FragmentProvenance(CRPFragmentProvenance),
  ContextFragment(CRPContextFragment), ContextBudget(CRPContextBudget),
  ContextPayload(CRPAssembledContext)
- Removed duplicate ContextFragment dataclass from skeleton_compressor
- Updated project_context.py to pass tuples to frozen AssembledContext
- Added Behave tests (10 scenarios), Robot integration tests (3 cases),
  and ASV benchmarks for the unified hierarchy
- Updated Known Limitations table in docs/reference/acms.md

ISSUES CLOSED: #569
2026-03-05 21:38:55 +00:00

276 lines
12 KiB
Markdown

# ACMS v1 Context Assembly Pipeline
This document covers the Adaptive Context Management System (ACMS) v1
context assembly pipeline, which integrates UKO (Unified Knowledge Ontology)
and CRP (Context Request Protocol) components with pluggable context
strategies.
## Overview
The ACMS pipeline assembles context fragments into a budget-constrained
payload for actor consumption. It supports multiple context strategies
that determine how fragments are ranked and selected to fit within the
token budget. The pipeline implements the spec's 10-component architecture
across three phases: Strategy Orchestration, Fragment Fusion, and Context
Finalization.
| Component | Description |
|-----------|-------------|
| `ContextFragment` | A single piece of context with UKO node, relevance, provenance, and tier |
| `FragmentProvenance` | Provenance trace linking a fragment back to its originating resource |
| `ContextBudget` | Token budget with max and reserved tokens |
| `ContextPayload` | Assembled payload with fragments, token count, budget usage, context hash, and provenance map |
| `ACMSPipeline` | Pipeline that applies a context strategy to assemble payloads |
## Context Fragments
Each `ContextFragment` represents a single piece of context:
| Field | Type | Description |
|-------|------|-------------|
| `fragment_id` | `str` | ULID identifier (auto-generated) |
| `uko_node` | `str` | UKO URI of the source node (required) |
| `content` | `str` | Rendered text content (max 1,000,000 chars) |
| `detail_depth` | `int` | Resolved depth: 0 (MODULE_LISTING) through 9 (FULL_SOURCE), default 0 |
| `token_count` | `int` | Actual token count of content (required, >= 0) |
| `relevance_score` | `float` | Score from 0.0 to 1.0 (default 0.5) |
| `provenance` | `FragmentProvenance` | Provenance trace (required) |
| `tier` | `str` | Priority tier: `"hot"`, `"warm"`, `"cold"` (default `"warm"`) |
| `metadata` | `dict[str, str]` | Arbitrary key-value metadata (max 64 entries) |
| `created_at` | `datetime` | UTC timestamp (auto-generated) |
### FragmentProvenance
| Field | Type | Description |
|-------|------|-------------|
| `resource_uri` | `str` | URI of the originating resource (required) |
| `location` | `str` | Location within the resource (default `""`) |
| `resource_type` | `str` | Type of originating resource (default `"unknown"`) |
### Tiers
Fragments are classified into three tiers:
- **hot** -- Critical context that should always be included first
- **warm** -- Standard context included when budget allows (default)
- **cold** -- Low-priority context included only if space remains
> **Note:** In v1, tiers are *sort-priority labels* used for ranking during
> assembly. They do **not** represent storage tiers with retention policies,
> promotion, or eviction semantics. Full hot/warm/cold storage-tier semantics
> will be implemented separately in `ContextTierService`.
## Budget Management
The `ContextBudget` defines the token budget for assembly:
- `max_tokens` -- Maximum total tokens (default 4096, must be >= 1)
- `reserved_tokens` -- Tokens reserved for system prompt (default 512, must be >= 0)
- `available_tokens` -- Computed as `max_tokens - reserved_tokens`
Validation: `reserved_tokens` must be strictly less than `max_tokens`.
The pipeline never exceeds the available token budget. Fragments that
would push the total over the limit are skipped, with early termination
once the budget is fully consumed.
## Context Payload
The assembled `ContextPayload` includes:
| Field | Type | Description |
|-------|------|-------------|
| `payload_id` | `str` | ULID identifier (auto-generated) |
| `plan_id` | `str` | Plan this payload was assembled for |
| `fragments` | `tuple[ContextFragment, ...]` | Selected fragments |
| `total_tokens` | `int` | Sum of fragment `token_count` values |
| `budget` | `ContextBudget` | Budget used for assembly |
| `budget_used` | `float` | Fraction of budget consumed (0.0-1.0) |
| `strategies_used` | `tuple[str, ...]` | Strategy names that contributed (immutable) |
| `context_hash` | `str` | SHA-256 hash of assembled content |
| `preamble` | `str \| None` | Optional structure summary |
| `provenance_map` | `dict[str, Any]` | Fragment ID -> provenance mapping |
| `assembled_at` | `datetime` | UTC timestamp |
Properties:
- `is_within_budget` -- `True` if total tokens do not exceed available budget
- `remaining_tokens` -- Tokens still available in the budget
## Context Strategies
Strategies implement the `ContextStrategy` protocol:
| Method | Description |
|--------|-------------|
| `name` (property) | Strategy identifier |
| `capabilities` (property) | `StrategyCapabilities` dataclass |
| `can_handle(request)` | Confidence (0.0-1.0) for handling a request |
| `assemble(fragments, budget)` | Rank/filter fragments to fit budget |
| `explain()` | Human-readable explanation |
### Relevance (default)
Sorts fragments by `relevance_score` descending. Highest-relevance
fragments are selected first until the budget is exhausted.
### Recency
Sorts fragments by `created_at` descending (datetime comparison, not
string comparison). Most recent fragments are selected first.
### Tiered
Groups fragments by tier priority (`hot` > `warm` > `cold`). Within
each tier, fragments are sorted by `relevance_score` descending.
## 10-Component Pipeline Architecture
The pipeline runs three phases:
All 10 components have explicit Protocol definitions and injectable
Default implementations. Inject custom components via the
`ACMSPipeline` constructor.
**Phase 1 — Strategy Orchestration:** `StrategySelector`,
`BudgetAllocator`, `StrategyExecutor` (v1 defaults: single-strategy
selection, full-budget allocation, synchronous execution)
**Phase 2 — Fragment Fusion:** `FragmentDeduplicator`,
`DetailDepthResolver`, `FragmentScorer`, `BudgetPacker`,
`FragmentOrderer` (v1 defaults: pass-through / no-op)
**Phase 3 — Context Finalization:** `PreambleGenerator`,
`SkeletonCompressor` (v1 defaults: no-op preamble, identity compression)
## v1 Known Limitations
The following are known deviations from the full specification, accepted
for the v1 implementation with a path to spec conformance in future
milestones:
| Area | v1 Behaviour | Spec Target | Planned |
|------|-------------|-------------|---------|
| `ContextStrategy.can_handle` signature | `(request: dict[str, Any]) -> float` | `(request: ContextRequest, backends: BackendSet) -> float` | M6 strategy registry aligns signatures |
| `ContextStrategy.assemble` signature | `(fragments, budget)` — receives pre-fetched fragments | `(request, backends, budget, plan_context)` — queries backends directly | M6 strategy registry aligns signatures |
| `StrategyCapabilities` fields | `supports_semantic_search`, `supports_graph_navigation`, `supports_temporal_archaeology`, `max_fragments` | `uses_text`, `uses_vector`, `uses_graph`, `uses_temporal`, `uko_levels`, `resource_types`, `quality_score` | M6 strategy registry uses spec field names |
| Pipeline components | All 10 Protocol + Default classes defined; defaults are pass-through stubs | Production implementations (parallel execution, dedup, scoring, compression) | Future milestone |
| Tiers | Sort-priority labels for ranking (`hot > warm > cold`) | Storage tiers with retention policies, promotion/demotion | `ContextTierService` in future milestone |
| `StrategySelector.select()` | `(strategies, request: dict)` | `(strategies, request: ContextRequest, backends: BackendSet)` — spec §42666 | M6 strategy registry aligns signatures |
| `BudgetAllocator.allocate()` | `(candidates, total_budget: int)` | `(candidates, total_budget, request: ContextRequest)` — spec §42682 | Future milestone |
| `StrategyExecutor.execute()` | `(allocations, fragments, budget)` | `(allocations, request: ContextRequest, backends: BackendSet, plan_context: PlanContext)` — spec §42698 | M6 strategy registry aligns signatures |
| `BudgetPacker.pack()` | `(fragments: Sequence[ContextFragment], budget: ContextBudget)` | `(scored_fragments: list[ScoredFragment], budget: int, detail_level_maps)` — spec §42763 | Future milestone |
| `SkeletonCompressor.compress()` | `(fragments: tuple, skeleton_budget: int) -> tuple` | `(parent_context: AssembledContext, child_focus: list[str], skeleton_budget: int) -> AssembledContext` — spec §42811 | Future milestone |
| `DetailDepthResolver.resolve()` | Missing `budget` parameter | `resolve(fragments, budget)` | Future milestone |
| `FragmentScorer.score()` | Missing `plan_context` param; returns `Sequence[ContextFragment]` | Returns `list[ScoredFragment]` with `plan_context` | Future milestone |
| `PreambleGenerator.generate()` | Missing `strategies_used`, `budget_used`, `max_tokens` params | Full parameter set per spec §42795 | Future milestone |
| `provenance_map` keying | Keyed by `fragment_id` (ULID) | Spec keys by `uko_node` (UKO URI) | Under review — `fragment_id` avoids collisions when multiple fragments share a `uko_node` |
| CRP base model mutability | `FragmentProvenance`, `ContextFragment`, `ContextBudget`, `AssembledContext` are `frozen=True` | CRP models may be mutable | Core types extend CRP bases via Pydantic v2 inheritance; freezing CRP bases ensures consistent immutability across the hierarchy. No CRP consumer mutates these instances. |
## Extension Points
Register custom context strategies at runtime:
```python
from cleveragents.application.services.acms_service import (
ACMSPipeline,
ContextStrategy,
StrategyCapabilities,
)
from cleveragents.domain.models.core.context_fragment import (
ContextBudget,
ContextFragment,
FragmentProvenance,
)
from collections.abc import Sequence
from typing import Any
class MyCustomStrategy:
@property
def name(self) -> str:
return "custom"
@property
def capabilities(self) -> StrategyCapabilities:
return StrategyCapabilities()
def can_handle(self, request: dict[str, Any]) -> float:
return 0.5
def assemble(
self,
fragments: Sequence[ContextFragment],
budget: ContextBudget,
) -> Sequence[ContextFragment]:
# Custom ranking logic
return list(fragments)
def explain(self) -> str:
return "Custom strategy description."
pipeline = ACMSPipeline()
pipeline.register_strategy("custom", MyCustomStrategy())
payload = pipeline.assemble(
plan_id="plan-1",
fragments=fragments,
budget=budget,
strategy="custom",
)
```
## Example Usage
```python
from cleveragents.domain.models.core.context_fragment import (
ContextBudget,
ContextFragment,
ContextPayload,
FragmentProvenance,
)
from cleveragents.application.services.acms_service import ACMSPipeline
# Create fragments
fragments = [
ContextFragment(
uko_node="project://myapp/src/main.py",
content="def main(): ...",
relevance_score=0.9,
token_count=100,
tier="hot",
provenance=FragmentProvenance(resource_uri="project://myapp/src/main.py"),
),
ContextFragment(
uko_node="project://myapp/decisions/001",
content="Use async IO",
relevance_score=0.7,
token_count=50,
tier="warm",
provenance=FragmentProvenance(resource_uri="project://myapp/decisions/001"),
),
ContextFragment(
uko_node="project://myapp/README.md",
content="README.md content",
relevance_score=0.3,
token_count=200,
tier="cold",
provenance=FragmentProvenance(resource_uri="project://myapp/README.md"),
),
]
# Define budget
budget = ContextBudget(max_tokens=2048, reserved_tokens=256)
# Assemble with default relevance strategy
pipeline = ACMSPipeline()
payload = pipeline.assemble(plan_id="plan-1", fragments=fragments, budget=budget)
print(f"Fragments selected: {len(payload.fragments)}")
print(f"Total tokens: {payload.total_tokens}")
print(f"Budget used: {payload.budget_used:.1%}")
print(f"Within budget: {payload.is_within_budget}")
print(f"Remaining: {payload.remaining_tokens}")
print(f"Context hash: {payload.context_hash[:16]}...")
print(f"Strategies: {payload.strategies_used}")
```