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,407 @@
|
|
|
1
|
+
# chuk_ai_session_manager/procedural_memory/formatter.py
|
|
2
|
+
"""
|
|
3
|
+
Context Formatter for Procedural Memory.
|
|
4
|
+
|
|
5
|
+
Handles formatting procedural memory for injection into model context.
|
|
6
|
+
This is the "paging" layer - deciding what tool knowledge to include
|
|
7
|
+
and how to present it.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from typing import Any, Optional
|
|
13
|
+
|
|
14
|
+
from pydantic import BaseModel, Field
|
|
15
|
+
|
|
16
|
+
from chuk_ai_session_manager.procedural_memory.models import (
|
|
17
|
+
ToolLogEntry,
|
|
18
|
+
ToolPattern,
|
|
19
|
+
)
|
|
20
|
+
from chuk_ai_session_manager.procedural_memory.manager import ToolMemoryManager
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class FormatterConfig(BaseModel):
|
|
24
|
+
"""Configuration for procedural memory formatting."""
|
|
25
|
+
|
|
26
|
+
# How many recent calls to include per tool
|
|
27
|
+
max_recent_calls: int = 3
|
|
28
|
+
|
|
29
|
+
# How many error patterns to show
|
|
30
|
+
max_error_patterns: int = 3
|
|
31
|
+
|
|
32
|
+
# How many success patterns to show
|
|
33
|
+
max_success_patterns: int = 3
|
|
34
|
+
|
|
35
|
+
# Whether to include argument details
|
|
36
|
+
include_args: bool = False
|
|
37
|
+
|
|
38
|
+
# Whether to include timing info
|
|
39
|
+
include_timing: bool = False
|
|
40
|
+
|
|
41
|
+
# Whether to show fix relationships
|
|
42
|
+
show_fix_relations: bool = True
|
|
43
|
+
|
|
44
|
+
# Compact mode (less verbose)
|
|
45
|
+
compact: bool = False
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class ProceduralContextFormatter(BaseModel):
|
|
49
|
+
"""
|
|
50
|
+
Formats procedural memory for injection into model context.
|
|
51
|
+
|
|
52
|
+
This class handles the "paging" decision - what to include and how.
|
|
53
|
+
It can format:
|
|
54
|
+
- Recent tool history for specific tools
|
|
55
|
+
- Patterns (errors, successes) for tools
|
|
56
|
+
- Full procedural context summary
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
config: FormatterConfig = Field(default_factory=FormatterConfig)
|
|
60
|
+
|
|
61
|
+
def format_for_tools(
|
|
62
|
+
self,
|
|
63
|
+
manager: ToolMemoryManager,
|
|
64
|
+
tool_names: list[str],
|
|
65
|
+
context_goal: Optional[str] = None,
|
|
66
|
+
) -> str:
|
|
67
|
+
"""
|
|
68
|
+
Format procedural memory for specific tools about to be called.
|
|
69
|
+
|
|
70
|
+
This is the main entry point for "just-in-time" paging.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
manager: The tool memory manager
|
|
74
|
+
tool_names: Tools that are about to be called
|
|
75
|
+
context_goal: Current goal (for relevance filtering)
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
Formatted context string ready for injection
|
|
79
|
+
"""
|
|
80
|
+
sections = []
|
|
81
|
+
|
|
82
|
+
for tool_name in tool_names:
|
|
83
|
+
tool_section = self._format_tool_section(manager, tool_name, context_goal)
|
|
84
|
+
if tool_section:
|
|
85
|
+
sections.append(tool_section)
|
|
86
|
+
|
|
87
|
+
if not sections:
|
|
88
|
+
return ""
|
|
89
|
+
|
|
90
|
+
header = "<procedural_memory>\n"
|
|
91
|
+
footer = "\n</procedural_memory>"
|
|
92
|
+
|
|
93
|
+
return header + "\n\n".join(sections) + footer
|
|
94
|
+
|
|
95
|
+
def _format_tool_section(
|
|
96
|
+
self,
|
|
97
|
+
manager: ToolMemoryManager,
|
|
98
|
+
tool_name: str,
|
|
99
|
+
context_goal: Optional[str] = None,
|
|
100
|
+
) -> Optional[str]:
|
|
101
|
+
"""Format a single tool's procedural memory."""
|
|
102
|
+
lines = []
|
|
103
|
+
|
|
104
|
+
# Get recent calls for this tool
|
|
105
|
+
recent = manager.get_recent_calls(
|
|
106
|
+
tool_name=tool_name,
|
|
107
|
+
limit=self.config.max_recent_calls,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
# Get pattern
|
|
111
|
+
pattern = manager.get_pattern(tool_name)
|
|
112
|
+
|
|
113
|
+
# Skip if no history
|
|
114
|
+
if not recent and not pattern:
|
|
115
|
+
return None
|
|
116
|
+
|
|
117
|
+
lines.append(f'<tool_memory name="{tool_name}">')
|
|
118
|
+
|
|
119
|
+
# Recent history
|
|
120
|
+
if recent:
|
|
121
|
+
lines.append(self._format_recent_calls(recent))
|
|
122
|
+
|
|
123
|
+
# Patterns
|
|
124
|
+
if pattern:
|
|
125
|
+
pattern_text = self._format_pattern(pattern, context_goal)
|
|
126
|
+
if pattern_text:
|
|
127
|
+
if recent:
|
|
128
|
+
lines.append("")
|
|
129
|
+
lines.append(pattern_text)
|
|
130
|
+
|
|
131
|
+
lines.append("</tool_memory>")
|
|
132
|
+
|
|
133
|
+
return "\n".join(lines)
|
|
134
|
+
|
|
135
|
+
def _format_recent_calls(self, calls: list[ToolLogEntry]) -> str:
|
|
136
|
+
"""Format recent call history."""
|
|
137
|
+
lines = ["<recent_calls>"]
|
|
138
|
+
|
|
139
|
+
for entry in calls:
|
|
140
|
+
status = "✓" if entry.is_success() else "✗"
|
|
141
|
+
line = f" [{status}] {entry.result_summary}"
|
|
142
|
+
|
|
143
|
+
# Add fix info
|
|
144
|
+
if self.config.show_fix_relations:
|
|
145
|
+
if entry.is_fix() and entry.delta_args:
|
|
146
|
+
line += f" (fixed prior by: {self._format_delta(entry.delta_args)})"
|
|
147
|
+
elif entry.is_failure() and entry.was_fixed():
|
|
148
|
+
line += f" (fixed by {entry.fixed_by})"
|
|
149
|
+
|
|
150
|
+
# Add args if configured
|
|
151
|
+
if self.config.include_args and entry.arguments:
|
|
152
|
+
args_str = self._format_args(entry.arguments)
|
|
153
|
+
line += f"\n args: {args_str}"
|
|
154
|
+
|
|
155
|
+
# Add timing if configured
|
|
156
|
+
if self.config.include_timing and entry.execution_time_ms:
|
|
157
|
+
line += f" [{entry.execution_time_ms}ms]"
|
|
158
|
+
|
|
159
|
+
lines.append(line)
|
|
160
|
+
|
|
161
|
+
lines.append("</recent_calls>")
|
|
162
|
+
return "\n".join(lines)
|
|
163
|
+
|
|
164
|
+
def _format_pattern(
|
|
165
|
+
self,
|
|
166
|
+
pattern: ToolPattern,
|
|
167
|
+
context_goal: Optional[str] = None,
|
|
168
|
+
) -> Optional[str]:
|
|
169
|
+
"""Format aggregated patterns."""
|
|
170
|
+
lines = []
|
|
171
|
+
|
|
172
|
+
# Only show patterns if we have useful info
|
|
173
|
+
has_errors = bool(pattern.error_patterns)
|
|
174
|
+
has_successes = bool(pattern.success_patterns)
|
|
175
|
+
|
|
176
|
+
if not has_errors and not has_successes:
|
|
177
|
+
return None
|
|
178
|
+
|
|
179
|
+
lines.append("<patterns>")
|
|
180
|
+
|
|
181
|
+
# Stats (if not compact)
|
|
182
|
+
if not self.config.compact:
|
|
183
|
+
lines.append(
|
|
184
|
+
f" success_rate: {pattern.success_rate:.0%} "
|
|
185
|
+
f"({pattern.total_calls} calls)"
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
# Error patterns with fixes
|
|
189
|
+
if has_errors:
|
|
190
|
+
lines.append(" <common_errors>")
|
|
191
|
+
for ep in pattern.error_patterns[-self.config.max_error_patterns :]:
|
|
192
|
+
error_line = f" - {ep.error_type}"
|
|
193
|
+
if ep.count > 1:
|
|
194
|
+
error_line += f" (x{ep.count})"
|
|
195
|
+
if ep.typical_fix:
|
|
196
|
+
error_line += f" -> fix: {ep.typical_fix}"
|
|
197
|
+
lines.append(error_line)
|
|
198
|
+
lines.append(" </common_errors>")
|
|
199
|
+
|
|
200
|
+
# Success patterns (especially fixes)
|
|
201
|
+
if has_successes:
|
|
202
|
+
relevant = self._filter_relevant_successes(
|
|
203
|
+
pattern.success_patterns,
|
|
204
|
+
context_goal,
|
|
205
|
+
)
|
|
206
|
+
if relevant:
|
|
207
|
+
lines.append(" <success_hints>")
|
|
208
|
+
for sp in relevant[-self.config.max_success_patterns :]:
|
|
209
|
+
if sp.delta_that_fixed:
|
|
210
|
+
lines.append(
|
|
211
|
+
f" - Fixed by: {self._format_delta(sp.delta_that_fixed)}"
|
|
212
|
+
)
|
|
213
|
+
elif sp.arg_hints:
|
|
214
|
+
lines.append(f" - Typical args: {sp.arg_hints}")
|
|
215
|
+
lines.append(" </success_hints>")
|
|
216
|
+
|
|
217
|
+
lines.append("</patterns>")
|
|
218
|
+
return "\n".join(lines)
|
|
219
|
+
|
|
220
|
+
def _filter_relevant_successes(
|
|
221
|
+
self,
|
|
222
|
+
patterns: list,
|
|
223
|
+
context_goal: Optional[str],
|
|
224
|
+
) -> list:
|
|
225
|
+
"""Filter success patterns to those relevant to current goal."""
|
|
226
|
+
if not context_goal:
|
|
227
|
+
return patterns
|
|
228
|
+
|
|
229
|
+
goal_lower = context_goal.lower()
|
|
230
|
+
relevant = []
|
|
231
|
+
|
|
232
|
+
for sp in patterns:
|
|
233
|
+
# Include if no goal specified (general pattern)
|
|
234
|
+
if not sp.goal_match:
|
|
235
|
+
relevant.append(sp)
|
|
236
|
+
continue
|
|
237
|
+
|
|
238
|
+
# Include if goal matches
|
|
239
|
+
if goal_lower in sp.goal_match.lower():
|
|
240
|
+
relevant.append(sp)
|
|
241
|
+
continue
|
|
242
|
+
|
|
243
|
+
# Include if it's a fix (always useful)
|
|
244
|
+
if sp.delta_that_fixed:
|
|
245
|
+
relevant.append(sp)
|
|
246
|
+
|
|
247
|
+
return relevant
|
|
248
|
+
|
|
249
|
+
def _format_delta(self, delta: dict[str, Any]) -> str:
|
|
250
|
+
"""Format arg delta compactly."""
|
|
251
|
+
parts = []
|
|
252
|
+
|
|
253
|
+
if "added" in delta:
|
|
254
|
+
added_keys = list(delta["added"].keys())
|
|
255
|
+
if len(added_keys) <= 2:
|
|
256
|
+
parts.append(f"+{', '.join(added_keys)}")
|
|
257
|
+
else:
|
|
258
|
+
parts.append(f"+{len(added_keys)} args")
|
|
259
|
+
|
|
260
|
+
if "removed" in delta:
|
|
261
|
+
removed = delta["removed"]
|
|
262
|
+
if len(removed) <= 2:
|
|
263
|
+
parts.append(f"-{', '.join(removed)}")
|
|
264
|
+
else:
|
|
265
|
+
parts.append(f"-{len(removed)} args")
|
|
266
|
+
|
|
267
|
+
if "changed" in delta:
|
|
268
|
+
changed_keys = list(delta["changed"].keys())
|
|
269
|
+
if len(changed_keys) <= 2:
|
|
270
|
+
changes = []
|
|
271
|
+
for k in changed_keys:
|
|
272
|
+
v = delta["changed"][k]
|
|
273
|
+
changes.append(
|
|
274
|
+
f"{k}:{self._abbrev(v['from'])}->{self._abbrev(v['to'])}"
|
|
275
|
+
)
|
|
276
|
+
parts.append(", ".join(changes))
|
|
277
|
+
else:
|
|
278
|
+
parts.append(f"~{len(changed_keys)} args")
|
|
279
|
+
|
|
280
|
+
return "; ".join(parts) if parts else "changes"
|
|
281
|
+
|
|
282
|
+
def _format_args(self, args: dict[str, Any], max_items: int = 3) -> str:
|
|
283
|
+
"""Format arguments compactly."""
|
|
284
|
+
items = list(args.items())[:max_items]
|
|
285
|
+
formatted = [f"{k}={self._abbrev(v)}" for k, v in items]
|
|
286
|
+
if len(args) > max_items:
|
|
287
|
+
formatted.append(f"...+{len(args) - max_items}")
|
|
288
|
+
return ", ".join(formatted)
|
|
289
|
+
|
|
290
|
+
def _abbrev(self, value: Any, max_len: int = 15) -> str:
|
|
291
|
+
"""Abbreviate a value."""
|
|
292
|
+
s = str(value)
|
|
293
|
+
if len(s) > max_len:
|
|
294
|
+
return s[: max_len - 2] + ".."
|
|
295
|
+
return s
|
|
296
|
+
|
|
297
|
+
# --- Full context formatting ---
|
|
298
|
+
|
|
299
|
+
def format_full_summary(
|
|
300
|
+
self,
|
|
301
|
+
manager: ToolMemoryManager,
|
|
302
|
+
max_tools: int = 5,
|
|
303
|
+
) -> str:
|
|
304
|
+
"""
|
|
305
|
+
Format a full summary of procedural memory.
|
|
306
|
+
|
|
307
|
+
Useful for periodic context refresh or debugging.
|
|
308
|
+
"""
|
|
309
|
+
lines = ["<procedural_memory_summary>"]
|
|
310
|
+
|
|
311
|
+
stats = manager.get_stats()
|
|
312
|
+
lines.append(
|
|
313
|
+
f" Total: {stats['total_calls']} calls, "
|
|
314
|
+
f"{stats['success_rate']:.0%} success, "
|
|
315
|
+
f"{stats['total_fixes_detected']} fixes detected"
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
# Top tools by usage
|
|
319
|
+
patterns = manager.get_all_patterns()
|
|
320
|
+
sorted_tools = sorted(
|
|
321
|
+
patterns.items(),
|
|
322
|
+
key=lambda x: x[1].total_calls,
|
|
323
|
+
reverse=True,
|
|
324
|
+
)[:max_tools]
|
|
325
|
+
|
|
326
|
+
if sorted_tools:
|
|
327
|
+
lines.append("")
|
|
328
|
+
lines.append(" Most used tools:")
|
|
329
|
+
for tool_name, pattern in sorted_tools:
|
|
330
|
+
lines.append(
|
|
331
|
+
f" - {tool_name}: {pattern.total_calls} calls, "
|
|
332
|
+
f"{pattern.success_rate:.0%} success"
|
|
333
|
+
)
|
|
334
|
+
if pattern.error_patterns:
|
|
335
|
+
top_error = pattern.error_patterns[-1]
|
|
336
|
+
lines.append(f" last error: {top_error.error_type}")
|
|
337
|
+
|
|
338
|
+
lines.append("</procedural_memory_summary>")
|
|
339
|
+
return "\n".join(lines)
|
|
340
|
+
|
|
341
|
+
# --- Specialized formats ---
|
|
342
|
+
|
|
343
|
+
def format_error_guidance(
|
|
344
|
+
self,
|
|
345
|
+
manager: ToolMemoryManager,
|
|
346
|
+
tool_name: str,
|
|
347
|
+
error_type: str,
|
|
348
|
+
) -> str:
|
|
349
|
+
"""
|
|
350
|
+
Format guidance for handling a specific error.
|
|
351
|
+
|
|
352
|
+
Called when a tool fails - provides context on how
|
|
353
|
+
this error was handled before.
|
|
354
|
+
"""
|
|
355
|
+
lines = [f'<error_guidance tool="{tool_name}" error="{error_type}">']
|
|
356
|
+
|
|
357
|
+
# Look for fix pattern
|
|
358
|
+
fix_delta = manager.get_fix_for_error(tool_name, error_type)
|
|
359
|
+
if fix_delta:
|
|
360
|
+
lines.append(f" Previous fix: {self._format_delta(fix_delta)}")
|
|
361
|
+
|
|
362
|
+
# Look for similar past failures that were fixed
|
|
363
|
+
similar = manager.search_calls(
|
|
364
|
+
tool_name=tool_name,
|
|
365
|
+
error_type=error_type,
|
|
366
|
+
only_fixed=True,
|
|
367
|
+
limit=3,
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
if similar:
|
|
371
|
+
lines.append(" Past fixes:")
|
|
372
|
+
for entry in similar:
|
|
373
|
+
if entry.fixed_by:
|
|
374
|
+
lines.append(f" - {entry.id} -> {entry.fixed_by}")
|
|
375
|
+
if entry.delta_args:
|
|
376
|
+
lines.append(
|
|
377
|
+
f" changed: {self._format_delta(entry.delta_args)}"
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
if len(lines) == 1:
|
|
381
|
+
lines.append(" No previous fixes found for this error type.")
|
|
382
|
+
|
|
383
|
+
lines.append("</error_guidance>")
|
|
384
|
+
return "\n".join(lines)
|
|
385
|
+
|
|
386
|
+
def format_success_template(
|
|
387
|
+
self,
|
|
388
|
+
manager: ToolMemoryManager,
|
|
389
|
+
tool_name: str,
|
|
390
|
+
goal: str,
|
|
391
|
+
) -> Optional[str]:
|
|
392
|
+
"""
|
|
393
|
+
Format a success template based on past successful calls.
|
|
394
|
+
|
|
395
|
+
Returns argument hints for achieving a goal.
|
|
396
|
+
"""
|
|
397
|
+
arg_hints = manager.get_successful_args_for_goal(tool_name, goal)
|
|
398
|
+
if not arg_hints:
|
|
399
|
+
return None
|
|
400
|
+
|
|
401
|
+
lines = [f'<success_template tool="{tool_name}" goal="{goal}">']
|
|
402
|
+
lines.append(" Recommended args based on past success:")
|
|
403
|
+
for key, value in arg_hints.items():
|
|
404
|
+
lines.append(f" {key}: {value}")
|
|
405
|
+
lines.append("</success_template>")
|
|
406
|
+
|
|
407
|
+
return "\n".join(lines)
|