| """ |
| compiler.py — Prompt compiler with token budget enforcement and credit assignment. |
| |
| The PromptCompiler selects which memories to include in the prompt based on: |
| 1. Relevance to the current task |
| 2. Trust score (immune-scanned, tested) |
| 3. Utility score (has this memory actually helped before?) |
| 4. Scope match (right agent, right tools, right task category) |
| 5. Diversity (avoid redundant memories) |
| 6. Token cost (fit within budget) |
| |
| Returns included_memory_ids so the orchestrator can do credit assignment |
| after the step: update utility scores for memories that were in context. |
| """ |
| from __future__ import annotations |
|
|
| import logging |
| from dataclasses import dataclass, field |
| from typing import Any |
|
|
| from purpose_agent.memory import MemoryCard, MemoryKind, MemoryStatus, MemoryStore |
| from purpose_agent.v2_types import MemoryScope |
|
|
| logger = logging.getLogger(__name__) |
|
|
|
|
| @dataclass |
| class CompiledPrompt: |
| """Result of prompt compilation.""" |
| system_sections: list[str] = field(default_factory=list) |
| included_memory_ids: list[str] = field(default_factory=list) |
| total_tokens_estimated: int = 0 |
| budget_remaining: int = 0 |
| memories_considered: int = 0 |
| memories_included: int = 0 |
|
|
| @property |
| def system_prompt(self) -> str: |
| return "\n\n".join(self.system_sections) |
|
|
|
|
| class PromptCompiler: |
| """ |
| Compiles a prompt by selecting the best memories under a token budget. |
| |
| The key invariant: only promoted memories are included. |
| Candidate/quarantined/rejected memories are never exposed to the LLM. |
| """ |
|
|
| def __init__( |
| self, |
| memory_store: MemoryStore, |
| token_budget: int = 4096, |
| chars_per_token: int = 4, |
| ): |
| self.store = memory_store |
| self.token_budget = token_budget |
| self.chars_per_token = chars_per_token |
|
|
| def compile( |
| self, |
| task: str, |
| base_prompt: str, |
| scope: MemoryScope | None = None, |
| max_memories: int = 15, |
| ) -> CompiledPrompt: |
| """ |
| Compile a prompt: base_prompt + best memories under token budget. |
| |
| Returns CompiledPrompt with included_memory_ids for credit assignment. |
| """ |
| result = CompiledPrompt() |
| result.system_sections.append(base_prompt) |
|
|
| base_tokens = len(base_prompt) // self.chars_per_token |
| remaining = self.token_budget - base_tokens |
| result.budget_remaining = remaining |
|
|
| if remaining <= 100: |
| result.total_tokens_estimated = base_tokens |
| return result |
|
|
| |
| candidates = self.store.retrieve( |
| query_text=task, |
| scope=scope, |
| statuses=[MemoryStatus.PROMOTED], |
| top_k=max_memories * 2, |
| ) |
| result.memories_considered = len(candidates) |
|
|
| |
| selected = self._diverse_select(candidates, max_memories) |
|
|
| |
| memory_sections = [] |
| for card in selected: |
| text = self._format_memory(card) |
| token_cost = len(text) // self.chars_per_token |
|
|
| if token_cost > remaining: |
| continue |
|
|
| memory_sections.append(text) |
| result.included_memory_ids.append(card.id) |
| remaining -= token_cost |
| card.times_retrieved += 1 |
|
|
| if memory_sections: |
| result.system_sections.append( |
| "## Learned Knowledge\n" + "\n".join(memory_sections) |
| ) |
|
|
| result.memories_included = len(result.included_memory_ids) |
| result.budget_remaining = remaining |
| result.total_tokens_estimated = (self.token_budget - remaining) |
|
|
| return result |
|
|
| def _format_memory(self, card: MemoryCard) -> str: |
| """Format a single memory card for prompt inclusion.""" |
| if card.kind == MemoryKind.SKILL_CARD: |
| text = f"- Skill: When {card.pattern}, do: {card.strategy}" |
| if card.steps: |
| text += " Steps: " + "; ".join(card.steps[:3]) |
| elif card.kind == MemoryKind.USER_PREFERENCE: |
| text = f"- User preference: {card.content or card.strategy}" |
| elif card.kind == MemoryKind.FAILURE_PATTERN: |
| text = f"- Avoid: {card.pattern} — {card.strategy}" |
| elif card.kind == MemoryKind.TOOL_POLICY: |
| text = f"- Tool tip ({', '.join(card.scope.tool_names)}): {card.strategy}" |
| elif card.kind == MemoryKind.PURPOSE_CONTRACT: |
| text = f"- Goal constraint: {card.content or card.strategy}" |
| elif card.kind == MemoryKind.CRITIC_CALIBRATION: |
| text = f"- Scoring note: {card.content or card.strategy}" |
| else: |
| text = f"- [{card.kind.value}] {card.content or card.strategy}" |
| return text |
|
|
| def _diverse_select( |
| self, candidates: list[MemoryCard], max_n: int |
| ) -> list[MemoryCard]: |
| """Select diverse memories — avoid near-duplicates.""" |
| if len(candidates) <= max_n: |
| return candidates |
|
|
| selected: list[MemoryCard] = [] |
| seen_patterns: set[str] = set() |
|
|
| for card in candidates: |
| |
| key = (card.pattern or card.content or "")[:50].lower().strip() |
| if key in seen_patterns: |
| continue |
| seen_patterns.add(key) |
| selected.append(card) |
| if len(selected) >= max_n: |
| break |
|
|
| return selected |
|
|