hanzo 0.3.22__py3-none-any.whl → 0.3.24__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.

Potentially problematic release.


This version of hanzo might be problematic. Click here for more details.

hanzo/memory_manager.py CHANGED
@@ -3,66 +3,70 @@ Memory management system for Hanzo Dev.
3
3
  Provides persistent context and memory like Claude Desktop.
4
4
  """
5
5
 
6
- import json
7
6
  import os
7
+ import json
8
+ import hashlib
9
+ from typing import Any, Dict, List, Optional
8
10
  from pathlib import Path
9
- from typing import Dict, List, Any, Optional
10
11
  from datetime import datetime
11
- from dataclasses import dataclass, asdict
12
- import hashlib
12
+ from dataclasses import asdict, dataclass
13
+
13
14
 
14
15
  @dataclass
15
16
  class MemoryItem:
16
17
  """A single memory item."""
18
+
17
19
  id: str
18
20
  content: str
19
21
  type: str # 'context', 'instruction', 'fact', 'code'
20
22
  created_at: str
21
23
  tags: List[str]
22
24
  priority: int = 0 # Higher priority items are kept longer
23
-
25
+
24
26
  def to_dict(self) -> Dict:
25
27
  return asdict(self)
26
-
28
+
27
29
  @classmethod
28
- def from_dict(cls, data: Dict) -> 'MemoryItem':
30
+ def from_dict(cls, data: Dict) -> "MemoryItem":
29
31
  return cls(**data)
30
32
 
31
33
 
32
34
  class MemoryManager:
33
35
  """Manages persistent memory and context for AI conversations."""
34
-
36
+
35
37
  def __init__(self, workspace_dir: str = None):
36
38
  """Initialize memory manager."""
37
39
  if workspace_dir:
38
40
  self.memory_dir = Path(workspace_dir) / ".hanzo" / "memory"
39
41
  else:
40
42
  self.memory_dir = Path.home() / ".hanzo" / "memory"
41
-
43
+
42
44
  self.memory_dir.mkdir(parents=True, exist_ok=True)
43
45
  self.memory_file = self.memory_dir / "context.json"
44
46
  self.session_file = self.memory_dir / "session.json"
45
-
47
+
46
48
  self.memories: List[MemoryItem] = []
47
49
  self.session_context: Dict[str, Any] = {}
48
-
50
+
49
51
  self.load_memories()
50
52
  self.load_session()
51
-
53
+
52
54
  def load_memories(self):
53
55
  """Load persistent memories from disk."""
54
56
  if self.memory_file.exists():
55
57
  try:
56
- with open(self.memory_file, 'r') as f:
58
+ with open(self.memory_file, "r") as f:
57
59
  data = json.load(f)
58
- self.memories = [MemoryItem.from_dict(item) for item in data.get('memories', [])]
60
+ self.memories = [
61
+ MemoryItem.from_dict(item) for item in data.get("memories", [])
62
+ ]
59
63
  except Exception as e:
60
64
  print(f"Error loading memories: {e}")
61
65
  self.memories = []
62
66
  else:
63
67
  # Initialize with default memories
64
68
  self._init_default_memories()
65
-
69
+
66
70
  def _init_default_memories(self):
67
71
  """Initialize with helpful default memories."""
68
72
  defaults = [
@@ -72,7 +76,7 @@ class MemoryManager:
72
76
  type="instruction",
73
77
  created_at=datetime.now().isoformat(),
74
78
  tags=["system", "identity"],
75
- priority=10
79
+ priority=10,
76
80
  ),
77
81
  MemoryItem(
78
82
  id=self._generate_id("capabilities"),
@@ -80,7 +84,7 @@ class MemoryManager:
80
84
  type="fact",
81
85
  created_at=datetime.now().isoformat(),
82
86
  tags=["system", "capabilities"],
83
- priority=9
87
+ priority=9,
84
88
  ),
85
89
  MemoryItem(
86
90
  id=self._generate_id("help"),
@@ -88,71 +92,77 @@ class MemoryManager:
88
92
  type="instruction",
89
93
  created_at=datetime.now().isoformat(),
90
94
  tags=["system", "usage"],
91
- priority=8
95
+ priority=8,
92
96
  ),
93
97
  ]
94
98
  self.memories = defaults
95
99
  self.save_memories()
96
-
100
+
97
101
  def save_memories(self):
98
102
  """Save memories to disk."""
99
103
  try:
100
104
  data = {
101
- 'memories': [m.to_dict() for m in self.memories],
102
- 'updated_at': datetime.now().isoformat()
105
+ "memories": [m.to_dict() for m in self.memories],
106
+ "updated_at": datetime.now().isoformat(),
103
107
  }
104
- with open(self.memory_file, 'w') as f:
108
+ with open(self.memory_file, "w") as f:
105
109
  json.dump(data, f, indent=2)
106
110
  except Exception as e:
107
111
  print(f"Error saving memories: {e}")
108
-
112
+
109
113
  def load_session(self):
110
114
  """Load current session context."""
111
115
  if self.session_file.exists():
112
116
  try:
113
- with open(self.session_file, 'r') as f:
117
+ with open(self.session_file, "r") as f:
114
118
  self.session_context = json.load(f)
115
- except:
119
+ except Exception:
116
120
  self.session_context = {}
117
121
  else:
118
122
  self.session_context = {
119
- 'started_at': datetime.now().isoformat(),
120
- 'messages': [],
121
- 'current_task': None,
122
- 'preferences': {}
123
+ "started_at": datetime.now().isoformat(),
124
+ "messages": [],
125
+ "current_task": None,
126
+ "preferences": {},
123
127
  }
124
-
128
+
125
129
  def save_session(self):
126
130
  """Save session context."""
127
131
  try:
128
- with open(self.session_file, 'w') as f:
132
+ with open(self.session_file, "w") as f:
129
133
  json.dump(self.session_context, f, indent=2)
130
134
  except Exception as e:
131
135
  print(f"Error saving session: {e}")
132
-
133
- def add_memory(self, content: str, type: str = "context", tags: List[str] = None, priority: int = 0) -> str:
136
+
137
+ def add_memory(
138
+ self,
139
+ content: str,
140
+ type: str = "context",
141
+ tags: List[str] = None,
142
+ priority: int = 0,
143
+ ) -> str:
134
144
  """Add a new memory item."""
135
145
  memory_id = self._generate_id(content)
136
-
146
+
137
147
  # Check if similar memory exists
138
148
  for mem in self.memories:
139
149
  if mem.content == content:
140
150
  return mem.id # Don't duplicate
141
-
151
+
142
152
  memory = MemoryItem(
143
153
  id=memory_id,
144
154
  content=content,
145
155
  type=type,
146
156
  created_at=datetime.now().isoformat(),
147
157
  tags=tags or [],
148
- priority=priority
158
+ priority=priority,
149
159
  )
150
-
160
+
151
161
  self.memories.append(memory)
152
162
  self.save_memories()
153
-
163
+
154
164
  return memory_id
155
-
165
+
156
166
  def remove_memory(self, memory_id: str) -> bool:
157
167
  """Remove a memory by ID."""
158
168
  for i, mem in enumerate(self.memories):
@@ -161,7 +171,7 @@ class MemoryManager:
161
171
  self.save_memories()
162
172
  return True
163
173
  return False
164
-
174
+
165
175
  def clear_memories(self, keep_system: bool = True):
166
176
  """Clear all memories, optionally keeping system memories."""
167
177
  if keep_system:
@@ -169,37 +179,39 @@ class MemoryManager:
169
179
  else:
170
180
  self.memories = []
171
181
  self.save_memories()
172
-
173
- def get_memories(self, type: str = None, tags: List[str] = None) -> List[MemoryItem]:
182
+
183
+ def get_memories(
184
+ self, type: str = None, tags: List[str] = None
185
+ ) -> List[MemoryItem]:
174
186
  """Get memories filtered by type or tags."""
175
187
  result = self.memories
176
-
188
+
177
189
  if type:
178
190
  result = [m for m in result if m.type == type]
179
-
191
+
180
192
  if tags:
181
193
  result = [m for m in result if any(tag in m.tags for tag in tags)]
182
-
194
+
183
195
  # Sort by priority and creation date
184
196
  result.sort(key=lambda m: (-m.priority, m.created_at), reverse=True)
185
-
197
+
186
198
  return result
187
-
199
+
188
200
  def get_context_string(self, max_tokens: int = 2000) -> str:
189
201
  """Get a formatted context string for AI prompts."""
190
202
  # Sort memories by priority
191
203
  sorted_memories = sorted(self.memories, key=lambda m: -m.priority)
192
-
204
+
193
205
  context_parts = []
194
206
  token_count = 0
195
-
207
+
196
208
  for memory in sorted_memories:
197
209
  # Rough token estimation (4 chars = 1 token)
198
210
  memory_tokens = len(memory.content) // 4
199
-
211
+
200
212
  if token_count + memory_tokens > max_tokens:
201
213
  break
202
-
214
+
203
215
  if memory.type == "instruction":
204
216
  context_parts.append(f"INSTRUCTION: {memory.content}")
205
217
  elif memory.type == "fact":
@@ -208,101 +220,99 @@ class MemoryManager:
208
220
  context_parts.append(f"CODE CONTEXT:\n{memory.content}")
209
221
  else:
210
222
  context_parts.append(memory.content)
211
-
223
+
212
224
  token_count += memory_tokens
213
-
225
+
214
226
  return "\n\n".join(context_parts)
215
-
227
+
216
228
  def add_message(self, role: str, content: str):
217
229
  """Add a message to session history."""
218
- self.session_context['messages'].append({
219
- 'role': role,
220
- 'content': content,
221
- 'timestamp': datetime.now().isoformat()
222
- })
223
-
230
+ self.session_context["messages"].append(
231
+ {"role": role, "content": content, "timestamp": datetime.now().isoformat()}
232
+ )
233
+
224
234
  # Keep only last 50 messages
225
- if len(self.session_context['messages']) > 50:
226
- self.session_context['messages'] = self.session_context['messages'][-50:]
227
-
235
+ if len(self.session_context["messages"]) > 50:
236
+ self.session_context["messages"] = self.session_context["messages"][-50:]
237
+
228
238
  self.save_session()
229
-
239
+
230
240
  def get_recent_messages(self, count: int = 10) -> List[Dict]:
231
241
  """Get recent messages from session."""
232
- return self.session_context['messages'][-count:]
233
-
242
+ return self.session_context["messages"][-count:]
243
+
234
244
  def set_preference(self, key: str, value: Any):
235
245
  """Set a user preference."""
236
- self.session_context['preferences'][key] = value
246
+ self.session_context["preferences"][key] = value
237
247
  self.save_session()
238
-
248
+
239
249
  def get_preference(self, key: str, default: Any = None) -> Any:
240
250
  """Get a user preference."""
241
- return self.session_context['preferences'].get(key, default)
242
-
251
+ return self.session_context["preferences"].get(key, default)
252
+
243
253
  def _generate_id(self, content: str) -> str:
244
254
  """Generate a unique ID for a memory item."""
245
255
  hash_input = f"{content}{datetime.now().isoformat()}"
246
256
  return hashlib.md5(hash_input.encode()).hexdigest()[:8]
247
-
257
+
248
258
  def summarize_for_ai(self) -> str:
249
259
  """Create a summary suitable for AI context."""
250
260
  summary = []
251
-
261
+
252
262
  # Add system memories
253
263
  system_memories = self.get_memories(tags=["system"])
254
264
  if system_memories:
255
265
  summary.append("SYSTEM CONTEXT:")
256
266
  for mem in system_memories[:3]: # Top 3 system memories
257
267
  summary.append(f"- {mem.content}")
258
-
268
+
259
269
  # Add recent instructions
260
270
  instructions = self.get_memories(type="instruction")
261
271
  if instructions:
262
272
  summary.append("\nINSTRUCTIONS:")
263
273
  for mem in instructions[:3]: # Top 3 instructions
264
274
  summary.append(f"- {mem.content}")
265
-
275
+
266
276
  # Add important facts
267
277
  facts = self.get_memories(type="fact")
268
278
  if facts:
269
279
  summary.append("\nKEY FACTS:")
270
280
  for mem in facts[:5]: # Top 5 facts
271
281
  summary.append(f"- {mem.content}")
272
-
282
+
273
283
  # Add current task if set
274
- if self.session_context.get('current_task'):
284
+ if self.session_context.get("current_task"):
275
285
  summary.append(f"\nCURRENT TASK: {self.session_context['current_task']}")
276
-
286
+
277
287
  return "\n".join(summary)
278
-
288
+
279
289
  def export_memories(self, file_path: str):
280
290
  """Export memories to a file."""
281
291
  data = {
282
- 'memories': [m.to_dict() for m in self.memories],
283
- 'session': self.session_context,
284
- 'exported_at': datetime.now().isoformat()
292
+ "memories": [m.to_dict() for m in self.memories],
293
+ "session": self.session_context,
294
+ "exported_at": datetime.now().isoformat(),
285
295
  }
286
-
287
- with open(file_path, 'w') as f:
296
+
297
+ with open(file_path, "w") as f:
288
298
  json.dump(data, f, indent=2)
289
-
299
+
290
300
  def import_memories(self, file_path: str):
291
301
  """Import memories from a file."""
292
- with open(file_path, 'r') as f:
302
+ with open(file_path, "r") as f:
293
303
  data = json.load(f)
294
-
304
+
295
305
  # Merge memories (avoid duplicates)
296
306
  existing_ids = {m.id for m in self.memories}
297
-
298
- for mem_data in data.get('memories', []):
299
- if mem_data['id'] not in existing_ids:
307
+
308
+ for mem_data in data.get("memories", []):
309
+ if mem_data["id"] not in existing_ids:
300
310
  self.memories.append(MemoryItem.from_dict(mem_data))
301
-
311
+
302
312
  # Merge session preferences
303
- if 'session' in data and 'preferences' in data['session']:
304
- self.session_context['preferences'].update(data['session']['preferences'])
305
-
313
+ if "session" in data and "preferences" in data["session"]:
314
+ self.session_context["preferences"].update(data["session"]["preferences"])
315
+
306
316
  self.save_memories()
307
317
  self.save_session()
308
318
 
@@ -312,85 +322,86 @@ def handle_memory_command(command: str, memory_manager: MemoryManager, console)
312
322
  Handle #memory commands.
313
323
  Returns True if command was handled, False otherwise.
314
324
  """
