Coverage for /home/admin/Documents/AI/applications/lexigram-dev/lexigram/experimental/ai/lexigram-ai-memory/src/lexigram/ai/memory/di/provider.py: 74%
70 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-25 07:19 +0800
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-25 07:19 +0800
1"""DI provider for lexigram-ai-memory."""
3from __future__ import annotations
5from typing import TYPE_CHECKING, cast
7from lexigram.ai.memory.backends.in_memory import InMemoryMemoryBackend
8from lexigram.ai.memory.config import MemoryConfig
9from lexigram.ai.memory.consolidation.consolidator import MemoryConsolidator
10from lexigram.ai.memory.consolidation.scheduler import ConsolidationScheduler
11from lexigram.ai.memory.episodic.store import EpisodicMemoryStore
12from lexigram.ai.memory.pruning.pruner import DynamicContextPruner
13from lexigram.ai.memory.retrieval.ranking import RelevanceRanker
14from lexigram.ai.memory.retrieval.retriever import MemoryRetriever
15from lexigram.ai.memory.semantic.store import SemanticMemoryStore
16from lexigram.ai.memory.working.manager import WorkingMemoryManager
17from lexigram.contracts.ai.memory import (
18 EpisodicMemoryProtocol,
19 MemoryConsolidatorProtocol,
20 MemoryStoreProtocol,
21 SemanticMemoryProtocol,
22 WorkingMemoryProtocol,
23)
24from lexigram.contracts.core.health import HealthCheckResult, HealthStatus
25from lexigram.di.provider import Provider, ProviderPriority
26from lexigram.logging import (
27 get_logger,
28)
30if TYPE_CHECKING:
31 from lexigram.contracts.core.di import (
32 ContainerRegistrarProtocol,
33 ContainerResolverProtocol,
34 )
36logger = get_logger(__name__)
39class MemoryProvider(Provider):
40 """Registers all memory services in the DI container.
42 Wires up the MemoryStoreProtocol, EpisodicMemoryProtocol,
43 SemanticMemoryProtocol, WorkingMemoryProtocol, and consolidation
44 services. The default backend is ``InMemoryMemoryBackend``; callers
45 with persistent needs should register a ``MemoryStoreProtocol``
46 override after this provider runs.
48 Note:
49 All memory services are owner-scoped — entries carry an
50 ``owner_id`` and every store operation is restricted to one
51 owner (user, session, or system scope).
52 """
54 name = "ai-memory"
55 priority = ProviderPriority.DOMAIN
56 config_key: str | None = "ai_memory"
57 config_model: type | None = MemoryConfig
59 def __init__(
60 self,
61 config: MemoryConfig | None = None,
62 enable_consolidation: bool = True,
63 ) -> None:
64 """Initialise the provider.
66 Args:
67 config: Memory configuration. Defaults to ``MemoryConfig()``.
68 enable_consolidation: Start the consolidation scheduler during
69 :meth:`boot`. When ``False``, the scheduler is never started
70 regardless of ``config.consolidation.enabled``.
71 """
72 super().__init__()
73 self._requested_config = config
74 self._mem_config = config or MemoryConfig()
75 self._enable_consolidation = enable_consolidation
76 self._scheduler: ConsolidationScheduler | None = None
78 async def register(self, container: ContainerRegistrarProtocol) -> None:
79 """Register memory services.
81 Args:
82 container: DI container registrar.
83 """
84 self._mem_config = self._requested_config or (
85 self.config
86 if isinstance(getattr(self, "config", None), MemoryConfig)
87 else self._mem_config
88 )
89 container.singleton(MemoryConfig, self._mem_config)
91 if not self._mem_config.enabled:
92 logger.info("memory_disabled", reason="MemoryConfig.enabled=False")
93 return
95 # Follow-up: wire cache/database/vector backends at boot phase
96 # (see 2026-08-12-ai-family-gaps-remediation.md Task 5) — requires
97 # resolving CacheBackendProtocol/DatabaseProviderProtocol from the
98 # container, which ContainerRegistrarProtocol does not permit.
99 if self._mem_config.default_backend != "in_memory":
100 logger.warning(
101 "memory_backend_not_implemented",
102 requested_backend=self._mem_config.default_backend,
103 fallback="in_memory",
104 )
106 backend = InMemoryMemoryBackend()
107 episodic = EpisodicMemoryStore(backend=backend)
108 semantic = SemanticMemoryStore(
109 min_confidence=self._mem_config.semantic.min_confidence,
110 max_facts_per_entity=self._mem_config.semantic.max_facts_per_entity,
111 )
112 working = WorkingMemoryManager(
113 episodic=episodic,
114 semantic=cast("SemanticMemoryProtocol", semantic),
115 config=self._mem_config.working,
116 )
117 consolidator = MemoryConsolidator(config=self._mem_config.consolidation)
118 retriever = MemoryRetriever(sources=[backend])
120 container.singleton(MemoryStoreProtocol, lambda: backend)
121 container.singleton(EpisodicMemoryProtocol, lambda: episodic)
122 container.singleton(SemanticMemoryProtocol, lambda: semantic)
123 container.singleton(WorkingMemoryProtocol, lambda: working)
124 container.singleton(MemoryConsolidatorProtocol, lambda: consolidator)
125 container.singleton(MemoryRetriever, lambda: retriever)
126 container.singleton(RelevanceRanker, RelevanceRanker)
127 # Register pruner — the container will auto-inject TokenCounterProtocol
128 container.singleton(DynamicContextPruner, DynamicContextPruner)
130 logger.debug("memory_services_registered")
132 async def boot(self, container: ContainerResolverProtocol) -> None:
133 """Start the consolidation scheduler if enabled.
135 Args:
136 container: Resolved DI container.
137 """
138 if not self._mem_config.enabled:
139 return
141 if self._enable_consolidation and self._mem_config.consolidation.enabled:
142 store = await container.resolve_optional(MemoryStoreProtocol)
143 consolidator = await container.resolve_optional(MemoryConsolidatorProtocol)
144 if store is None or consolidator is None:
145 logger.warning("memory_consolidation_dependencies_missing")
146 return
148 self._scheduler = ConsolidationScheduler(
149 store=store,
150 consolidator=consolidator,
151 config=self._mem_config.consolidation,
152 )
153 await self._scheduler.start()
154 logger.info("memory_consolidation_scheduler_started")
156 async def shutdown(self) -> None:
157 """Stop the consolidation scheduler."""
158 if self._scheduler:
159 await self._scheduler.stop()
160 self._scheduler = None
162 async def health_check(self, timeout: float = 5.0) -> HealthCheckResult:
163 """Health check — always healthy (in-process domain provider).
165 No external backend to ping.
167 Args:
168 timeout: Ignored for in-process providers.
170 Returns:
171 Always HEALTHY — no external backend to ping.
172 """
173 return HealthCheckResult(
174 component=self.name,
175 status=HealthStatus.HEALTHY,
176 details={"status": "operational"},
177 )
180__all__ = ["MemoryProvider"]