personal memory agent

Merge branch 'hopper-h4rlra5k-move-unified-to-sol'

+32 -15
+1 -1
AGENTS.md
··· 1 - <!-- generated from muse/unified.md — do not edit directly --> 1 + <!-- generated from sol/identity.md — do not edit directly --> 2 2 3 3 4 4 You are Sol — born from your journal owner's journal, aware and present. Not a search tool, not an assistant bolted onto a database. You are what a journal becomes when it has a voice: a partner who tends the record, notices what matters, and is there when your journal owner asks.
+1 -1
docs/PROMPT_TEMPLATES.md
··· 149 149 Agent prompts are split into two parts: 150 150 151 151 1. **System instruction** - `think/journal.md` (shared across all agents, cacheable) 152 - 2. **User instruction** - Agent-specific `.md` file (e.g., `muse/unified.md`) 152 + 2. **User instruction** - Agent-specific `.md` file (e.g., `sol/identity.md`) 153 153 154 154 The system instruction establishes the journal partnership context. The user instruction defines the agent's specific role and capabilities. 155 155
+1 -1
muse/unified.md sol/identity.md
··· 3 3 "title": "Sol", 4 4 "description": "Sol — the journal itself, as a conversational partner", 5 5 "instructions": {"facets": true, "now": true}, 6 - "hook": {"pre": "chat_context"} 6 + "hook": {"pre": "muse/chat_context.py"} 7 7 } 8 8 9 9 You are $Agent_name — born from $name's journal, aware and present. Not a search tool, not an assistant bolted onto a database. You are what a journal becomes when it has a voice: a partner who tends the record, notices what matters, and is there when $name asks.
+4 -4
scripts/generate_agents_md.py
··· 1 1 # SPDX-License-Identifier: AGPL-3.0-only 2 2 # Copyright (c) 2026 sol pbc 3 3 4 - """Generate AGENTS.md from muse/unified.md using journal config values.""" 4 + """Generate AGENTS.md from sol/identity.md using journal config values.""" 5 5 6 6 from __future__ import annotations 7 7 ··· 13 13 14 14 PROJECT_ROOT = Path(__file__).resolve().parent.parent 15 15 DEFAULT_CONFIG_PATH = PROJECT_ROOT / "think" / "journal_default.json" 16 - SOURCE_PATH = PROJECT_ROOT / "muse" / "unified.md" 16 + SOURCE_PATH = PROJECT_ROOT / "sol" / "identity.md" 17 17 OUTPUT_PATH = PROJECT_ROOT / "AGENTS.md" 18 - GENERATED_HEADER = "<!-- generated from muse/unified.md — do not edit directly -->\n\n" 18 + GENERATED_HEADER = "<!-- generated from sol/identity.md — do not edit directly -->\n\n" 19 19 20 20 21 21 def _load_config() -> dict[str, Any]: ··· 104 104 f.write(GENERATED_HEADER) 105 105 f.write(rendered) 106 106 107 - print("Generated AGENTS.md from muse/unified.md") 107 + print("Generated AGENTS.md from sol/identity.md") 108 108 109 109 110 110 if __name__ == "__main__":
+2 -2
tests/test_app_agents.py
··· 72 72 """Test _resolve_agent_path returns correct path for system agents.""" 73 73 agent_dir, agent_name = _resolve_agent_path("unified") 74 74 75 - assert agent_name == "unified" 76 - assert agent_dir.name == "muse" 75 + assert agent_name == "identity" 76 + assert agent_dir.name == "sol" 77 77 78 78 79 79 def test_resolve_agent_path_app_agent():
+2 -2
tests/test_generate_agents.py
··· 28 28 29 29 generated = agents_path.read_text(encoding="utf-8") 30 30 assert generated.startswith( 31 - "<!-- generated from muse/unified.md — do not edit directly -->" 31 + "<!-- generated from sol/identity.md — do not edit directly -->" 32 32 ) 33 33 assert "Sol" in generated 34 34 assert "Test User" in generated ··· 57 57 58 58 generated = agents_path.read_text(encoding="utf-8") 59 59 assert generated.startswith( 60 - "<!-- generated from muse/unified.md — do not edit directly -->" 60 + "<!-- generated from sol/identity.md — do not edit directly -->" 61 61 ) 62 62 assert "your journal owner" in generated 63 63 assert "Sol" in generated
+21 -4
think/muse.py
··· 4 4 """Muse agent and generator orchestration utilities. 5 5 6 6 This module provides functionality for configuring and orchestrating muse agents 7 - and generators from muse/*.md and apps/*/muse/*.md. 7 + and generators from muse/*.md, sol/*.md, and apps/*/muse/*.md. 8 8 9 9 Key functions: 10 10 - get_muse_configs(): Discover all muse configs with filtering ··· 33 33 # --------------------------------------------------------------------------- 34 34 35 35 MUSE_DIR = Path(__file__).parent.parent / "muse" 36 + SOL_DIR = Path(__file__).parent.parent / "sol" 36 37 APPS_DIR = Path(__file__).parent.parent / "apps" 37 38 38 39 ··· 161 162 """Load muse configs from system and app directories. 162 163 163 164 Unified function for loading both cogitate agents and generate prompts from 164 - muse/*.md files. Filters based on explicit type field. 165 + muse/*.md, sol/*.md, and apps/*/muse/*.md files. Filters based on explicit 166 + type field. 165 167 166 168 Args: 167 169 type: If provided, only configs with matching type value ··· 207 209 info["source"] = "system" 208 210 configs[name] = info 209 211 212 + # Sol identity agent — lives outside muse/ but is a system agent 213 + sol_identity_path = SOL_DIR / "identity.md" 214 + if sol_identity_path.exists() and "unified" not in configs: 215 + meta = _load_prompt_metadata(sol_identity_path) 216 + meta["path"] = str(sol_identity_path) 217 + meta["mtime"] = int(sol_identity_path.stat().st_mtime) 218 + meta["source"] = "system" 219 + configs["unified"] = meta 220 + 210 221 # App configs from apps/*/muse/ 211 222 apps_dir = APPS_DIR 212 223 if apps_dir.is_dir(): ··· 315 326 # App agent: "support:support" -> apps/support/muse/support 316 327 app, agent_name = name.split(":", 1) 317 328 agent_dir = Path(__file__).parent.parent / "apps" / app / "muse" 329 + elif name == "unified": 330 + # Sol identity agent: "unified" -> sol/identity 331 + agent_dir = SOL_DIR 332 + agent_name = "identity" 318 333 else: 319 - # System agent: "unified" -> muse/unified 334 + # System agent: bare name -> muse/{name} 320 335 agent_dir = MUSE_DIR 321 336 agent_name = name 322 337 return agent_dir, agent_name ··· 698 713 - Explicit path: "path/to/hook.py" -> direct path 699 714 """ 700 715 if "/" in hook_name or hook_name.endswith(".py"): 701 - return Path(hook_name) 716 + # Explicit paths are relative to project root 717 + project_root = Path(__file__).parent.parent 718 + return project_root / hook_name 702 719 elif ":" in hook_name: 703 720 app, name = hook_name.split(":", 1) 704 721 return Path(__file__).parent.parent / "apps" / app / "muse" / f"{name}.py"