chuk-ai-session-manager 0.7.1__py3-none-any.whl → 0.8.1__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.
Files changed (46) hide show
  1. chuk_ai_session_manager/__init__.py +84 -40
  2. chuk_ai_session_manager/api/__init__.py +1 -1
  3. chuk_ai_session_manager/api/simple_api.py +53 -59
  4. chuk_ai_session_manager/exceptions.py +31 -17
  5. chuk_ai_session_manager/guards/__init__.py +118 -0
  6. chuk_ai_session_manager/guards/bindings.py +217 -0
  7. chuk_ai_session_manager/guards/cache.py +163 -0
  8. chuk_ai_session_manager/guards/manager.py +819 -0
  9. chuk_ai_session_manager/guards/models.py +498 -0
  10. chuk_ai_session_manager/guards/ungrounded.py +159 -0
  11. chuk_ai_session_manager/infinite_conversation.py +86 -79
  12. chuk_ai_session_manager/memory/__init__.py +247 -0
  13. chuk_ai_session_manager/memory/artifacts_bridge.py +469 -0
  14. chuk_ai_session_manager/memory/context_packer.py +347 -0
  15. chuk_ai_session_manager/memory/fault_handler.py +507 -0
  16. chuk_ai_session_manager/memory/manifest.py +307 -0
  17. chuk_ai_session_manager/memory/models.py +1084 -0
  18. chuk_ai_session_manager/memory/mutation_log.py +186 -0
  19. chuk_ai_session_manager/memory/pack_cache.py +206 -0
  20. chuk_ai_session_manager/memory/page_table.py +275 -0
  21. chuk_ai_session_manager/memory/prefetcher.py +192 -0
  22. chuk_ai_session_manager/memory/tlb.py +247 -0
  23. chuk_ai_session_manager/memory/vm_prompts.py +238 -0
  24. chuk_ai_session_manager/memory/working_set.py +574 -0
  25. chuk_ai_session_manager/models/__init__.py +21 -9
  26. chuk_ai_session_manager/models/event_source.py +3 -1
  27. chuk_ai_session_manager/models/event_type.py +10 -1
  28. chuk_ai_session_manager/models/session.py +103 -68
  29. chuk_ai_session_manager/models/session_event.py +69 -68
  30. chuk_ai_session_manager/models/session_metadata.py +9 -10
  31. chuk_ai_session_manager/models/session_run.py +21 -22
  32. chuk_ai_session_manager/models/token_usage.py +76 -76
  33. chuk_ai_session_manager/procedural_memory/__init__.py +70 -0
  34. chuk_ai_session_manager/procedural_memory/formatter.py +407 -0
  35. chuk_ai_session_manager/procedural_memory/manager.py +523 -0
  36. chuk_ai_session_manager/procedural_memory/models.py +371 -0
  37. chuk_ai_session_manager/sample_tools.py +79 -46
  38. chuk_ai_session_manager/session_aware_tool_processor.py +27 -16
  39. chuk_ai_session_manager/session_manager.py +259 -232
  40. chuk_ai_session_manager/session_prompt_builder.py +163 -111
  41. chuk_ai_session_manager/session_storage.py +45 -52
  42. {chuk_ai_session_manager-0.7.1.dist-info → chuk_ai_session_manager-0.8.1.dist-info}/METADATA +80 -4
  43. chuk_ai_session_manager-0.8.1.dist-info/RECORD +45 -0
  44. {chuk_ai_session_manager-0.7.1.dist-info → chuk_ai_session_manager-0.8.1.dist-info}/WHEEL +1 -1
  45. chuk_ai_session_manager-0.7.1.dist-info/RECORD +0 -22
  46. {chuk_ai_session_manager-0.7.1.dist-info → chuk_ai_session_manager-0.8.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,469 @@
1
+ # chuk_ai_session_manager/memory/artifacts_bridge.py
2
+ """
3
+ Artifacts Bridge for AI Virtual Memory.
4
+
5
+ The ArtifactsBridge integrates with chuk-artifacts for persistent page storage.
6
+ It handles storing/loading pages to/from the L3 and L4 tiers.
7
+
8
+ When chuk-artifacts is not installed, falls back to in-memory storage.
9
+
10
+ Design principles:
11
+ - Async-native: All I/O operations are async
12
+ - Optional dependency: Works without chuk-artifacts
13
+ - Pydantic-native: All models are BaseModel subclasses
14
+ - No magic strings: Uses constants for MIME types
15
+ """
16
+
17
+ from datetime import datetime
18
+ from typing import Any, Dict, List, Optional, Protocol
19
+
20
+ from pydantic import BaseModel, Field
21
+
22
+ from .models import (
23
+ MEMORY_PAGE_MIME_TYPE,
24
+ VM_CHECKPOINT_MIME_TYPE,
25
+ MemoryPage,
26
+ StorageStats,
27
+ StorageTier,
28
+ )
29
+
30
+ # Try to import chuk-artifacts
31
+ try:
32
+ from chuk_artifacts import ArtifactStore
33
+ from chuk_artifacts.models import StorageScope
34
+
35
+ ARTIFACTS_AVAILABLE = True
36
+ except ImportError:
37
+ ARTIFACTS_AVAILABLE = False
38
+ ArtifactStore = None # type: ignore
39
+ StorageScope = None # type: ignore
40
+
41
+
42
+ class StorageBackend(Protocol):
43
+ """Protocol for page storage backends."""
44
+
45
+ async def store(
46
+ self,
47
+ page: MemoryPage,
48
+ tier: StorageTier,
49
+ ) -> str:
50
+ """Store a page and return its artifact_id."""
51
+ ...
52
+
53
+ async def load(
54
+ self,
55
+ artifact_id: str,
56
+ ) -> Optional[MemoryPage]:
57
+ """Load a page by artifact_id."""
58
+ ...
59
+
60
+ async def delete(
61
+ self,
62
+ artifact_id: str,
63
+ ) -> bool:
64
+ """Delete a page by artifact_id."""
65
+ ...
66
+
67
+
68
+ class PageMetadata(BaseModel):
69
+ """Metadata stored with a page in artifacts."""
70
+
71
+ page_id: str
72
+ modality: str
73
+ compression_level: int
74
+ tier: str
75
+ session_id: Optional[str] = None
76
+
77
+
78
+ class CheckpointMetadata(BaseModel):
79
+ """Metadata stored with a checkpoint."""
80
+
81
+ checkpoint_name: str
82
+ page_count: int
83
+ session_id: Optional[str] = None
84
+
85
+
86
+ class CheckpointEntry(BaseModel):
87
+ """Entry for a page in a checkpoint manifest."""
88
+
89
+ page_id: str
90
+ artifact_id: str
91
+ modality: str
92
+
93
+
94
+ class CheckpointManifest(BaseModel):
95
+ """Manifest for a checkpoint."""
96
+
97
+ name: str
98
+ created_at: datetime
99
+ page_count: int
100
+ pages: List[CheckpointEntry] = Field(default_factory=list)
101
+
102
+
103
+ class InMemoryBackend(BaseModel):
104
+ """
105
+ Simple in-memory storage backend for testing/development.
106
+
107
+ Not persistent - pages are lost when process exits.
108
+ """
109
+
110
+ pages: Dict[str, bytes] = Field(default_factory=dict)
111
+ counter: int = Field(default=0)
112
+
113
+ model_config = {"arbitrary_types_allowed": True}
114
+
115
+ async def store(
116
+ self,
117
+ page: MemoryPage,
118
+ tier: StorageTier,
119
+ ) -> str:
120
+ """Store a page in memory."""
121
+ self.counter += 1
122
+ artifact_id = f"mem_{tier.value}_{self.counter}"
123
+
124
+ # Serialize page to JSON bytes
125
+ data = page.model_dump_json().encode("utf-8")
126
+ self.pages[artifact_id] = data
127
+
128
+ return artifact_id
129
+
130
+ async def load(
131
+ self,
132
+ artifact_id: str,
133
+ ) -> Optional[MemoryPage]:
134
+ """Load a page from memory."""
135
+ data = self.pages.get(artifact_id)
136
+ if not data:
137
+ return None
138
+
139
+ # Deserialize
140
+ return MemoryPage.model_validate_json(data)
141
+
142
+ async def delete(
143
+ self,
144
+ artifact_id: str,
145
+ ) -> bool:
146
+ """Delete a page from memory."""
147
+ if artifact_id in self.pages:
148
+ del self.pages[artifact_id]
149
+ return True
150
+ return False
151
+
152
+ def clear(self) -> None:
153
+ """Clear all stored pages."""
154
+ self.pages.clear()
155
+ self.counter = 0
156
+
157
+
158
+ class ArtifactsBridge(BaseModel):
159
+ """
160
+ Bridge between VM and chuk-artifacts storage.
161
+
162
+ Handles page serialization/deserialization and tier-appropriate storage.
163
+
164
+ Usage:
165
+ # With chuk-artifacts
166
+ bridge = ArtifactsBridge()
167
+ await bridge.configure(artifact_store)
168
+
169
+ # Without chuk-artifacts (in-memory fallback)
170
+ bridge = ArtifactsBridge()
171
+ await bridge.configure() # Uses InMemoryBackend
172
+
173
+ # Store a page
174
+ artifact_id = await bridge.store_page(page, StorageTier.L3)
175
+
176
+ # Load a page
177
+ page = await bridge.load_page(artifact_id)
178
+ """
179
+
180
+ # Configuration
181
+ session_id: Optional[str] = None
182
+ _backend: Optional[InMemoryBackend] = None
183
+ _artifact_store: Optional[Any] = None
184
+ _using_artifacts: bool = False
185
+
186
+ model_config = {"arbitrary_types_allowed": True}
187
+
188
+ async def configure(
189
+ self,
190
+ artifact_store: Optional[Any] = None,
191
+ session_id: Optional[str] = None,
192
+ ) -> None:
193
+ """
194
+ Configure the bridge with a storage backend.
195
+
196
+ Args:
197
+ artifact_store: Optional ArtifactStore instance
198
+ session_id: Optional session ID for scoping
199
+
200
+ If no artifact_store is provided, uses in-memory backend.
201
+ """
202
+ self.session_id = session_id
203
+
204
+ if artifact_store and ARTIFACTS_AVAILABLE:
205
+ self._artifact_store = artifact_store
206
+ self._using_artifacts = True
207
+ else:
208
+ self._backend = InMemoryBackend()
209
+ self._using_artifacts = False
210
+
211
+ @property
212
+ def is_persistent(self) -> bool:
213
+ """Check if using persistent storage."""
214
+ return self._using_artifacts
215
+
216
+ async def store_page(
217
+ self,
218
+ page: MemoryPage,
219
+ tier: StorageTier = StorageTier.L3,
220
+ ) -> str:
221
+ """
222
+ Store a page to the specified tier.
223
+
224
+ Args:
225
+ page: MemoryPage to store
226
+ tier: Target storage tier (L3 or L4)
227
+
228
+ Returns:
229
+ Artifact ID for later retrieval
230
+ """
231
+ if self._using_artifacts and self._artifact_store:
232
+ return await self._store_with_artifacts(page, tier)
233
+ elif self._backend:
234
+ return await self._backend.store(page, tier)
235
+ else:
236
+ raise RuntimeError("ArtifactsBridge not configured")
237
+
238
+ async def _store_with_artifacts(
239
+ self,
240
+ page: MemoryPage,
241
+ tier: StorageTier,
242
+ ) -> str:
243
+ """Store using chuk-artifacts."""
244
+ # Serialize page
245
+ data = page.model_dump_json().encode("utf-8")
246
+
247
+ # Determine scope based on tier
248
+ if StorageScope:
249
+ scope = (
250
+ StorageScope.SESSION if tier == StorageTier.L3 else StorageScope.SANDBOX
251
+ )
252
+ else:
253
+ scope = "session"
254
+
255
+ # Build metadata
256
+ metadata = PageMetadata(
257
+ page_id=page.page_id,
258
+ modality=page.modality.value,
259
+ compression_level=page.compression_level
260
+ if isinstance(page.compression_level, int)
261
+ else page.compression_level.value,
262
+ tier=tier.value,
263
+ session_id=self.session_id,
264
+ )
265
+
266
+ # Store in artifacts
267
+ artifact_id = await self._artifact_store.store(
268
+ data=data,
269
+ mime=MEMORY_PAGE_MIME_TYPE,
270
+ scope=scope,
271
+ summary=f"VM page: {page.page_id}",
272
+ metadata=metadata.model_dump(),
273
+ )
274
+
275
+ return artifact_id
276
+
277
+ async def load_page(
278
+ self,
279
+ artifact_id: str,
280
+ ) -> Optional[MemoryPage]:
281
+ """
282
+ Load a page by artifact ID.
283
+
284
+ Args:
285
+ artifact_id: ID returned from store_page
286
+
287
+ Returns:
288
+ MemoryPage or None if not found
289
+ """
290
+ if self._using_artifacts and self._artifact_store:
291
+ return await self._load_with_artifacts(artifact_id)
292
+ elif self._backend:
293
+ return await self._backend.load(artifact_id)
294
+ else:
295
+ raise RuntimeError("ArtifactsBridge not configured")
296
+
297
+ async def _load_with_artifacts(
298
+ self,
299
+ artifact_id: str,
300
+ ) -> Optional[MemoryPage]:
301
+ """Load using chuk-artifacts."""
302
+ try:
303
+ # Get artifact data
304
+ data = await self._artifact_store.retrieve(artifact_id)
305
+ if not data:
306
+ return None
307
+
308
+ # Deserialize
309
+ if isinstance(data, bytes):
310
+ return MemoryPage.model_validate_json(data)
311
+ elif isinstance(data, str):
312
+ return MemoryPage.model_validate_json(data.encode())
313
+ else:
314
+ return None
315
+ except Exception:
316
+ return None
317
+
318
+ async def delete_page(
319
+ self,
320
+ artifact_id: str,
321
+ ) -> bool:
322
+ """
323
+ Delete a stored page.
324
+
325
+ Args:
326
+ artifact_id: ID of the artifact to delete
327
+
328
+ Returns:
329
+ True if deleted, False if not found
330
+ """
331
+ if self._using_artifacts and self._artifact_store:
332
+ try:
333
+ await self._artifact_store.delete(artifact_id)
334
+ return True
335
+ except Exception:
336
+ return False
337
+ elif self._backend:
338
+ return await self._backend.delete(artifact_id)
339
+ else:
340
+ raise RuntimeError("ArtifactsBridge not configured")
341
+
342
+ async def store_checkpoint(
343
+ self,
344
+ pages: List[MemoryPage],
345
+ checkpoint_name: str,
346
+ ) -> str:
347
+ """
348
+ Store a checkpoint of multiple pages.
349
+
350
+ Args:
351
+ pages: List of pages to checkpoint
352
+ checkpoint_name: Name for the checkpoint
353
+
354
+ Returns:
355
+ Checkpoint artifact ID
356
+ """
357
+ # Create checkpoint manifest
358
+ manifest = CheckpointManifest(
359
+ name=checkpoint_name,
360
+ created_at=datetime.utcnow(),
361
+ page_count=len(pages),
362
+ pages=[],
363
+ )
364
+
365
+ # Store each page and record artifact IDs
366
+ for page in pages:
367
+ artifact_id = await self.store_page(page, StorageTier.L3)
368
+ manifest.pages.append(
369
+ CheckpointEntry(
370
+ page_id=page.page_id,
371
+ artifact_id=artifact_id,
372
+ modality=page.modality.value,
373
+ )
374
+ )
375
+
376
+ # Store manifest itself
377
+ manifest_data = manifest.model_dump_json().encode("utf-8")
378
+
379
+ if self._using_artifacts and self._artifact_store:
380
+ if StorageScope:
381
+ scope = StorageScope.SESSION
382
+ else:
383
+ scope = "session"
384
+
385
+ checkpoint_metadata = CheckpointMetadata(
386
+ checkpoint_name=checkpoint_name,
387
+ page_count=len(pages),
388
+ session_id=self.session_id,
389
+ )
390
+
391
+ checkpoint_id = await self._artifact_store.store(
392
+ data=manifest_data,
393
+ mime=VM_CHECKPOINT_MIME_TYPE,
394
+ scope=scope,
395
+ summary=f"VM checkpoint: {checkpoint_name}",
396
+ metadata=checkpoint_metadata.model_dump(),
397
+ )
398
+ return checkpoint_id
399
+ else:
400
+ # In-memory: store manifest directly
401
+ if self._backend:
402
+ self._backend.counter += 1
403
+ checkpoint_id = f"checkpoint_{self._backend.counter}"
404
+ self._backend.pages[checkpoint_id] = manifest_data
405
+ return checkpoint_id
406
+ raise RuntimeError("ArtifactsBridge not configured")
407
+
408
+ async def load_checkpoint(
409
+ self,
410
+ checkpoint_id: str,
411
+ ) -> List[MemoryPage]:
412
+ """
413
+ Load all pages from a checkpoint.
414
+
415
+ Args:
416
+ checkpoint_id: ID from store_checkpoint
417
+
418
+ Returns:
419
+ List of MemoryPages
420
+ """
421
+ # Load manifest
422
+ manifest_data: Optional[bytes] = None
423
+ if self._using_artifacts and self._artifact_store:
424
+ manifest_data = await self._artifact_store.retrieve(checkpoint_id)
425
+ elif self._backend:
426
+ manifest_data = self._backend.pages.get(checkpoint_id)
427
+ else:
428
+ raise RuntimeError("ArtifactsBridge not configured")
429
+
430
+ if not manifest_data:
431
+ return []
432
+
433
+ if isinstance(manifest_data, bytes):
434
+ manifest_str = manifest_data.decode("utf-8")
435
+ else:
436
+ manifest_str = manifest_data
437
+
438
+ manifest = CheckpointManifest.model_validate_json(manifest_str)
439
+
440
+ # Load each page
441
+ pages: List[MemoryPage] = []
442
+ for entry in manifest.pages:
443
+ page = await self.load_page(entry.artifact_id)
444
+ if page:
445
+ pages.append(page)
446
+
447
+ return pages
448
+
449
+ def get_stats(self) -> StorageStats:
450
+ """Get storage statistics."""
451
+ if self._using_artifacts:
452
+ return StorageStats(
453
+ backend="chuk-artifacts",
454
+ persistent=True,
455
+ session_id=self.session_id,
456
+ pages_stored=0, # Would need to query artifact store
457
+ )
458
+ elif self._backend:
459
+ return StorageStats(
460
+ backend="in-memory",
461
+ persistent=False,
462
+ session_id=self.session_id,
463
+ pages_stored=len(self._backend.pages),
464
+ )
465
+ else:
466
+ return StorageStats(
467
+ backend="unconfigured",
468
+ persistent=False,
469
+ )