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
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
276 lines
12 KiB
Markdown
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}")
|
|
```
|