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,574 @@
1
+ # chuk_ai_session_manager/memory/working_set.py
2
+ """
3
+ Working Set Manager for AI Virtual Memory.
4
+
5
+ The WorkingSetManager tracks which pages are currently "hot" (in L0/L1)
6
+ and manages capacity constraints. It's the gatekeeper for what's in context.
7
+
8
+ Design principles:
9
+ - Pydantic-native: BaseModel subclass with proper validation
10
+ - Token-aware: Tracks token budget across modalities
11
+ - Eviction-ready: Provides candidates when under pressure
12
+ - Pinning support: Critical pages are never evicted
13
+ - Anti-thrash: Prevent evicting recently faulted pages
14
+ """
15
+
16
+ from datetime import datetime
17
+ from typing import Dict, List, Optional, Set, Tuple
18
+
19
+ from pydantic import BaseModel, Field, PrivateAttr
20
+
21
+ from .models import (
22
+ MemoryPage,
23
+ StorageTier,
24
+ TokenBudget,
25
+ WorkingSetStats,
26
+ )
27
+
28
+
29
+ # =============================================================================
30
+ # Pinned Set
31
+ # =============================================================================
32
+
33
+
34
+ class PinnedSet(BaseModel):
35
+ """
36
+ Pages that are never evicted from working set.
37
+ Pinning prevents thrash on critical context.
38
+
39
+ Auto-pinned by default:
40
+ - System prompt page
41
+ - Active goal/plan page
42
+ - User preferences page
43
+ - Current tool schemas
44
+ - Last N turns (configurable, typically 2-4)
45
+ """
46
+
47
+ # Explicitly pinned pages
48
+ pinned: Set[str] = Field(default_factory=set)
49
+
50
+ # Auto-pin configuration
51
+ auto_pin_last_n_turns: int = Field(
52
+ default=3, description="Auto-pin last N user+assistant turn pairs"
53
+ )
54
+ auto_pin_system_prompt: bool = Field(default=True)
55
+ auto_pin_claims: bool = Field(
56
+ default=True, description="Auto-pin claim pages (high-value)"
57
+ )
58
+
59
+ # Pages auto-pinned (tracked separately for debugging)
60
+ auto_pinned: Set[str] = Field(default_factory=set)
61
+
62
+ def pin(self, page_id: str) -> None:
63
+ """Explicitly pin a page."""
64
+ self.pinned.add(page_id)
65
+
66
+ def unpin(self, page_id: str) -> None:
67
+ """Unpin a page (only affects explicit pins)."""
68
+ self.pinned.discard(page_id)
69
+ self.auto_pinned.discard(page_id)
70
+
71
+ def is_pinned(self, page_id: str) -> bool:
72
+ """Check if a page is pinned (explicitly or auto)."""
73
+ return page_id in self.pinned or page_id in self.auto_pinned
74
+
75
+ def auto_pin(self, page_id: str) -> None:
76
+ """Auto-pin a page (e.g., recent turn, claim)."""
77
+ self.auto_pinned.add(page_id)
78
+
79
+ def clear_auto_pins(self) -> None:
80
+ """Clear all auto-pins (before recalculating)."""
81
+ self.auto_pinned.clear()
82
+
83
+ def get_all_pinned(self) -> Set[str]:
84
+ """Get all pinned pages (explicit + auto)."""
85
+ return self.pinned | self.auto_pinned
86
+
87
+ def count(self) -> int:
88
+ """Total number of pinned pages."""
89
+ return len(self.get_all_pinned())
90
+
91
+
92
+ # =============================================================================
93
+ # Anti-Thrash Policy
94
+ # =============================================================================
95
+
96
+
97
+ class AntiThrashPolicy(BaseModel):
98
+ """
99
+ Prevent evicting pages that were just faulted in.
100
+
101
+ OS working sets fail when you thrash. LLM working sets thrash
102
+ when user toggles between topics.
103
+ """
104
+
105
+ # Recently-evicted pages get a "do not evict again" window
106
+ eviction_cooldown_turns: int = Field(default=3)
107
+
108
+ # Recently-faulted pages get temporary protection
109
+ fault_protection_turns: int = Field(default=2)
110
+
111
+ # Track eviction/fault history: page_id -> turn number
112
+ _eviction_history: Dict[str, int] = PrivateAttr(default_factory=dict)
113
+ _fault_history: Dict[str, int] = PrivateAttr(default_factory=dict)
114
+
115
+ def record_eviction(self, page_id: str, turn: int) -> None:
116
+ """Record that a page was evicted at this turn."""
117
+ self._eviction_history[page_id] = turn
118
+
119
+ def record_fault(self, page_id: str, turn: int) -> None:
120
+ """Record that a page was faulted in at this turn."""
121
+ self._fault_history[page_id] = turn
122
+
123
+ def can_evict(self, page_id: str, current_turn: int) -> bool:
124
+ """Check if page is in cooldown period."""
125
+ # Check fault protection
126
+ if page_id in self._fault_history:
127
+ fault_turn = self._fault_history[page_id]
128
+ if current_turn - fault_turn < self.fault_protection_turns:
129
+ return False
130
+
131
+ # Check eviction cooldown (avoid re-evicting)
132
+ if page_id in self._eviction_history:
133
+ evict_turn = self._eviction_history[page_id]
134
+ if current_turn - evict_turn < self.eviction_cooldown_turns:
135
+ return False
136
+
137
+ return True
138
+
139
+ def get_eviction_penalty(self, page_id: str, current_turn: int) -> float:
140
+ """
141
+ Higher penalty = less likely to evict.
142
+
143
+ Recently faulted = high penalty (we just loaded it!)
144
+ Recently evicted = high penalty (avoid re-evicting)
145
+ """
146
+ penalty = 0.0
147
+
148
+ if page_id in self._fault_history:
149
+ fault_turn = self._fault_history[page_id]
150
+ turns_since = current_turn - fault_turn
151
+ if turns_since < self.fault_protection_turns:
152
+ # High penalty if recently faulted
153
+ penalty += 1.0 - (turns_since / self.fault_protection_turns)
154
+
155
+ if page_id in self._eviction_history:
156
+ evict_turn = self._eviction_history[page_id]
157
+ turns_since = current_turn - evict_turn
158
+ if turns_since < self.eviction_cooldown_turns:
159
+ # Moderate penalty if recently evicted
160
+ penalty += 0.5 * (1.0 - (turns_since / self.eviction_cooldown_turns))
161
+
162
+ return min(1.0, penalty)
163
+
164
+ def cleanup_old_history(self, current_turn: int, max_age: int = 20) -> None:
165
+ """Remove old history entries to prevent memory growth."""
166
+ self._fault_history = {
167
+ pid: turn
168
+ for pid, turn in self._fault_history.items()
169
+ if current_turn - turn <= max_age
170
+ }
171
+ self._eviction_history = {
172
+ pid: turn
173
+ for pid, turn in self._eviction_history.items()
174
+ if current_turn - turn <= max_age
175
+ }
176
+
177
+
178
+ class WorkingSetConfig(BaseModel):
179
+ """Configuration for working set management."""
180
+
181
+ # Token limits
182
+ max_l0_tokens: int = Field(
183
+ default=128_000, description="Maximum tokens in L0 (context window)"
184
+ )
185
+ max_l1_pages: int = Field(default=100, description="Maximum pages in L1 cache")
186
+
187
+ # Eviction thresholds
188
+ eviction_threshold: float = Field(
189
+ default=0.85,
190
+ ge=0.0,
191
+ le=1.0,
192
+ description="Trigger eviction when utilization exceeds this",
193
+ )
194
+ target_utilization: float = Field(
195
+ default=0.70, ge=0.0, le=1.0, description="Target utilization after eviction"
196
+ )
197
+
198
+ # Reserved tokens
199
+ reserved_tokens: int = Field(
200
+ default=4000, description="Reserved for system prompt, tools, etc."
201
+ )
202
+
203
+
204
+ class WorkingSetManager(BaseModel):
205
+ """
206
+ Manages the working set (L0 + L1 pages).
207
+
208
+ The working set is what's currently "hot" - either in the context window
209
+ (L0) or in fast cache (L1). This manager tracks capacity, handles
210
+ promotion/demotion, and identifies eviction candidates.
211
+
212
+ Includes pinning (never evict critical pages) and anti-thrash
213
+ (don't evict recently-faulted pages).
214
+ """
215
+
216
+ config: WorkingSetConfig = Field(default_factory=WorkingSetConfig)
217
+
218
+ # Token budget tracking
219
+ budget: TokenBudget = Field(default_factory=TokenBudget)
220
+
221
+ # L0 pages (in context) - ordered by position
222
+ l0_pages: List[str] = Field(
223
+ default_factory=list, description="Page IDs in L0, ordered"
224
+ )
225
+
226
+ # L1 pages (hot cache) - maps page_id -> MemoryPage
227
+ l1_cache: Dict[str, MemoryPage] = Field(
228
+ default_factory=dict, description="Pages in L1 cache"
229
+ )
230
+
231
+ # Page importance overrides
232
+ importance_overrides: Dict[str, float] = Field(
233
+ default_factory=dict, description="Manual importance adjustments"
234
+ )
235
+
236
+ # Pinned set - pages that are never evicted
237
+ pinned_set: PinnedSet = Field(default_factory=PinnedSet)
238
+
239
+ # Anti-thrash policy
240
+ anti_thrash: AntiThrashPolicy = Field(default_factory=AntiThrashPolicy)
241
+
242
+ # Current turn number (for anti-thrash)
243
+ current_turn: int = Field(default=0)
244
+
245
+ model_config = {"arbitrary_types_allowed": True}
246
+
247
+ def __len__(self) -> int:
248
+ """Total pages in working set."""
249
+ return len(self.l0_pages) + len(self.l1_cache)
250
+
251
+ @property
252
+ def l0_count(self) -> int:
253
+ """Number of pages in L0."""
254
+ return len(self.l0_pages)
255
+
256
+ @property
257
+ def l1_count(self) -> int:
258
+ """Number of pages in L1."""
259
+ return len(self.l1_cache)
260
+
261
+ @property
262
+ def utilization(self) -> float:
263
+ """Current L0 token utilization (0-1)."""
264
+ return self.budget.utilization
265
+
266
+ @property
267
+ def tokens_used(self) -> int:
268
+ """Total tokens in L0."""
269
+ return self.budget.used
270
+
271
+ @property
272
+ def tokens_available(self) -> int:
273
+ """Available tokens for new content."""
274
+ return self.budget.available
275
+
276
+ def needs_eviction(self) -> bool:
277
+ """Check if eviction is needed."""
278
+ return self.utilization > self.config.eviction_threshold
279
+
280
+ def can_fit(self, tokens: int) -> bool:
281
+ """Check if additional tokens can fit in L0."""
282
+ return self.budget.can_fit(tokens)
283
+
284
+ def add_to_l0(self, page: MemoryPage) -> bool:
285
+ """
286
+ Add a page to L0 (context window).
287
+
288
+ Returns True if successful, False if insufficient space.
289
+ Does NOT automatically evict - call get_eviction_candidates first.
290
+ """
291
+ tokens = page.size_tokens or page.estimate_tokens()
292
+
293
+ if not self.budget.can_fit(tokens):
294
+ return False
295
+
296
+ # Add to budget
297
+ self.budget.add(tokens, page.modality)
298
+
299
+ # Add to L0 list (at end = most recent)
300
+ if page.page_id not in self.l0_pages:
301
+ self.l0_pages.append(page.page_id)
302
+
303
+ # Remove from L1 if present (promoted to L0)
304
+ self.l1_cache.pop(page.page_id, None)
305
+
306
+ # Update page state
307
+ page.storage_tier = StorageTier.L0
308
+ page.mark_accessed()
309
+
310
+ return True
311
+
312
+ def add_to_l1(self, page: MemoryPage) -> bool:
313
+ """
314
+ Add a page to L1 (hot cache).
315
+
316
+ Returns True if successful, False if L1 is full.
317
+ """
318
+ if len(self.l1_cache) >= self.config.max_l1_pages:
319
+ return False
320
+
321
+ # Remove from L0 if present (demoted to L1)
322
+ if page.page_id in self.l0_pages:
323
+ self.l0_pages.remove(page.page_id)
324
+ tokens = page.size_tokens or page.estimate_tokens()
325
+ self.budget.remove(tokens, page.modality)
326
+
327
+ # Add to L1
328
+ self.l1_cache[page.page_id] = page
329
+ page.storage_tier = StorageTier.L1
330
+ page.mark_accessed()
331
+
332
+ return True
333
+
334
+ def remove(self, page_id: str) -> Optional[MemoryPage]:
335
+ """
336
+ Remove a page from the working set entirely.
337
+
338
+ Returns the removed page, or None if not found.
339
+ """
340
+ # Check L1 first
341
+ page = self.l1_cache.pop(page_id, None)
342
+
343
+ # Check L0
344
+ if page_id in self.l0_pages:
345
+ self.l0_pages.remove(page_id)
346
+ # We need the page to update budget - caller should handle this
347
+ # For now, assume average tokens per modality
348
+
349
+ return page
350
+
351
+ def remove_from_l0(self, page_id: str, page: MemoryPage) -> bool:
352
+ """
353
+ Remove a specific page from L0, updating budget.
354
+
355
+ Returns True if removed, False if not in L0.
356
+ """
357
+ if page_id not in self.l0_pages:
358
+ return False
359
+
360
+ self.l0_pages.remove(page_id)
361
+ tokens = page.size_tokens or page.estimate_tokens()
362
+ self.budget.remove(tokens, page.modality)
363
+ return True
364
+
365
+ def promote_to_l0(self, page: MemoryPage) -> bool:
366
+ """
367
+ Promote a page from L1 to L0.
368
+
369
+ Returns True if successful.
370
+ """
371
+ if page.page_id not in self.l1_cache:
372
+ return False
373
+
374
+ return self.add_to_l0(page)
375
+
376
+ def demote_to_l1(self, page: MemoryPage) -> bool:
377
+ """
378
+ Demote a page from L0 to L1.
379
+
380
+ Returns True if successful.
381
+ """
382
+ if page.page_id not in self.l0_pages:
383
+ return False
384
+
385
+ return self.add_to_l1(page)
386
+
387
+ def get_page(self, page_id: str) -> Optional[MemoryPage]:
388
+ """Get a page from L1 cache (L0 pages are tracked by ID only)."""
389
+ return self.l1_cache.get(page_id)
390
+
391
+ def is_in_l0(self, page_id: str) -> bool:
392
+ """Check if a page is in L0."""
393
+ return page_id in self.l0_pages
394
+
395
+ def is_in_l1(self, page_id: str) -> bool:
396
+ """Check if a page is in L1."""
397
+ return page_id in self.l1_cache
398
+
399
+ def is_in_working_set(self, page_id: str) -> bool:
400
+ """Check if a page is in the working set (L0 or L1)."""
401
+ return self.is_in_l0(page_id) or self.is_in_l1(page_id)
402
+
403
+ def get_eviction_candidates(
404
+ self,
405
+ tokens_needed: int = 0,
406
+ from_tier: StorageTier = StorageTier.L0,
407
+ ) -> List[Tuple[str, float]]:
408
+ """
409
+ Get pages that are candidates for eviction, scored by priority.
410
+
411
+ Returns list of (page_id, eviction_score) tuples.
412
+ Lower score = evict first.
413
+
414
+ Scoring considers:
415
+ - Pinning (pinned pages are excluded)
416
+ - Anti-thrash (recently faulted pages get penalty)
417
+ - Recency (LRU component)
418
+ - Access frequency (LFU component)
419
+ - Importance (user/system-assigned)
420
+ - Position (older messages first)
421
+ """
422
+ candidates = []
423
+
424
+ if from_tier == StorageTier.L0:
425
+ # For L0, we only have page IDs - score by position
426
+ for i, page_id in enumerate(self.l0_pages):
427
+ # Skip pinned pages
428
+ if self.pinned_set.is_pinned(page_id):
429
+ continue
430
+
431
+ # Check anti-thrash policy
432
+ if not self.anti_thrash.can_evict(page_id, self.current_turn):
433
+ continue
434
+
435
+ # Earlier position = lower score = evict first
436
+ position_score = i / max(len(self.l0_pages), 1)
437
+ importance = self.importance_overrides.get(page_id, 0.5)
438
+
439
+ # Add anti-thrash penalty (higher penalty = higher score = less likely to evict)
440
+ thrash_penalty = self.anti_thrash.get_eviction_penalty(
441
+ page_id, self.current_turn
442
+ )
443
+
444
+ # Combine position, importance, and anti-thrash
445
+ score = position_score * 0.4 + importance * 0.4 + thrash_penalty * 0.2
446
+ candidates.append((page_id, score))
447
+ else:
448
+ # For L1, we have full pages with access tracking
449
+ now = datetime.utcnow()
450
+ for page_id, page in self.l1_cache.items():
451
+ # Skip pinned pages
452
+ if self.pinned_set.is_pinned(page_id) or page.pinned:
453
+ continue
454
+
455
+ # Check anti-thrash policy
456
+ if not self.anti_thrash.can_evict(page_id, self.current_turn):
457
+ continue
458
+
459
+ # Recency score (seconds since access, normalized)
460
+ age_seconds = (now - page.last_accessed).total_seconds()
461
+ recency_score = 1.0 / (1.0 + age_seconds / 3600) # Decay over hours
462
+
463
+ # Frequency score (log scale)
464
+ import math
465
+
466
+ frequency_score = math.log1p(page.access_count) / 10.0
467
+ frequency_score = min(1.0, frequency_score)
468
+
469
+ # Importance (page type affects this)
470
+ importance = self.importance_overrides.get(page_id, page.importance)
471
+
472
+ # Add anti-thrash penalty
473
+ thrash_penalty = self.anti_thrash.get_eviction_penalty(
474
+ page_id, self.current_turn
475
+ )
476
+
477
+ # Combined score (higher = keep longer)
478
+ score = (
479
+ recency_score * 0.3
480
+ + frequency_score * 0.2
481
+ + importance * 0.3
482
+ + thrash_penalty * 0.2
483
+ )
484
+ candidates.append((page_id, score))
485
+
486
+ # Sort by score (lowest first = evict first)
487
+ candidates.sort(key=lambda x: x[1])
488
+
489
+ return candidates
490
+
491
+ def new_turn(self) -> None:
492
+ """Advance to a new turn, updating anti-thrash tracking."""
493
+ self.current_turn += 1
494
+ # Cleanup old history periodically
495
+ if self.current_turn % 10 == 0:
496
+ self.anti_thrash.cleanup_old_history(self.current_turn)
497
+
498
+ def pin_page(self, page_id: str) -> None:
499
+ """Pin a page (will not be evicted)."""
500
+ self.pinned_set.pin(page_id)
501
+
502
+ def unpin_page(self, page_id: str) -> None:
503
+ """Unpin a page."""
504
+ self.pinned_set.unpin(page_id)
505
+
506
+ def is_pinned(self, page_id: str) -> bool:
507
+ """Check if a page is pinned."""
508
+ return self.pinned_set.is_pinned(page_id)
509
+
510
+ def record_fault(self, page_id: str) -> None:
511
+ """Record a page fault for anti-thrash tracking."""
512
+ self.anti_thrash.record_fault(page_id, self.current_turn)
513
+
514
+ def record_eviction(self, page_id: str) -> None:
515
+ """Record an eviction for anti-thrash tracking."""
516
+ self.anti_thrash.record_eviction(page_id, self.current_turn)
517
+
518
+ def calculate_eviction_target(self, tokens_needed: int = 0) -> int:
519
+ """
520
+ Calculate how many tokens to free to reach target utilization.
521
+
522
+ Args:
523
+ tokens_needed: Additional tokens we need to fit
524
+
525
+ Returns:
526
+ Number of tokens to evict
527
+ """
528
+ current = self.budget.used
529
+ max_tokens = self.config.max_l0_tokens - self.config.reserved_tokens
530
+ target = int(max_tokens * self.config.target_utilization)
531
+
532
+ # Need to get below target AND fit new tokens
533
+ required_free = current - target + tokens_needed
534
+
535
+ return max(0, required_free)
536
+
537
+ def set_importance(self, page_id: str, importance: float) -> None:
538
+ """Set importance override for a page."""
539
+ self.importance_overrides[page_id] = max(0.0, min(1.0, importance))
540
+
541
+ def clear_importance(self, page_id: str) -> None:
542
+ """Remove importance override for a page."""
543
+ self.importance_overrides.pop(page_id, None)
544
+
545
+ def get_l0_page_ids(self) -> List[str]:
546
+ """Get all page IDs in L0, in order."""
547
+ return list(self.l0_pages)
548
+
549
+ def get_l1_pages(self) -> List[MemoryPage]:
550
+ """Get all pages in L1."""
551
+ return list(self.l1_cache.values())
552
+
553
+ def get_stats(self) -> WorkingSetStats:
554
+ """Get working set statistics."""
555
+ return WorkingSetStats(
556
+ l0_pages=len(self.l0_pages),
557
+ l1_pages=len(self.l1_cache),
558
+ total_pages=len(self),
559
+ tokens_used=self.tokens_used,
560
+ tokens_available=self.tokens_available,
561
+ utilization=self.utilization,
562
+ needs_eviction=self.needs_eviction(),
563
+ tokens_by_modality=dict(self.budget.tokens_by_modality),
564
+ )
565
+
566
+ def clear(self) -> None:
567
+ """Clear the entire working set."""
568
+ self.l0_pages.clear()
569
+ self.l1_cache.clear()
570
+ self.budget = TokenBudget(
571
+ total_limit=self.config.max_l0_tokens,
572
+ reserved=self.config.reserved_tokens,
573
+ )
574
+ self.importance_overrides.clear()
@@ -2,35 +2,40 @@
2
2
  """
3
3
  Core models for the chuk session manager.
4
4
  """
