contextos-memory-runtime 1.0.0rc2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- contextos/__init__.py +3 -0
- contextos/__main__.py +6 -0
- contextos/api/__init__.py +1 -0
- contextos/api/routes/__init__.py +1 -0
- contextos/api/routes/desktop.py +322 -0
- contextos/api/routes/ingest.py +17 -0
- contextos/api/routes/memories.py +84 -0
- contextos/api/routes/models.py +81 -0
- contextos/api/routes/retrieval.py +89 -0
- contextos/api/routes/system.py +216 -0
- contextos/api/server.py +195 -0
- contextos/benchmarks/__init__.py +1 -0
- contextos/benchmarks/compilation.py +245 -0
- contextos/benchmarks/connectors.py +423 -0
- contextos/benchmarks/explainability.py +103 -0
- contextos/benchmarks/final.py +406 -0
- contextos/benchmarks/graph.py +310 -0
- contextos/benchmarks/graph_adversarial.py +525 -0
- contextos/benchmarks/mcp.py +324 -0
- contextos/benchmarks/model_routing.py +203 -0
- contextos/benchmarks/optimization.py +305 -0
- contextos/benchmarks/rescue_integration.py +127 -0
- contextos/benchmarks/retrieval.py +266 -0
- contextos/benchmarks/temporal.py +377 -0
- contextos/benchmarks/temporal_hotpath.py +76 -0
- contextos/benchmarks/terminal.py +62 -0
- contextos/cli/__init__.py +1 -0
- contextos/cli/app.py +932 -0
- contextos/cli/dashboard.py +174 -0
- contextos/cli/formatters.py +299 -0
- contextos/config/__init__.py +1 -0
- contextos/config/settings.py +160 -0
- contextos/connectors/__init__.py +6 -0
- contextos/connectors/fake.py +11 -0
- contextos/connectors/json_import.py +125 -0
- contextos/connectors/local_files.py +102 -0
- contextos/connectors/manager.py +293 -0
- contextos/connectors/models.py +62 -0
- contextos/connectors/protocols.py +11 -0
- contextos/core/__init__.py +103 -0
- contextos/core/enums.py +489 -0
- contextos/core/exceptions.py +293 -0
- contextos/core/models.py +1147 -0
- contextos/core/protocols.py +549 -0
- contextos/daemon/__init__.py +1 -0
- contextos/daemon/manager.py +510 -0
- contextos/daemon/state.py +127 -0
- contextos/daemon/wiring.py +296 -0
- contextos/demo.py +217 -0
- contextos/embedding/__init__.py +1 -0
- contextos/embedding/deterministic.py +76 -0
- contextos/embedding/sentence_transformers.py +80 -0
- contextos/mcp/__init__.py +5 -0
- contextos/mcp/server.py +269 -0
- contextos/providers/__init__.py +13 -0
- contextos/providers/fake.py +217 -0
- contextos/providers/ollama.py +297 -0
- contextos/providers/openai_compatible.py +337 -0
- contextos/services/__init__.py +1 -0
- contextos/services/compilation.py +535 -0
- contextos/services/explainability.py +553 -0
- contextos/services/extraction.py +311 -0
- contextos/services/graph.py +524 -0
- contextos/services/graph_retrieval.py +143 -0
- contextos/services/ingestion.py +143 -0
- contextos/services/inspection.py +174 -0
- contextos/services/memory.py +291 -0
- contextos/services/model_service.py +409 -0
- contextos/services/optimization.py +426 -0
- contextos/services/privacy.py +331 -0
- contextos/services/retrieval.py +302 -0
- contextos/services/retrieval_index.py +88 -0
- contextos/services/router.py +302 -0
- contextos/services/secret_scanner.py +207 -0
- contextos/services/telemetry_query.py +102 -0
- contextos/services/temporal.py +500 -0
- contextos/services/token_counter.py +222 -0
- contextos/storage/__init__.py +1 -0
- contextos/storage/connector_repo.py +67 -0
- contextos/storage/database.py +497 -0
- contextos/storage/event_repo.py +137 -0
- contextos/storage/graph_repo.py +228 -0
- contextos/storage/lexical/__init__.py +1 -0
- contextos/storage/lexical/bm25.py +134 -0
- contextos/storage/memory_repo.py +589 -0
- contextos/storage/relation_repo.py +80 -0
- contextos/storage/telemetry_repo.py +481 -0
- contextos/storage/vector/__init__.py +1 -0
- contextos/storage/vector/in_memory.py +162 -0
- contextos_memory_runtime-1.0.0rc2.dist-info/METADATA +143 -0
- contextos_memory_runtime-1.0.0rc2.dist-info/RECORD +93 -0
- contextos_memory_runtime-1.0.0rc2.dist-info/WHEEL +4 -0
- contextos_memory_runtime-1.0.0rc2.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,549 @@
|
|
|
1
|
+
"""Component protocols (interfaces) for ContextOS.
|
|
2
|
+
|
|
3
|
+
Every major component communicates through a Protocol defined here.
|
|
4
|
+
Concrete implementations are injected at startup via daemon/wiring.py.
|
|
5
|
+
|
|
6
|
+
Protocol rules:
|
|
7
|
+
- All methods that touch I/O are async.
|
|
8
|
+
- Protocols are minimal — they define the contract, not convenience methods.
|
|
9
|
+
- Return types are domain models from core.models, never raw dicts or tuples.
|
|
10
|
+
- Protocols are testable: contract test suites verify any implementation.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from datetime import datetime
|
|
16
|
+
from typing import Any, Protocol, runtime_checkable
|
|
17
|
+
from uuid import UUID
|
|
18
|
+
|
|
19
|
+
from contextos.core.enums import (
|
|
20
|
+
GraphRelationType,
|
|
21
|
+
MemoryStatus,
|
|
22
|
+
MemoryType,
|
|
23
|
+
OptimizationStrategy,
|
|
24
|
+
RoutingPolicy,
|
|
25
|
+
SourceRole,
|
|
26
|
+
)
|
|
27
|
+
from contextos.core.models import (
|
|
28
|
+
CandidateMemory,
|
|
29
|
+
CompiledContext,
|
|
30
|
+
CompilationConfig,
|
|
31
|
+
ContextBudget,
|
|
32
|
+
EventFilters,
|
|
33
|
+
GraphEdge,
|
|
34
|
+
GraphExpansion,
|
|
35
|
+
GraphNode,
|
|
36
|
+
IngestRequest,
|
|
37
|
+
IngestResult,
|
|
38
|
+
LexicalResult,
|
|
39
|
+
Memory,
|
|
40
|
+
MemoryFilters,
|
|
41
|
+
MemoryRelation,
|
|
42
|
+
MemoryUpdate,
|
|
43
|
+
ModelCapabilities,
|
|
44
|
+
ModelInvocationTelemetry,
|
|
45
|
+
ModelRequest,
|
|
46
|
+
ModelResponse,
|
|
47
|
+
RawEvent,
|
|
48
|
+
RetrievalConfig,
|
|
49
|
+
RetrievalQuery,
|
|
50
|
+
RetrievalResult,
|
|
51
|
+
RouteDecision,
|
|
52
|
+
SelectionResult,
|
|
53
|
+
ScanResult,
|
|
54
|
+
ScoredMemory,
|
|
55
|
+
StageTrace,
|
|
56
|
+
TelemetrySummary,
|
|
57
|
+
TemporalDecision,
|
|
58
|
+
TemporalResolutionResult,
|
|
59
|
+
MemorySlot,
|
|
60
|
+
VectorResult,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# ---------------------------------------------------------------------------
|
|
65
|
+
# Storage Protocols
|
|
66
|
+
# ---------------------------------------------------------------------------
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@runtime_checkable
|
|
70
|
+
class MemoryRepository(Protocol):
|
|
71
|
+
"""Persistence layer for Memory objects (SQLite)."""
|
|
72
|
+
|
|
73
|
+
async def get(self, memory_id: UUID) -> Memory | None: ...
|
|
74
|
+
|
|
75
|
+
async def list(self, filters: MemoryFilters) -> list[Memory]: ...
|
|
76
|
+
|
|
77
|
+
async def create(self, memory: Memory) -> Memory: ...
|
|
78
|
+
|
|
79
|
+
async def update(self, memory_id: UUID, update: MemoryUpdate, expected_version: int) -> Memory:
|
|
80
|
+
"""Update a memory. Raises ConcurrencyError if version doesn't match."""
|
|
81
|
+
...
|
|
82
|
+
|
|
83
|
+
async def update_status(
|
|
84
|
+
self, memory_id: UUID, new_status: MemoryStatus, expected_version: int
|
|
85
|
+
) -> Memory: ...
|
|
86
|
+
|
|
87
|
+
async def supersede(
|
|
88
|
+
self, old_id: UUID, successor: Memory, expected_version: int
|
|
89
|
+
) -> Memory: ...
|
|
90
|
+
|
|
91
|
+
async def update_access(self, memory_id: UUID) -> None:
|
|
92
|
+
"""Increment access_count and update last_accessed_at."""
|
|
93
|
+
...
|
|
94
|
+
|
|
95
|
+
async def delete(self, memory_id: UUID) -> None:
|
|
96
|
+
"""Remove from database entirely (hard delete at storage level)."""
|
|
97
|
+
...
|
|
98
|
+
|
|
99
|
+
async def count(self, filters: MemoryFilters | None = None) -> int: ...
|
|
100
|
+
|
|
101
|
+
async def get_by_hash(self, content_hash: str) -> Memory | None:
|
|
102
|
+
"""Find a memory by its content hash. Used for exact dedup."""
|
|
103
|
+
...
|
|
104
|
+
|
|
105
|
+
async def list_by_slot(self, slot_key: str) -> list[Memory]: ...
|
|
106
|
+
|
|
107
|
+
async def list_temporal(self, *, limit: int = 500) -> list[Memory]: ...
|
|
108
|
+
|
|
109
|
+
async def apply_temporal_decision(
|
|
110
|
+
self, candidate: Memory, decision: TemporalDecision
|
|
111
|
+
) -> TemporalResolutionResult: ...
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@runtime_checkable
|
|
115
|
+
class EventRepository(Protocol):
|
|
116
|
+
"""Append-only persistence for RawEvent objects."""
|
|
117
|
+
|
|
118
|
+
async def append(self, event: RawEvent) -> None: ...
|
|
119
|
+
|
|
120
|
+
async def get(self, event_id: UUID) -> RawEvent | None: ...
|
|
121
|
+
|
|
122
|
+
async def list(self, filters: EventFilters) -> list[RawEvent]: ...
|
|
123
|
+
|
|
124
|
+
async def count(self, filters: EventFilters | None = None) -> int: ...
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@runtime_checkable
|
|
128
|
+
class RelationRepository(Protocol):
|
|
129
|
+
"""Persistence for MemoryRelation objects."""
|
|
130
|
+
|
|
131
|
+
async def create(self, relation: MemoryRelation) -> MemoryRelation: ...
|
|
132
|
+
|
|
133
|
+
async def get_relations(
|
|
134
|
+
self, memory_id: UUID, direction: str = "both"
|
|
135
|
+
) -> list[MemoryRelation]:
|
|
136
|
+
"""Get relations for a memory.
|
|
137
|
+
|
|
138
|
+
direction: 'outgoing', 'incoming', or 'both'.
|
|
139
|
+
"""
|
|
140
|
+
...
|
|
141
|
+
|
|
142
|
+
async def delete_for_memory(self, memory_id: UUID) -> int:
|
|
143
|
+
"""Delete all relations involving this memory. Returns count deleted."""
|
|
144
|
+
...
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
@runtime_checkable
|
|
148
|
+
class GraphRepository(Protocol):
|
|
149
|
+
"""Persistence contract for the rebuildable graph projection."""
|
|
150
|
+
|
|
151
|
+
async def replace_all(self, nodes: list[GraphNode], edges: list[GraphEdge]) -> None: ...
|
|
152
|
+
|
|
153
|
+
async def nodes(self) -> list[GraphNode]: ...
|
|
154
|
+
|
|
155
|
+
async def find_nodes(self, canonical_keys: set[str]) -> list[GraphNode]: ...
|
|
156
|
+
|
|
157
|
+
async def edges_for_nodes(
|
|
158
|
+
self, node_ids: set[UUID], *, limit: int | None = None
|
|
159
|
+
) -> list[GraphEdge]: ...
|
|
160
|
+
|
|
161
|
+
async def all_edges(self) -> list[GraphEdge]: ...
|
|
162
|
+
|
|
163
|
+
async def counts(self) -> tuple[int, int, int]: ...
|
|
164
|
+
|
|
165
|
+
async def source_is_dirty(self) -> bool: ...
|
|
166
|
+
|
|
167
|
+
@runtime_checkable
|
|
168
|
+
class VectorStore(Protocol):
|
|
169
|
+
"""Vector storage and similarity search."""
|
|
170
|
+
|
|
171
|
+
async def add(
|
|
172
|
+
self, ids: list[str], vectors: list[list[float]], metadata: list[dict[str, Any]]
|
|
173
|
+
) -> None: ...
|
|
174
|
+
|
|
175
|
+
async def search(
|
|
176
|
+
self,
|
|
177
|
+
vector: list[float],
|
|
178
|
+
top_k: int = 20,
|
|
179
|
+
filters: dict[str, Any] | None = None,
|
|
180
|
+
) -> list[VectorResult]: ...
|
|
181
|
+
|
|
182
|
+
async def delete(self, ids: list[str]) -> None: ...
|
|
183
|
+
|
|
184
|
+
async def count(self) -> int: ...
|
|
185
|
+
|
|
186
|
+
async def rebuild(
|
|
187
|
+
self, ids: list[str], vectors: list[list[float]], metadata: list[dict[str, Any]]
|
|
188
|
+
) -> None: ...
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
@runtime_checkable
|
|
192
|
+
class LexicalIndex(Protocol):
|
|
193
|
+
"""Full-text / BM25 lexical search index."""
|
|
194
|
+
|
|
195
|
+
async def index(self, doc_id: str, text: str, metadata: dict[str, Any] | None = None) -> None:
|
|
196
|
+
...
|
|
197
|
+
|
|
198
|
+
async def search(
|
|
199
|
+
self,
|
|
200
|
+
query: str,
|
|
201
|
+
top_k: int = 20,
|
|
202
|
+
filters: dict[str, Any] | None = None,
|
|
203
|
+
) -> list[LexicalResult]: ...
|
|
204
|
+
|
|
205
|
+
async def delete(self, doc_id: str) -> None: ...
|
|
206
|
+
|
|
207
|
+
async def count(self) -> int: ...
|
|
208
|
+
|
|
209
|
+
async def rebuild(self, documents: dict[str, str]) -> None:
|
|
210
|
+
"""Rebuild the entire index from a dict of {id: text}."""
|
|
211
|
+
...
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
# ---------------------------------------------------------------------------
|
|
215
|
+
# Service Protocols
|
|
216
|
+
# ---------------------------------------------------------------------------
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
@runtime_checkable
|
|
220
|
+
class IngestionService(Protocol):
|
|
221
|
+
"""End-to-end ingestion pipeline: validate → scan → store → extract → index."""
|
|
222
|
+
|
|
223
|
+
async def ingest(self, request: IngestRequest) -> IngestResult: ...
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
@runtime_checkable
|
|
227
|
+
class MemoryService(Protocol):
|
|
228
|
+
"""Business logic for memory management, including lifecycle transitions."""
|
|
229
|
+
|
|
230
|
+
async def get(self, memory_id: UUID) -> Memory | None: ...
|
|
231
|
+
|
|
232
|
+
async def list(self, filters: MemoryFilters) -> list[Memory]: ...
|
|
233
|
+
|
|
234
|
+
async def search(self, query: str, limit: int = 50) -> list[ScoredMemory]: ...
|
|
235
|
+
|
|
236
|
+
async def update(self, memory_id: UUID, update: MemoryUpdate) -> Memory: ...
|
|
237
|
+
|
|
238
|
+
async def transition(
|
|
239
|
+
self, memory_id: UUID, new_status: MemoryStatus, reason: str = ""
|
|
240
|
+
) -> Memory:
|
|
241
|
+
"""Transition a memory to a new lifecycle state.
|
|
242
|
+
|
|
243
|
+
Raises InvalidTransitionError if the transition is not allowed.
|
|
244
|
+
"""
|
|
245
|
+
...
|
|
246
|
+
|
|
247
|
+
async def delete(self, memory_id: UUID) -> None:
|
|
248
|
+
"""Soft delete: transition to DELETED, remove from indices."""
|
|
249
|
+
...
|
|
250
|
+
|
|
251
|
+
async def purge(self, memory_id: UUID) -> None:
|
|
252
|
+
"""Hard delete: destroy from all stores."""
|
|
253
|
+
...
|
|
254
|
+
|
|
255
|
+
async def history(self, topic: str) -> list[Memory]:
|
|
256
|
+
"""Temporal evolution of memories related to a topic."""
|
|
257
|
+
...
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
@runtime_checkable
|
|
261
|
+
class RetrievalService(Protocol):
|
|
262
|
+
"""Multi-strategy retrieval with fusion, reranking, and dedup."""
|
|
263
|
+
|
|
264
|
+
async def retrieve(
|
|
265
|
+
self, query: str | RetrievalQuery, config: RetrievalConfig | None = None
|
|
266
|
+
) -> RetrievalResult: ...
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
@runtime_checkable
|
|
270
|
+
class CompilationService(Protocol):
|
|
271
|
+
"""Budget-constrained context compilation from retrieved memories."""
|
|
272
|
+
|
|
273
|
+
async def compile(
|
|
274
|
+
self,
|
|
275
|
+
query: str,
|
|
276
|
+
memories: list[ScoredMemory] | SelectionResult,
|
|
277
|
+
config: CompilationConfig | None = None,
|
|
278
|
+
) -> CompiledContext: ...
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
@runtime_checkable
|
|
282
|
+
class TokenAwareOptimizer(Protocol):
|
|
283
|
+
"""Select whole retrieved memories within a memory-context budget."""
|
|
284
|
+
|
|
285
|
+
def optimize(
|
|
286
|
+
self,
|
|
287
|
+
query: str,
|
|
288
|
+
candidates: list[ScoredMemory],
|
|
289
|
+
budget: ContextBudget,
|
|
290
|
+
strategy: OptimizationStrategy = OptimizationStrategy.CONTEXTOS,
|
|
291
|
+
) -> SelectionResult: ...
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
@runtime_checkable
|
|
295
|
+
class TemporalResolver(Protocol):
|
|
296
|
+
"""Resolve accepted candidates into deterministic temporal timelines."""
|
|
297
|
+
|
|
298
|
+
async def resolve(self, candidate: Memory) -> TemporalResolutionResult: ...
|
|
299
|
+
|
|
300
|
+
async def get_current_state(self, slot: MemorySlot | str) -> list[Memory]: ...
|
|
301
|
+
|
|
302
|
+
async def get_history(self, slot: MemorySlot | str) -> list[Memory]: ...
|
|
303
|
+
|
|
304
|
+
async def get_previous(self, memory_id: UUID) -> Memory | None: ...
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
@runtime_checkable
|
|
308
|
+
class GraphService(Protocol):
|
|
309
|
+
"""Bounded graph projection and traversal contract."""
|
|
310
|
+
|
|
311
|
+
async def rebuild(self) -> tuple[int, int, int]: ...
|
|
312
|
+
|
|
313
|
+
async def upsert_memory(self, memory: Memory) -> tuple[int, int, int]: ...
|
|
314
|
+
|
|
315
|
+
async def remove_memory(self, memory_id: UUID) -> tuple[int, int, int]: ...
|
|
316
|
+
|
|
317
|
+
async def find_entities(self, text: str) -> list[GraphNode]: ...
|
|
318
|
+
|
|
319
|
+
async def neighbors(
|
|
320
|
+
self,
|
|
321
|
+
node_id: UUID,
|
|
322
|
+
*,
|
|
323
|
+
relation_types: set[GraphRelationType] | None = None,
|
|
324
|
+
min_confidence: float = 0.0,
|
|
325
|
+
) -> list[GraphEdge]: ...
|
|
326
|
+
|
|
327
|
+
async def expand(
|
|
328
|
+
self,
|
|
329
|
+
*,
|
|
330
|
+
query_text: str,
|
|
331
|
+
seed_memory_ids: list[UUID] | None = None,
|
|
332
|
+
max_hops: int = 2,
|
|
333
|
+
min_confidence: float = 0.6,
|
|
334
|
+
max_nodes: int = 100,
|
|
335
|
+
max_edges: int = 250,
|
|
336
|
+
relation_types: set[GraphRelationType] | None = None,
|
|
337
|
+
) -> GraphExpansion: ...
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
# ---------------------------------------------------------------------------
|
|
341
|
+
# Infrastructure Protocols
|
|
342
|
+
# ---------------------------------------------------------------------------
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
@runtime_checkable
|
|
346
|
+
class EmbeddingService(Protocol):
|
|
347
|
+
"""Generate embeddings from text. Abstracts over model choice."""
|
|
348
|
+
|
|
349
|
+
async def embed(self, texts: list[str]) -> list[list[float]]:
|
|
350
|
+
"""Embed a batch of texts."""
|
|
351
|
+
...
|
|
352
|
+
|
|
353
|
+
async def embed_query(self, query: str) -> list[float]:
|
|
354
|
+
"""Embed a single query. May use a different prefix/strategy than documents."""
|
|
355
|
+
...
|
|
356
|
+
|
|
357
|
+
@property
|
|
358
|
+
def dimension(self) -> int:
|
|
359
|
+
"""Embedding vector dimension."""
|
|
360
|
+
...
|
|
361
|
+
|
|
362
|
+
@property
|
|
363
|
+
def model_name(self) -> str:
|
|
364
|
+
"""Name of the loaded model."""
|
|
365
|
+
...
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
@runtime_checkable
|
|
369
|
+
class SecretScanner(Protocol):
|
|
370
|
+
"""Detect secrets and sensitive content in text."""
|
|
371
|
+
|
|
372
|
+
def scan(self, text: str) -> ScanResult: ...
|
|
373
|
+
|
|
374
|
+
def redact(self, text: str) -> tuple[str, ScanResult]:
|
|
375
|
+
"""Scan and return (redacted_text, scan_result)."""
|
|
376
|
+
...
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
@runtime_checkable
|
|
380
|
+
class MemoryExtractor(Protocol):
|
|
381
|
+
"""Extract discrete memories from raw text."""
|
|
382
|
+
|
|
383
|
+
async def extract(
|
|
384
|
+
self,
|
|
385
|
+
text: str,
|
|
386
|
+
source_type: str = "cli_input",
|
|
387
|
+
source_uri: str | None = None,
|
|
388
|
+
suggested_type: MemoryType | None = None,
|
|
389
|
+
tags: list[str] | None = None,
|
|
390
|
+
source_role: SourceRole = SourceRole.USER,
|
|
391
|
+
confirmed_user_information: bool = False,
|
|
392
|
+
) -> list[CandidateMemory]: ...
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
@runtime_checkable
|
|
396
|
+
class DuplicateDetector(Protocol):
|
|
397
|
+
"""Detect duplicates and near-duplicates against existing memories."""
|
|
398
|
+
|
|
399
|
+
async def check(
|
|
400
|
+
self, candidate: CandidateMemory, existing_memories: list[Memory] | None = None
|
|
401
|
+
) -> DuplicateCheckResult: ...
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
class DuplicateCheckResult:
|
|
405
|
+
"""Result of a duplicate check."""
|
|
406
|
+
|
|
407
|
+
__slots__ = ("is_duplicate", "is_near_duplicate", "matched_memory_id", "similarity_score")
|
|
408
|
+
|
|
409
|
+
def __init__(
|
|
410
|
+
self,
|
|
411
|
+
is_duplicate: bool = False,
|
|
412
|
+
is_near_duplicate: bool = False,
|
|
413
|
+
matched_memory_id: UUID | None = None,
|
|
414
|
+
similarity_score: float = 0.0,
|
|
415
|
+
) -> None:
|
|
416
|
+
self.is_duplicate = is_duplicate
|
|
417
|
+
self.is_near_duplicate = is_near_duplicate
|
|
418
|
+
self.matched_memory_id = matched_memory_id
|
|
419
|
+
self.similarity_score = similarity_score
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
@runtime_checkable
|
|
423
|
+
class TokenCounter(Protocol):
|
|
424
|
+
"""Count tokens in text. Abstracts over tokenizer choice."""
|
|
425
|
+
|
|
426
|
+
def count(self, text: str) -> int: ...
|
|
427
|
+
|
|
428
|
+
def count_batch(self, texts: list[str]) -> list[int]: ...
|
|
429
|
+
|
|
430
|
+
@property
|
|
431
|
+
def encoding_name(self) -> str: ...
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
# ---------------------------------------------------------------------------
|
|
435
|
+
# Observability Protocol
|
|
436
|
+
# ---------------------------------------------------------------------------
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
@runtime_checkable
|
|
440
|
+
class TraceCollector(Protocol):
|
|
441
|
+
"""Collect and store pipeline traces."""
|
|
442
|
+
|
|
443
|
+
async def record(self, trace_id: str, stage: StageTrace) -> None: ...
|
|
444
|
+
|
|
445
|
+
async def get_traces(self, limit: int = 100) -> list[dict[str, Any]]: ...
|
|
446
|
+
|
|
447
|
+
async def get_stats(self) -> dict[str, Any]: ...
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
# ---------------------------------------------------------------------------
|
|
451
|
+
# Phase 9: Model Runtime, Router, and Telemetry Protocols
|
|
452
|
+
# ---------------------------------------------------------------------------
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
@runtime_checkable
|
|
456
|
+
class ModelProvider(Protocol):
|
|
457
|
+
"""Downstream LLM execution provider interface."""
|
|
458
|
+
|
|
459
|
+
@property
|
|
460
|
+
def provider_id(self) -> str:
|
|
461
|
+
"""Unique provider identifier (e.g. 'fake', 'ollama', 'openai_compatible')."""
|
|
462
|
+
...
|
|
463
|
+
|
|
464
|
+
@property
|
|
465
|
+
def is_local(self) -> bool:
|
|
466
|
+
"""Whether this provider runs locally without external network exfiltration."""
|
|
467
|
+
...
|
|
468
|
+
|
|
469
|
+
async def list_models(self) -> list[ModelCapabilities]:
|
|
470
|
+
"""List models offered by this provider."""
|
|
471
|
+
...
|
|
472
|
+
|
|
473
|
+
async def health(self) -> bool:
|
|
474
|
+
"""Check provider connectivity and readiness."""
|
|
475
|
+
...
|
|
476
|
+
|
|
477
|
+
async def generate(self, request: ModelRequest) -> ModelResponse:
|
|
478
|
+
"""Generate a completion for the given request."""
|
|
479
|
+
...
|
|
480
|
+
|
|
481
|
+
def count_tokens(self, text: str, model: str) -> int:
|
|
482
|
+
"""Count tokens using this provider's tokenizer or tokenizer family."""
|
|
483
|
+
...
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
@runtime_checkable
|
|
487
|
+
class ModelRouter(Protocol):
|
|
488
|
+
"""Deterministic routing protocol to select providers and models."""
|
|
489
|
+
|
|
490
|
+
async def route(
|
|
491
|
+
self,
|
|
492
|
+
request: ModelRequest,
|
|
493
|
+
providers: dict[str, ModelProvider],
|
|
494
|
+
policy: RoutingPolicy | None = None,
|
|
495
|
+
) -> RouteDecision:
|
|
496
|
+
"""Select the target provider and model based on policy and constraints."""
|
|
497
|
+
...
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
@runtime_checkable
|
|
501
|
+
class TelemetryRepository(Protocol):
|
|
502
|
+
"""Persistence repository for model invocation telemetry."""
|
|
503
|
+
|
|
504
|
+
async def record(self, telemetry: ModelInvocationTelemetry) -> None:
|
|
505
|
+
"""Persist a single model invocation telemetry record."""
|
|
506
|
+
...
|
|
507
|
+
|
|
508
|
+
async def get(self, invocation_id: UUID) -> ModelInvocationTelemetry | None:
|
|
509
|
+
"""Retrieve a telemetry record by invocation UUID."""
|
|
510
|
+
...
|
|
511
|
+
|
|
512
|
+
async def list_recent(
|
|
513
|
+
self,
|
|
514
|
+
limit: int = 50,
|
|
515
|
+
model_id: str | None = None,
|
|
516
|
+
provider_id: str | None = None,
|
|
517
|
+
start: datetime | None = None,
|
|
518
|
+
) -> list[ModelInvocationTelemetry]:
|
|
519
|
+
"""List recent invocations in descending chronological order."""
|
|
520
|
+
...
|
|
521
|
+
|
|
522
|
+
async def provider_model_breakdown(
|
|
523
|
+
self,
|
|
524
|
+
start: datetime | None = None,
|
|
525
|
+
provider_id: str | None = None,
|
|
526
|
+
model_id: str | None = None,
|
|
527
|
+
) -> list[dict[str, Any]]: ...
|
|
528
|
+
|
|
529
|
+
async def context_measurement_bases(
|
|
530
|
+
self,
|
|
531
|
+
model_id: str | None = None,
|
|
532
|
+
provider_id: str | None = None,
|
|
533
|
+
start: datetime | None = None,
|
|
534
|
+
) -> list[dict[str, str]]: ...
|
|
535
|
+
|
|
536
|
+
async def count(self) -> int:
|
|
537
|
+
"""Count total recorded invocations."""
|
|
538
|
+
...
|
|
539
|
+
|
|
540
|
+
async def summary(
|
|
541
|
+
self,
|
|
542
|
+
start: datetime | None = None,
|
|
543
|
+
end: datetime | None = None,
|
|
544
|
+
provider_id: str | None = None,
|
|
545
|
+
model_id: str | None = None,
|
|
546
|
+
success_only: bool = False,
|
|
547
|
+
) -> TelemetrySummary:
|
|
548
|
+
"""Compute aggregated token usage, avoidance, and latency statistics."""
|
|
549
|
+
...
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Daemon package for ContextOS."""
|