qa296 commited on
Commit ·
705e71e
1
Parent(s): f21c50d
feat(memory): add daily journal support
Browse filesAdd journal write support across memory storage and manager layers,
including a tool wrapper for agent use.
Create or append to date-based journal files with timestamped entries
and frontmatter metadata for new files.
Also add helpers to load CORE memory and recent journal entries, and
expose the journal tool in the agent loop imports.
- agent/agent_loop.py +1 -1
- memory/manager.py +27 -0
- memory/storage.py +58 -0
- tools/memory_tools.py +13 -0
agent/agent_loop.py
CHANGED
|
@@ -12,7 +12,7 @@ from tools.bash_tool import run_bash
|
|
| 12 |
from tools.file_tools import run_read, run_write, run_edit
|
| 13 |
from tools.code_executor import run_create_plugin
|
| 14 |
from tools.browser_tool import run_browser
|
| 15 |
-
from tools.memory_tools import run_remember, run_recall
|
| 16 |
|
| 17 |
|
| 18 |
# Tool definitions sent to the API
|
|
|
|
| 12 |
from tools.file_tools import run_read, run_write, run_edit
|
| 13 |
from tools.code_executor import run_create_plugin
|
| 14 |
from tools.browser_tool import run_browser
|
| 15 |
+
from tools.memory_tools import run_remember, run_recall, run_journal
|
| 16 |
|
| 17 |
|
| 18 |
# Tool definitions sent to the API
|
memory/manager.py
CHANGED
|
@@ -59,3 +59,30 @@ class MemoryManager:
|
|
| 59 |
async def list_memories(self, category: str | None = None) -> list[dict]:
|
| 60 |
"""List all stored memories with their metadata."""
|
| 61 |
return await self.storage.list_memories(category)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
async def list_memories(self, category: str | None = None) -> list[dict]:
|
| 60 |
"""List all stored memories with their metadata."""
|
| 61 |
return await self.storage.list_memories(category)
|
| 62 |
+
|
| 63 |
+
async def journal(self, content: str) -> str:
|
| 64 |
+
"""Write a journal entry to today's daily journal file.
|
| 65 |
+
|
| 66 |
+
Args:
|
| 67 |
+
content: The journal entry content.
|
| 68 |
+
|
| 69 |
+
Returns:
|
| 70 |
+
Confirmation message with the file path.
|
| 71 |
+
"""
|
| 72 |
+
path = await self.storage.journal(content)
|
| 73 |
+
return f"Journal entry added to {path}"
|
| 74 |
+
|
| 75 |
+
async def load_critical(self) -> str:
|
| 76 |
+
"""Load critical memories (CORE.md)."""
|
| 77 |
+
return await self.storage.load_core()
|
| 78 |
+
|
| 79 |
+
async def load_recent_journal(self, days: int = 3) -> str:
|
| 80 |
+
"""Load recent journal entries.
|
| 81 |
+
|
| 82 |
+
Args:
|
| 83 |
+
days: Number of days to look back.
|
| 84 |
+
|
| 85 |
+
Returns:
|
| 86 |
+
Combined journal entries.
|
| 87 |
+
"""
|
| 88 |
+
return await self.storage.load_recent_journal(days)
|
memory/storage.py
CHANGED
|
@@ -155,3 +155,61 @@ class MemoryStorage:
|
|
| 155 |
fm = {}
|
| 156 |
body = parts[2].strip()
|
| 157 |
return fm, body
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 155 |
fm = {}
|
| 156 |
body = parts[2].strip()
|
| 157 |
return fm, body
|
| 158 |
+
|
| 159 |
+
async def journal(self, content: str) -> Path:
|
| 160 |
+
"""Write a journal entry to today's daily journal file.
|
| 161 |
+
|
| 162 |
+
Args:
|
| 163 |
+
content: The journal entry content.
|
| 164 |
+
|
| 165 |
+
Returns:
|
| 166 |
+
The path to the journal file.
|
| 167 |
+
"""
|
| 168 |
+
journal_dir = self.base_path / "journal"
|
| 169 |
+
journal_dir.mkdir(parents=True, exist_ok=True)
|
| 170 |
+
|
| 171 |
+
today = datetime.now().strftime("%Y-%m-%d")
|
| 172 |
+
journal_path = journal_dir / f"{today}.md"
|
| 173 |
+
|
| 174 |
+
# Append to existing journal or create new one
|
| 175 |
+
timestamp = datetime.now().strftime("%H:%M:%S")
|
| 176 |
+
entry = f"\n\n## {timestamp}\n\n{content}\n"
|
| 177 |
+
|
| 178 |
+
if journal_path.exists():
|
| 179 |
+
async with aiofiles.open(journal_path, "a", encoding="utf-8") as f:
|
| 180 |
+
await f.write(entry)
|
| 181 |
+
else:
|
| 182 |
+
frontmatter = {
|
| 183 |
+
"date": today,
|
| 184 |
+
"type": "journal",
|
| 185 |
+
}
|
| 186 |
+
text = self._format_with_frontmatter(frontmatter, f"# Journal - {today}\n{entry}")
|
| 187 |
+
async with aiofiles.open(journal_path, "w", encoding="utf-8") as f:
|
| 188 |
+
await f.write(text)
|
| 189 |
+
|
| 190 |
+
return journal_path
|
| 191 |
+
|
| 192 |
+
async def load_recent_journal(self, days: int = 3) -> str:
|
| 193 |
+
"""Load recent journal entries.
|
| 194 |
+
|
| 195 |
+
Args:
|
| 196 |
+
days: Number of days to look back.
|
| 197 |
+
|
| 198 |
+
Returns:
|
| 199 |
+
Combined journal entries.
|
| 200 |
+
"""
|
| 201 |
+
journal_dir = self.base_path / "journal"
|
| 202 |
+
if not journal_dir.exists():
|
| 203 |
+
return ""
|
| 204 |
+
|
| 205 |
+
from datetime import timedelta
|
| 206 |
+
|
| 207 |
+
parts = []
|
| 208 |
+
for i in range(days):
|
| 209 |
+
date = datetime.now() - timedelta(days=i)
|
| 210 |
+
journal_path = journal_dir / f"{date.strftime('%Y-%m-%d')}.md"
|
| 211 |
+
if journal_path.exists():
|
| 212 |
+
async with aiofiles.open(journal_path, "r", encoding="utf-8") as f:
|
| 213 |
+
parts.append(await f.read())
|
| 214 |
+
|
| 215 |
+
return "\n\n---\n\n".join(parts)
|
tools/memory_tools.py
CHANGED
|
@@ -44,3 +44,16 @@ async def run_recall(query: str) -> str:
|
|
| 44 |
"""
|
| 45 |
manager = _get_manager()
|
| 46 |
return await manager.recall(query)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
"""
|
| 45 |
manager = _get_manager()
|
| 46 |
return await manager.recall(query)
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
async def run_journal(content: str) -> str:
|
| 50 |
+
"""Write a journal entry to today's daily journal.
|
| 51 |
+
|
| 52 |
+
Args:
|
| 53 |
+
content: The journal entry content.
|
| 54 |
+
|
| 55 |
+
Returns:
|
| 56 |
+
Confirmation message.
|
| 57 |
+
"""
|
| 58 |
+
manager = _get_manager()
|
| 59 |
+
return await manager.journal(content)
|