5
+
5
6
  # Import each model separately to avoid circular imports
7
+ # These are re-exported as part of the public API
6
8
  try:
7
- from chuk_ai_session_manager.models.event_source import EventSource
9
+ from chuk_ai_session_manager.models.event_source import EventSource # noqa: F401
8
10
  except ImportError:
9
11
  pass
10
12
 
11
13
  try:
12
- from chuk_ai_session_manager.models.event_type import EventType
14
+ from chuk_ai_session_manager.models.event_type import EventType # noqa: F401
13
15
  except ImportError:
14
16
  pass
15
17
 
16
18
  try:
17
- from chuk_ai_session_manager.models.session_event import SessionEvent
19
+ from chuk_ai_session_manager.models.session_event import SessionEvent # noqa: F401
18
20
  except ImportError:
19
21
  pass
20
22
 
21
23
  try:
22
- from chuk_ai_session_manager.models.session_metadata import SessionMetadata
24
+ from chuk_ai_session_manager.models.session_metadata import SessionMetadata # noqa: F401
23
25
  except ImportError:
24
26
  pass
25
27
 
26
28
  try:
27
- from chuk_ai_session_manager.models.session_run import SessionRun, RunStatus
29
+ from chuk_ai_session_manager.models.session_run import ( # noqa: F401
30
+ RunStatus,
31
+ SessionRun,
32
+ )
28
33
  except ImportError:
