# 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}")
```
