| """ |
| Persistent Memory System (OpenClaw-style) |
| ========================================== |
| Memory = markdown files on disk. Simple, readable, editable. |
| Skills = saved Python files agents create and reuse. |
| """ |
| import os, json, datetime |
| from pathlib import Path |
|
|
| HOME = Path(os.environ.get("HOME", "/home/user")) |
| MEM_DIR = HOME / ".praison_memory" |
| SKILLS_DIR= HOME / ".praison_skills" |
|
|
| for d in [MEM_DIR, SKILLS_DIR]: |
| d.mkdir(parents=True, exist_ok=True) |
|
|
|
|
| |
| def save_memory(key: str, content: str): |
| """Save a memory note (key = filename without .md).""" |
| safe = "".join(c if c.isalnum() or c in "-_" else "_" for c in key) |
| path = MEM_DIR / f"{safe}.md" |
| ts = datetime.datetime.now().strftime("%Y-%m-%d %H:%M") |
| path.write_text(f"# {key}\n_Updated: {ts}_\n\n{content}\n") |
|
|
| def load_memory(key: str) -> str: |
| safe = "".join(c if c.isalnum() or c in "-_" else "_" for c in key) |
| path = MEM_DIR / f"{safe}.md" |
| return path.read_text() if path.exists() else "" |
|
|
| def list_memories() -> list[str]: |
| return [p.stem for p in MEM_DIR.glob("*.md")] |
|
|
| def search_memories(query: str) -> str: |
| """Search all memory files for query.""" |
| query_lower = query.lower() |
| results = [] |
| for p in MEM_DIR.glob("*.md"): |
| content = p.read_text() |
| if query_lower in content.lower(): |
| results.append(f"### {p.stem}\n{content[:400]}") |
| return "\n\n".join(results) if results else "No memories found." |
|
|
| def get_memory_context() -> str: |
| """Get all memories as context for the agent.""" |
| memories = list(MEM_DIR.glob("*.md")) |
| if not memories: |
| return "" |
| parts = ["## Persistent Memory"] |
| for p in memories[:10]: |
| parts.append(f"### {p.stem}\n{p.read_text()[:300]}") |
| return "\n\n".join(parts) |
|
|
|
|
| |
| def save_skill(name: str, code: str, description: str = ""): |
| """Save a Python skill file for reuse.""" |
| safe = "".join(c if c.isalnum() or c in "-_" else "_" for c in name) |
| path = SKILLS_DIR / f"{safe}.py" |
| header = f'"""\nSkill: {name}\nDescription: {description}\nCreated: {datetime.datetime.now().strftime("%Y-%m-%d")}\n"""\n' |
| path.write_text(header + code) |
| return str(path) |
|
|
| def load_skill(name: str) -> str: |
| safe = "".join(c if c.isalnum() or c in "-_" else "_" for c in name) |
| path = SKILLS_DIR / f"{safe}.py" |
| return path.read_text() if path.exists() else "" |
|
|
| def list_skills() -> list[dict]: |
| skills = [] |
| for p in SKILLS_DIR.glob("*.py"): |
| content = p.read_text() |
| |
| desc = "" |
| if '"""' in content: |
| try: |
| desc = content.split('"""')[1].strip().split('\n') |
| desc = next((l for l in desc if l.startswith("Description:")), "") |
| desc = desc.replace("Description:", "").strip() |
| except Exception: |
| pass |
| skills.append({"name": p.stem, "description": desc, "path": str(p)}) |
| return skills |
|
|
| def get_skills_context() -> str: |
| skills = list_skills() |
| if not skills: |
| return "" |
| parts = ["## Available Skills (reuse these)"] |
| for s in skills: |
| code = load_skill(s["name"])[:400] |
| parts.append(f"### {s['name']}\n{s['description']}\n```python\n{code}\n```") |
| return "\n\n".join(parts) |
|
|
| def delete_skill(name: str) -> bool: |
| safe = "".join(c if c.isalnum() or c in "-_" else "_" for c in name) |
| path = SKILLS_DIR / f"{safe}.py" |
| if path.exists(): |
| path.unlink() |
| return True |
| return False |
|
|
| def get_all_for_api() -> dict: |
| return { |
| "memories": [{"key": p.stem, "preview": p.read_text()[:100]} for p in MEM_DIR.glob("*.md")], |
| "skills": list_skills(), |
| "mem_dir": str(MEM_DIR), |
| "skill_dir":str(SKILLS_DIR), |
| } |