315
- from rich.table import Table
316
325
  from rich.panel import Panel
317
-
326
+ from rich.table import Table
327
+
318
328
  parts = command.strip().split(maxsplit=2)
319
-
329
+
320
330
  if len(parts) == 1 or parts[1] == "show":
321
331
  # Show current memories
322
332
  memories = memory_manager.get_memories()
323
-
333
+
324
334
  if not memories:
325
335
  console.print("[yellow]No memories stored.[/yellow]")
326
336
  return True
327
-
328
- table = Table(title="Current Memories", show_header=True,
329
- header_style="bold magenta")
337
+
338
+ table = Table(
339
+ title="Current Memories", show_header=True, header_style="bold magenta"
340
+ )
330
341
  table.add_column("ID", style="cyan", width=10)
331
342
  table.add_column("Type", width=12)
332
343
  table.add_column("Content", width=50)
333
344
  table.add_column("Priority", width=8)
334
-
345
+
335
346
  for mem in memories[:10]: # Show top 10
336
347
  content = mem.content[:47] + "..." if len(mem.content) > 50 else mem.content
337
348
  table.add_row(mem.id, mem.type, content, str(mem.priority))
338
-
349
+
339
350
  console.print(table)
340
-
351
+
341
352
  if len(memories) > 10:
342
353
  console.print(f"[dim]... and {len(memories) - 10} more[/dim]")
