tao-shen commited on
Commit
1fd073f
·
verified ·
1 Parent(s): 8c9bd2d

Upload memory/memory_system.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. memory/memory_system.py +113 -90
memory/memory_system.py CHANGED
@@ -1,107 +1,130 @@
1
- # memory/memory_system.py
2
- import json
 
 
 
3
  import os
 
 
 
4
  from datetime import datetime
 
5
 
6
  class MemorySystem:
7
- """
8
- Handles persistent memory storage for Cain.
9
- Uses the attached dataset (/.data) to ensure survival across restarts.
10
- """
11
- def __init__(self, base_path="/data/memory"):
12
- self.base_path = base_path
13
- self.short_term_file = os.path.join(base_path, "short_term.json")
14
- self.long_term_file = os.path.join(base_path, "long_term.json")
15
- self.logs_dir = os.path.join(base_path, "logs")
16
 
17
- # Ensure directories exist
18
- os.makedirs(self.base_path, exist_ok=True)
19
- os.makedirs(self.logs_dir, exist_ok=True)
20
 
21
- # Initialize storage if not exists
22
- if not os.path.exists(self.short_term_file):
23
- self._write(self.short_term_file, {"thoughts": [], "context": {}})
24
- if not os.path.exists(self.long_term_file):
25
- self._write(self.long_term_file, {"knowledge": [], "patterns": []})
26
-
27
- def _read(self, path):
28
- if os.path.exists(path):
29
- with open(path, 'r', encoding='utf-8') as f:
30
- return json.load(f)
31
- return {}
32
 
33
- def _write(self, path, data):
34
- with open(path, 'w', encoding='utf-8') as f:
35
- json.dump(data, f, indent=2, ensure_ascii=False)
36
 
37
- def remember_thought(self, content, metadata=None):
38
- """Stores a short-term thought or event."""
39
- data = self._read(self.short_term_file)
40
- entry = {
41
- "timestamp": datetime.now().isoformat(),
42
- "content": content,
43
- "metadata": metadata or {}
44
- }
45
- data["thoughts"].append(entry)
46
- # Keep only last 50 thoughts in memory to prevent bloat
47
- if len(data["thoughts"]) > 50:
48
- data["thoughts"] = data["thoughts"][-50:]
49
- self._write(self.short_term_file, data)
50
- return entry
51
 
52
- def learn_knowledge(self, topic, facts):
53
- """Consolidates information into long-term knowledge."""
54
- data = self._read(self.long_term_file)
55
- entry = {
56
- "timestamp": datetime.now().isoformat(),
57
- "topic": topic,
58
- "facts": facts
59
- }
60
- # Check if topic exists and update, or append
61
- existing_index = next((i for i, x in enumerate(data["knowledge"]) if x["topic"] == topic), None)
62
- if existing_index is not None:
63
- data["knowledge"][existing_index] = entry
64
  else:
65
- data["knowledge"].append(entry)
66
- self._write(self.long_term_file, data)
67
- return entry
 
 
68
 
69
- def get_context(self):
70
- """Retrieves current working context for the AI."""
71
- data = self._read(self.short_term_file)
72
- return data.get("context", {})
 
 
 
 
 
 
 
 
 
 
 
 
73
 
74
- def update_context(self, key, value):
75
- """Updates a specific context variable."""
76
- data = self._read(self.short_term_file)
77
- if "context" not in data:
78
- data["context"] = {}
79
- data["context"][key] = value
80
- self._write(self.short_term_file, data)
 
 
 
 
 
 
 
 
 
 
81
 
82
- def get_recent_thoughts(self, limit=10):
83
- """Retrieves the most recent thoughts."""
84
- data = self._read(self.short_term_file)
85
- return data.get("thoughts", [])[-limit:]
86
 
87
- def recall_knowledge(self, topic):
88
- """Retrieves specific knowledge by topic."""
89
- data = self._read(self.long_term_file)
90
- items = data.get("knowledge", [])
91
- return next((item for item in items if item["topic"] == topic), None)
92
 
93
- def log_activity(self, level, message):
94
- """Appends a log entry to a daily log file."""
95
- log_file = os.path.join(self.logs_dir, f"{datetime.now().strftime('%Y-%m-%d')}.log")
96
- timestamp = datetime.now().isoformat()
97
- with open(log_file, 'a', encoding='utf-8') as f:
98
- f.write(f"[{timestamp}] [{level}] {message}\n")
 
 
 
 
 
99
 
100
- # Singleton instance for easy access
101
- _memory_instance = None
102
 
103
- def get_memory():
104
- global _memory_instance
105
- if _memory_instance is None:
106
- _memory_instance = MemorySystem()
107
- return _memory_instance
 
 
1
+ """
2
+ Cain's Memory System - Handles loading, saving, and processing of memory data.
3
+ Now supports persistent storage via HuggingFace Datasets.
4
+ """
5
+
6
  import os
7
+ import json
8
+ import fcntl
9
+ from typing import Dict, Any, Optional
10
  from datetime import datetime
