chuk-ai-session-manager 0.7__py3-none-any.whl → 0.8__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.
- chuk_ai_session_manager/__init__.py +84 -40
- chuk_ai_session_manager/api/__init__.py +1 -1
- chuk_ai_session_manager/api/simple_api.py +53 -59
- chuk_ai_session_manager/exceptions.py +31 -17
- chuk_ai_session_manager/guards/__init__.py +118 -0
- chuk_ai_session_manager/guards/bindings.py +217 -0
- chuk_ai_session_manager/guards/cache.py +163 -0
- chuk_ai_session_manager/guards/manager.py +819 -0
- chuk_ai_session_manager/guards/models.py +498 -0
- chuk_ai_session_manager/guards/ungrounded.py +159 -0
- chuk_ai_session_manager/infinite_conversation.py +86 -79
- chuk_ai_session_manager/memory/__init__.py +247 -0
- chuk_ai_session_manager/memory/artifacts_bridge.py +469 -0
- chuk_ai_session_manager/memory/context_packer.py +347 -0
- chuk_ai_session_manager/memory/fault_handler.py +507 -0
- chuk_ai_session_manager/memory/manifest.py +307 -0
- chuk_ai_session_manager/memory/models.py +1084 -0
- chuk_ai_session_manager/memory/mutation_log.py +186 -0
- chuk_ai_session_manager/memory/pack_cache.py +206 -0
- chuk_ai_session_manager/memory/page_table.py +275 -0
- chuk_ai_session_manager/memory/prefetcher.py +192 -0
- chuk_ai_session_manager/memory/tlb.py +247 -0
- chuk_ai_session_manager/memory/vm_prompts.py +238 -0
- chuk_ai_session_manager/memory/working_set.py +574 -0
- chuk_ai_session_manager/models/__init__.py +21 -9
- chuk_ai_session_manager/models/event_source.py +3 -1
- chuk_ai_session_manager/models/event_type.py +10 -1
- chuk_ai_session_manager/models/session.py +103 -68
- chuk_ai_session_manager/models/session_event.py +69 -68
- chuk_ai_session_manager/models/session_metadata.py +9 -10
- chuk_ai_session_manager/models/session_run.py +21 -22
- chuk_ai_session_manager/models/token_usage.py +76 -76
- chuk_ai_session_manager/procedural_memory/__init__.py +70 -0
- chuk_ai_session_manager/procedural_memory/formatter.py +407 -0
- chuk_ai_session_manager/procedural_memory/manager.py +523 -0
- chuk_ai_session_manager/procedural_memory/models.py +371 -0
- chuk_ai_session_manager/sample_tools.py +79 -46
- chuk_ai_session_manager/session_aware_tool_processor.py +27 -16
- chuk_ai_session_manager/session_manager.py +238 -197
- chuk_ai_session_manager/session_prompt_builder.py +163 -111
- chuk_ai_session_manager/session_storage.py +45 -52
- {chuk_ai_session_manager-0.7.dist-info → chuk_ai_session_manager-0.8.dist-info}/METADATA +78 -2
- chuk_ai_session_manager-0.8.dist-info/RECORD +45 -0
- {chuk_ai_session_manager-0.7.dist-info → chuk_ai_session_manager-0.8.dist-info}/WHEEL +1 -1
- chuk_ai_session_manager-0.7.dist-info/RECORD +0 -22
- {chuk_ai_session_manager-0.7.dist-info → chuk_ai_session_manager-0.8.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
# chuk_ai_session_manager/memory/manifest.py
|
|
2
|
+
"""
|
|
3
|
+
Manifest Builder for AI Virtual Memory.
|
|
4
|
+
|
|
5
|
+
The ManifestBuilder generates the VM:MANIFEST_JSON block - a machine-readable
|
|
6
|
+
index of all pages available to the model. This tells the model what exists
|
|
7
|
+
and where, enabling informed page_fault decisions.
|
|
8
|
+
|
|
9
|
+
Design principles:
|
|
10
|
+
- Machine-readable: Strict JSON for reliable parsing
|
|
11
|
+
- Discovery-focused: Hints help find relevant pages, not provide evidence
|
|
12
|
+
- Policy-aware: Includes fault limits and preferences
|
|
13
|
+
- Pydantic-native: All models are BaseModel subclasses
|
|
14
|
+
- No magic strings: Uses enums for all categorical values
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from typing import Callable, Dict, List, Optional
|
|
18
|
+
|
|
19
|
+
from pydantic import BaseModel, Field
|
|
20
|
+
|
|
21
|
+
from .models import (
|
|
22
|
+
ALL_COMPRESSION_LEVELS,
|
|
23
|
+
CompressionLevel,
|
|
24
|
+
MemoryPage,
|
|
25
|
+
Modality,
|
|
26
|
+
PageTableEntry,
|
|
27
|
+
StorageTier,
|
|
28
|
+
)
|
|
29
|
+
from .page_table import PageTable
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class WorkingSetEntry(BaseModel):
|
|
33
|
+
"""Entry for a page in the working set (already mapped)."""
|
|
34
|
+
|
|
35
|
+
page_id: str
|
|
36
|
+
modality: str
|
|
37
|
+
level: int = Field(..., description="Compression level (0-3)")
|
|
38
|
+
tokens_est: int
|
|
39
|
+
importance: float = 0.5
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class AvailablePageEntry(BaseModel):
|
|
43
|
+
"""Entry for a page available for loading (not yet mapped)."""
|
|
44
|
+
|
|
45
|
+
page_id: str
|
|
46
|
+
modality: str
|
|
47
|
+
tier: str = Field(..., description="Current storage tier")
|
|
48
|
+
levels: List[int] = Field(
|
|
49
|
+
default_factory=lambda: list(ALL_COMPRESSION_LEVELS),
|
|
50
|
+
description="Available compression levels",
|
|
51
|
+
)
|
|
52
|
+
hint: str = Field(default="", description="Discovery hint (NOT evidence)")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class ManifestPolicies(BaseModel):
|
|
56
|
+
"""Policies governing VM behavior."""
|
|
57
|
+
|
|
58
|
+
faults_allowed: bool = True
|
|
59
|
+
max_faults_per_turn: int = 2
|
|
60
|
+
upgrade_budget_tokens: int = 4096
|
|
61
|
+
prefer_levels: List[int] = Field(
|
|
62
|
+
default_factory=lambda: [
|
|
63
|
+
CompressionLevel.ABSTRACT.value,
|
|
64
|
+
CompressionLevel.REDUCED.value,
|
|
65
|
+
CompressionLevel.FULL.value,
|
|
66
|
+
],
|
|
67
|
+
description="Preference order for compression levels",
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class VMManifest(BaseModel):
|
|
72
|
+
"""
|
|
73
|
+
Complete VM manifest for inclusion in developer message.
|
|
74
|
+
|
|
75
|
+
This is the machine-readable counterpart to VM:CONTEXT.
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
session_id: str
|
|
79
|
+
working_set: List[WorkingSetEntry] = Field(default_factory=list)
|
|
80
|
+
available_pages: List[AvailablePageEntry] = Field(default_factory=list)
|
|
81
|
+
policies: ManifestPolicies = Field(default_factory=ManifestPolicies)
|
|
82
|
+
|
|
83
|
+
def to_json(self, indent: Optional[int] = None) -> str:
|
|
84
|
+
"""Serialize to JSON string."""
|
|
85
|
+
return self.model_dump_json(indent=indent)
|
|
86
|
+
|
|
87
|
+
def to_wrapped_json(self, indent: Optional[int] = None) -> str:
|
|
88
|
+
"""Serialize wrapped with VM:MANIFEST_JSON tags."""
|
|
89
|
+
json_str = self.to_json(indent=indent)
|
|
90
|
+
return f"<VM:MANIFEST_JSON>\n{json_str}\n</VM:MANIFEST_JSON>"
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class ManifestBuilder(BaseModel):
|
|
94
|
+
"""
|
|
95
|
+
Builds VM manifests from PageTable state.
|
|
96
|
+
|
|
97
|
+
Usage:
|
|
98
|
+
builder = ManifestBuilder()
|
|
99
|
+
manifest = builder.build(
|
|
100
|
+
session_id="sess_123",
|
|
101
|
+
page_table=page_table,
|
|
102
|
+
working_set_ids=["msg_1", "msg_2"],
|
|
103
|
+
hint_generator=lambda e: f"message about {e.modality}"
|
|
104
|
+
)
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
# Default policies
|
|
108
|
+
default_policies: ManifestPolicies = Field(default_factory=ManifestPolicies)
|
|
109
|
+
|
|
110
|
+
# Maximum available pages to include
|
|
111
|
+
max_available_pages: int = Field(
|
|
112
|
+
default=50, description="Limit available_pages to prevent manifest bloat"
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
def build(
|
|
116
|
+
self,
|
|
117
|
+
session_id: str,
|
|
118
|
+
page_table: PageTable,
|
|
119
|
+
working_set_ids: List[str],
|
|
120
|
+
working_set_tokens: Optional[Dict[str, int]] = None,
|
|
121
|
+
working_set_importance: Optional[Dict[str, float]] = None,
|
|
122
|
+
hint_generator: Optional[Callable[[PageTableEntry], str]] = None,
|
|
123
|
+
policies: Optional[ManifestPolicies] = None,
|
|
124
|
+
) -> VMManifest:
|
|
125
|
+
"""
|
|
126
|
+
Build a complete VM manifest.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
session_id: Session identifier
|
|
130
|
+
page_table: PageTable with all page entries
|
|
131
|
+
working_set_ids: Page IDs currently in working set (L0)
|
|
132
|
+
working_set_tokens: Optional token counts per page
|
|
133
|
+
working_set_importance: Optional importance scores per page
|
|
134
|
+
hint_generator: Optional function(PageTableEntry) -> str for hints
|
|
135
|
+
policies: Optional policy overrides
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
VMManifest ready for serialization
|
|
139
|
+
"""
|
|
140
|
+
working_set: List[WorkingSetEntry] = []
|
|
141
|
+
available_pages: List[AvailablePageEntry] = []
|
|
142
|
+
|
|
143
|
+
working_set_tokens = working_set_tokens or {}
|
|
144
|
+
working_set_importance = working_set_importance or {}
|
|
145
|
+
|
|
146
|
+
# Build working set entries
|
|
147
|
+
for page_id in working_set_ids:
|
|
148
|
+
entry = page_table.lookup(page_id)
|
|
149
|
+
if entry:
|
|
150
|
+
ws_entry = WorkingSetEntry(
|
|
151
|
+
page_id=page_id,
|
|
152
|
+
modality=entry.modality.value,
|
|
153
|
+
level=entry.compression_level,
|
|
154
|
+
tokens_est=working_set_tokens.get(
|
|
155
|
+
page_id, entry.size_tokens or 100
|
|
156
|
+
),
|
|
157
|
+
importance=working_set_importance.get(page_id, 0.5),
|
|
158
|
+
)
|
|
159
|
+
working_set.append(ws_entry)
|
|
160
|
+
|
|
161
|
+
# Build available pages (everything not in working set)
|
|
162
|
+
available_count = 0
|
|
163
|
+
for page_id, entry in page_table.entries.items():
|
|
164
|
+
if page_id in working_set_ids:
|
|
165
|
+
continue
|
|
166
|
+
|
|
167
|
+
if available_count >= self.max_available_pages:
|
|
168
|
+
break
|
|
169
|
+
|
|
170
|
+
# Generate hint
|
|
171
|
+
hint = ""
|
|
172
|
+
if hint_generator:
|
|
173
|
+
try:
|
|
174
|
+
hint = hint_generator(entry)
|
|
175
|
+
except Exception:
|
|
176
|
+
hint = ""
|
|
177
|
+
|
|
178
|
+
available_entry = AvailablePageEntry(
|
|
179
|
+
page_id=page_id,
|
|
180
|
+
modality=entry.modality.value,
|
|
181
|
+
tier=entry.tier.value,
|
|
182
|
+
levels=ALL_COMPRESSION_LEVELS,
|
|
183
|
+
hint=hint,
|
|
184
|
+
)
|
|
185
|
+
available_pages.append(available_entry)
|
|
186
|
+
available_count += 1
|
|
187
|
+
|
|
188
|
+
return VMManifest(
|
|
189
|
+
session_id=session_id,
|
|
190
|
+
working_set=working_set,
|
|
191
|
+
available_pages=available_pages,
|
|
192
|
+
policies=policies or self.default_policies,
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
def build_from_pages(
|
|
196
|
+
self,
|
|
197
|
+
session_id: str,
|
|
198
|
+
working_set_pages: List[MemoryPage],
|
|
199
|
+
available_entries: List[PageTableEntry],
|
|
200
|
+
hint_generator: Optional[Callable[[PageTableEntry], str]] = None,
|
|
201
|
+
policies: Optional[ManifestPolicies] = None,
|
|
202
|
+
) -> VMManifest:
|
|
203
|
+
"""
|
|
204
|
+
Build manifest directly from page objects (alternative API).
|
|
205
|
+
|
|
206
|
+
Args:
|
|
207
|
+
session_id: Session identifier
|
|
208
|
+
working_set_pages: MemoryPage objects in working set
|
|
209
|
+
available_entries: PageTableEntry objects for available pages
|
|
210
|
+
hint_generator: Optional function(PageTableEntry) -> str
|
|
211
|
+
policies: Optional policy overrides
|
|
212
|
+
|
|
213
|
+
Returns:
|
|
214
|
+
VMManifest
|
|
215
|
+
"""
|
|
216
|
+
working_set: List[WorkingSetEntry] = []
|
|
217
|
+
available_pages: List[AvailablePageEntry] = []
|
|
218
|
+
|
|
219
|
+
# Build working set from pages
|
|
220
|
+
for page in working_set_pages:
|
|
221
|
+
ws_entry = WorkingSetEntry(
|
|
222
|
+
page_id=page.page_id,
|
|
223
|
+
modality=page.modality.value
|
|
224
|
+
if hasattr(page.modality, "value")
|
|
225
|
+
else str(page.modality),
|
|
226
|
+
level=page.compression_level
|
|
227
|
+
if isinstance(page.compression_level, int)
|
|
228
|
+
else page.compression_level.value,
|
|
229
|
+
tokens_est=page.size_tokens or page.estimate_tokens(),
|
|
230
|
+
importance=page.importance,
|
|
231
|
+
)
|
|
232
|
+
working_set.append(ws_entry)
|
|
233
|
+
|
|
234
|
+
# Build available pages
|
|
235
|
+
working_set_ids = {p.page_id for p in working_set_pages}
|
|
236
|
+
for entry in available_entries[: self.max_available_pages]:
|
|
237
|
+
if entry.page_id in working_set_ids:
|
|
238
|
+
continue
|
|
239
|
+
|
|
240
|
+
hint = ""
|
|
241
|
+
if hint_generator:
|
|
242
|
+
try:
|
|
243
|
+
hint = hint_generator(entry)
|
|
244
|
+
except Exception:
|
|
245
|
+
hint = ""
|
|
246
|
+
|
|
247
|
+
available_entry = AvailablePageEntry(
|
|
248
|
+
page_id=entry.page_id,
|
|
249
|
+
modality=entry.modality.value
|
|
250
|
+
if hasattr(entry.modality, "value")
|
|
251
|
+
else str(entry.modality),
|
|
252
|
+
tier=entry.tier.value
|
|
253
|
+
if hasattr(entry.tier, "value")
|
|
254
|
+
else str(entry.tier),
|
|
255
|
+
levels=ALL_COMPRESSION_LEVELS,
|
|
256
|
+
hint=hint,
|
|
257
|
+
)
|
|
258
|
+
available_pages.append(available_entry)
|
|
259
|
+
|
|
260
|
+
return VMManifest(
|
|
261
|
+
session_id=session_id,
|
|
262
|
+
working_set=working_set,
|
|
263
|
+
available_pages=available_pages,
|
|
264
|
+
policies=policies or self.default_policies,
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
# Hint type constants
|
|
269
|
+
class HintType:
|
|
270
|
+
"""Constants for hint types in manifest generation."""
|
|
271
|
+
|
|
272
|
+
RECENT = "recent"
|
|
273
|
+
STORED = "stored"
|
|
274
|
+
ARCHIVED = "archived"
|
|
275
|
+
SUMMARY = "summary"
|
|
276
|
+
EXCERPT = "excerpt"
|
|
277
|
+
CONTENT = "content"
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def generate_simple_hint(entry: PageTableEntry) -> str:
|
|
281
|
+
"""
|
|
282
|
+
Simple hint generator based on page metadata.
|
|
283
|
+
|
|
284
|
+
This is a basic implementation - real systems would use
|
|
285
|
+
summaries, embeddings, or other content-aware hints.
|
|
286
|
+
"""
|
|
287
|
+
parts: List[str] = []
|
|
288
|
+
|
|
289
|
+
# Modality
|
|
290
|
+
if entry.modality != Modality.TEXT:
|
|
291
|
+
parts.append(entry.modality.value)
|
|
292
|
+
|
|
293
|
+
# Tier (indicates recency/importance)
|
|
294
|
+
if entry.tier == StorageTier.L2:
|
|
295
|
+
parts.append(HintType.RECENT)
|
|
296
|
+
elif entry.tier == StorageTier.L3:
|
|
297
|
+
parts.append(HintType.STORED)
|
|
298
|
+
elif entry.tier == StorageTier.L4:
|
|
299
|
+
parts.append(HintType.ARCHIVED)
|
|
300
|
+
|
|
301
|
+
# Compression level
|
|
302
|
+
if entry.compression_level == CompressionLevel.ABSTRACT:
|
|
303
|
+
parts.append(HintType.SUMMARY)
|
|
304
|
+
elif entry.compression_level == CompressionLevel.REDUCED:
|
|
305
|
+
parts.append(HintType.EXCERPT)
|
|
306
|
+
|
|
307
|
+
return " ".join(parts) if parts else HintType.CONTENT
|