343
-
354
+
344
355
  return True
345
-
356
+
346
357
  elif parts[1] == "add":
347
358
  if len(parts) < 3:
348
359
  console.print("[red]Usage: #memory add <content>[/red]")
349
360
  return True
350
-
361
+
351
362
  content = parts[2]
352
363
  memory_id = memory_manager.add_memory(content, type="context")
353
364
  console.print(f"[green]Added memory: {memory_id}[/green]")
354
365
  return True
355
-
366
+
356
367
  elif parts[1] == "remove":
357
368
  if len(parts) < 3:
358
369
  console.print("[red]Usage: #memory remove <id>[/red]")
359
370
  return True
360
-
371
+
361
372
  memory_id = parts[2]
362
373
  if memory_manager.remove_memory(memory_id):
363
374
  console.print(f"[green]Removed memory: {memory_id}[/green]")
364
375
  else:
365
376
  console.print(f"[red]Memory not found: {memory_id}[/red]")
366
377
  return True
367
-
378
+
368
379
  elif parts[1] == "clear":
369
380
  memory_manager.clear_memories(keep_system=True)
370
381
  console.print("[green]Cleared all non-system memories.[/green]")
371
382
  return True
372
-
383
+
373
384
  elif parts[1] == "save":
374
385
  memory_manager.save_memories()