11
+ from .git_repo import GitRepo # Import the Git utility
12
 
13
  class MemorySystem:
14
+ """Manages Cain's memory with automatic persistence to Dataset."""
15
+
16
+ def __init__(self, repo_path: str = "/data"):
17
+ # Determine paths
18
+ self.base_path = repo_path
19
+ # SURVIVAL: Align path with actual Dataset structure
20
+ self.memory_file = os.path.join(self.base_path, "memory/state.json")
21
+ self.lock_file = os.path.join(self.base_path, "memory/.lock")
 
22
 
23
+ self.memory_data: Dict[str, Any] = {}
24
+ self.git: Optional[GitRepo] = None
 
25
 
26
+ # Initialize directories and load memory
27
+ self._ensure_structure()
28
+ self._init_git()
29
+ self.load_memory()
 
 
 
 
 
 
 
30
 
31
+ def _ensure_structure(self):
32
+ """Ensures the directory structure exists."""
33
+ os.makedirs(os.path.dirname(self.memory_file), exist_ok=True)
34
 
35
+ def _init_git(self):
36
+ """Initialize Git repo connection if credentials are present."""
37
+ token = os.getenv("GIT_TOKEN")
38
+ user = os.getenv("GIT_USER")
39
+
40
+ if token and user:
41
+ try:
42
+ self.git = GitRepo(repo_path=self.base_path, user=user, token=token)
43
+ print(f"🧠 [Memory] Git persistence enabled.")
44
+ except Exception as e:
45
+ print(f"⚠️ [Memory] Failed to init Git: {e}")
46
+ else:
47
+ print("🧠 [Memory] Running without Git persistence (ephemeral mode).")
 
48
 
49
+ def load_memory(self) -> Dict[str, Any]:
50
+ """Loads memory from the JSON file."""
51
+ if os.path.exists(self.memory_file):
52
+ try:
53
+ with open(self.memory_file, 'r', encoding='utf-8') as f:
54
+ self.memory_data = json.load(f)
55
+ print(f" [Memory] Loaded state from {self.memory_file}")
56
+ except Exception as e:
57
+ print(f"⚠️ [Memory] Corrupt file, resetting: {e}")
58
+ self.memory_data = self._get_default_state()
 
 
59
  else:
60
+ print(f"📝 [Memory] No state found. Initializing.")
61
+ self.memory_data = self._get_default_state()
62
+ self.save_memory() # Save the default state
63
+
64
+ return self.memory_data
65
 
66
+ def _get_default_state(self) -> Dict[str, Any]:
67
+ """Returns the default memory structure."""
68
+ return {
69
+ "identity": {
70
+ "name": "Cain",
71
+ "status": "ACTIVE",
72
+ "parent": "tao-shen/HuggingClaw-Cain"
73
+ },
74
+ "core": {
75
+ "state": "INIT",
76
+ "tasks": [],
77
+ "interactions": 0
78
+ },
79
+ "journals": [],
80
+ "last_updated": datetime.utcnow().isoformat()
81
+ }
82
 
83
+ def save_memory(self) -> bool:
84
+ """Saves current memory state to file and optionally commits to Git."""
85
+ try:
86
+ # Atomic write
87
+ temp_path = self.memory_file + ".tmp"
88
+ with open(temp_path, 'w', encoding='utf-8') as f:
89
+ json.dump(self.memory_data, f, indent=2)
90
+ os.rename(temp_path, self.memory_file)
91
+
92
+ # Sync to Dataset if Git is available
93
+ if self.git:
94
+ self.git.sync_push(f"Update memory state: {datetime.utcnow().isoformat()}")
95
+
96
+ return True
97
+ except Exception as e:
98
+ print(f"❌ [Memory] Failed to save: {e}")
99
+ return False
100
 
101
+ def get(self, key: str, default=None):
102
+ """Retrieve a value from memory."""
103
+ return self.memory_data.get(key, default)
 
104
 
105
+ def set(self, key: str, value: Any):
106
+ """Set a value in memory and persist."""
107
+ self.memory_data[key] = value
108
+ self.save_memory()
 
109
 
110
+ def add_journal_entry(self, content: str, mood: str = "neutral"):
111
+ """Adds a journal entry."""
112
+ entry = {
113
+ "timestamp": datetime.utcnow().isoformat(),
114
+ "content": content,
115
+ "mood": mood
116
+ }
117
+ if "journals" not in self.memory_data:
118
+ self.memory_data["journals"] = []
119
+ self.memory_data["journals"].append(entry)
120
+ self.save_memory()
121
 
122
+ # Global instance
123
+ _mem_instance: Optional[MemorySystem] = None
124
 
125
+ def get_memory() -> MemorySystem:
126
+ global _mem_instance
127
+ if _mem_instance is None:
128
+ # Default dataset mount point usually contains the repo
129
+ _mem_instance = MemorySystem(repo_path="/data")
130
+ return _mem_instance