29
34
  pass
30
35
 
31
36
  # Import Session last since it might depend on the above
32
37
  try:
33
- from chuk_ai_session_manager.models.session import Session
38
+ from chuk_ai_session_manager.models.session import Session # noqa: F401
34
39
  except ImportError:
35
40
  pass
36
41
 
@@ -38,7 +43,14 @@ except ImportError:
38
43
  __all__ = []
39
44
 
40
45
  # Check which imports succeeded and add them to __all__
41
- for name in ['EventSource', 'EventType', 'SessionEvent', 'SessionMetadata',
42
- 'SessionRun', 'RunStatus', 'Session']:
46
+ for name in [
47
+ "EventSource",
48
+ "EventType",
49
+ "SessionEvent",
50
+ "SessionMetadata",
51
+ "SessionRun",
52
+ "RunStatus",
53
+ "Session",
54
+ ]:
43
55
  if name in globals():
44
- __all__.append(name)
56
+ __all__.append(name)
@@ -1,8 +1,10 @@
1
1
  # chuk_ai_session_manager/models/event_source.py
2
2
  from enum import Enum
3
3
 
4
+
4
5
  class EventSource(str, Enum):
5
6
  """Source of the session event."""
7
+
6
8
  USER = "user"
7
9
  LLM = "llm"
8
- SYSTEM = "system"
10
+ SYSTEM = "system"
@@ -1,9 +1,18 @@
1
1
  # chuk_ai_session_manager/models/event_type.py
2
2
  from enum import Enum
3
+
4
+
3
5
  class EventType(str, Enum):
4
6
  """Type of the session event."""
7
+
8
+ # Episodic memory
5
9
  MESSAGE = "message"
6
10
  SUMMARY = "summary"
7
11
  TOOL_CALL = "tool_call"
8
12
  REFERENCE = "reference"
9
- CONTEXT_BRIDGE = "context_bridge"
13
+ CONTEXT_BRIDGE = "context_bridge"
14
+
15
+ # Procedural memory
16
+ TOOL_TRACE = "tool_trace" # Rich tool invocation record
17
+ TOOL_PATTERN = "tool_pattern" # Aggregated tool patterns
18
+ TOOL_FIX = "tool_fix" # Fix relationship (failure -> success)