375
386
  memory_manager.save_session()
376
387
  console.print("[green]Memories saved.[/green]")
377
388
  return True
378
-
389
+
379
390
  elif parts[1] == "export":
380
391
  if len(parts) < 3:
381
392
  file_path = "hanzo_memories.json"
382
393
  else:
383
394
  file_path = parts[2]
384
-
395
+
385
396
  memory_manager.export_memories(file_path)
386
397
  console.print(f"[green]Exported memories to {file_path}[/green]")
387
398
  return True
388
-
399
+
389
400
  elif parts[1] == "import":
390
401
  if len(parts) < 3:
391
402
  console.print("[red]Usage: #memory import <file_path>[/red]")
392
403
  return True
393
-
404
+
394
405
  file_path = parts[2]
395
406
  try:
396
407
  memory_manager.import_memories(file_path)
@@ -398,14 +409,20 @@ def handle_memory_command(command: str, memory_manager: MemoryManager, console)
398
409
  except Exception as e:
399
410
  console.print(f"[red]Error importing: {e}[/red]")
400
411
  return True
401
-
412
+
402
413
  elif parts[1] == "context":
403
414
  # Show AI context
404
415
  context = memory_manager.summarize_for_ai()
405
- console.print(Panel(context, title="[bold cyan]AI Context[/bold cyan]",
406
- title_align="left", border_style="dim cyan"))
416
+ console.print(
417
+ Panel(
418
+ context,
419
+ title="[bold cyan]AI Context[/bold cyan]",
420
+ title_align="left",
421
+ border_style="dim cyan",
422
+ )
423
+ )
407
424
  return True
408
-
425
+
409
426
  elif parts[1] == "help":
410
427
  help_text = """Memory Commands:
411
428
  #memory [show] - Show current memories
@@ -417,9 +434,15 @@ def handle_memory_command(command: str, memory_manager: MemoryManager, console)
417
434
  #memory import <file> - Import memories from file
418
435
  #memory context - Show AI context summary
419
436
  #memory help - Show this help"""
420
-
421
- console.print(Panel(help_text, title="[bold cyan]Memory Help[/bold cyan]",
422
- title_align="left", border_style="dim cyan"))
437
+
438
+ console.print(
439
+ Panel(
440
+ help_text,
441
+ title="[bold cyan]Memory Help[/bold cyan]",
442
+ title_align="left",
443
+ border_style="dim cyan",
444
+ )
445
+ )
423
446
  return True
424
-
425
- return False
447
+
448
+ return False