personal memory agent
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Make get_muse_configs() metadata-only, remove expensive get_agent() path

get_muse_configs() was calling get_agent() → compose_instructions() for
every tool-using prompt, loading facet summaries and entity data from
the full journal (~40s). No caller needed the hydrated instructions —
cortex.py already calls get_agent() directly at spawn time.

Remove the conditional get_agent() branches so all configs go through
the fast _load_prompt_metadata() path. Simplify muse_cli.py to use
get_muse_configs() directly now that it's always fast.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

+15 -94
+6 -5
tests/test_app_agents.py
··· 121 121 122 122 123 123 def test_get_muse_configs_includes_system_agents(fixture_journal): 124 - """Test get_muse_configs returns system agents.""" 124 + """Test get_muse_configs returns system agents with metadata.""" 125 125 agents = get_muse_configs(has_tools=True) 126 126 127 - # Should include known system agents 127 + # Should include known system agents with frontmatter metadata 128 128 assert "default" in agents 129 129 assert agents["default"]["source"] == "system" 130 - assert "system_instruction" in agents["default"] 131 - assert "user_instruction" in agents["default"] 130 + assert "title" in agents["default"] 131 + assert "tools" in agents["default"] 132 + assert "path" in agents["default"] 132 133 133 134 134 135 def test_get_muse_configs_system_agents_have_metadata(fixture_journal): ··· 140 141 assert default is not None 141 142 assert default["source"] == "system" 142 143 assert "title" in default 143 - assert "name" in default 144 + assert "color" in default 144 145 145 146 146 147 def test_get_muse_configs_excludes_private_apps(fixture_journal, tmp_path, monkeypatch):
+7 -64
think/muse_cli.py
··· 28 28 from think.utils import ( 29 29 MUSE_DIR, 30 30 _load_prompt_metadata, 31 - get_config, 31 + get_muse_configs, 32 32 setup_cli, 33 33 ) 34 34 35 35 # Project root for computing relative paths 36 36 _PROJECT_ROOT = Path(__file__).parent.parent 37 37 38 - # Keys injected by get_agent() or internal bookkeeping — not frontmatter 39 - _SKIP_KEYS = frozenset( 40 - { 41 - "path", 42 - "mtime", 43 - "hook_path", 44 - "system_instruction", 45 - "user_instruction", 46 - "extra_context", 47 - "system_prompt_name", 48 - "sources", 49 - } 50 - ) 38 + # Internal bookkeeping keys to exclude from JSONL output 39 + _INTERNAL_KEYS = frozenset({"path", "mtime", "hook_path"}) 51 40 52 41 53 42 def _relative_path(abs_path: str) -> str: ··· 66 55 return MUSE_DIR / f"{name}.md" 67 56 68 57 69 - def _resolve_file_path(key: str, info: dict[str, Any]) -> str: 70 - """Resolve the relative file path for a config entry.""" 71 - if info.get("path"): 72 - return _relative_path(str(info["path"])) 73 - # Configs loaded via get_agent() lose their path — reconstruct from key 74 - return _relative_path(str(_resolve_md_path(key))) 75 - 76 - 77 58 def _scan_variables(body: str) -> list[str]: 78 59 """Scan prompt body text for $template variables.""" 79 60 # Match $word or ${word} but not $$ (escaped dollar signs) ··· 116 97 source: str | None = None, 117 98 include_disabled: bool = False, 118 99 ) -> dict[str, dict[str, Any]]: 119 - """Collect muse configs using metadata-only loading (no instruction composition). 100 + """Collect all muse configs with optional filters applied.""" 101 + configs = get_muse_configs(schedule=schedule, include_disabled=True) 120 102 121 - This avoids the expensive get_agent() path that compose_instructions() uses, 122 - which loads facet summaries and entity data from the journal. 123 - """ 124 - configs: dict[str, dict[str, Any]] = {} 125 - 126 - # System configs from muse/ 127 - if MUSE_DIR.is_dir(): 128 - for md_path in sorted(MUSE_DIR.glob("*.md")): 129 - info = _load_prompt_metadata(md_path) 130 - info["source"] = "system" 131 - configs[md_path.stem] = info 132 - 133 - # App configs from apps/*/muse/ 134 - apps_dir = _PROJECT_ROOT / "apps" 135 - if apps_dir.is_dir(): 136 - for app_path in sorted(apps_dir.iterdir()): 137 - if not app_path.is_dir() or app_path.name.startswith("_"): 138 - continue 139 - app_muse_dir = app_path / "muse" 140 - if not app_muse_dir.is_dir(): 141 - continue 142 - for md_path in sorted(app_muse_dir.glob("*.md")): 143 - info = _load_prompt_metadata(md_path) 144 - info["source"] = "app" 145 - info["app"] = app_path.name 146 - configs[f"{app_path.name}:{md_path.stem}"] = info 147 - 148 - # Merge journal config overrides (disabled/extract) 149 - overrides = get_config().get("agents", {}) 150 - for key, override in overrides.items(): 151 - if key in configs and isinstance(override, dict): 152 - if "disabled" in override: 153 - configs[key]["disabled"] = override["disabled"] 154 - if "extract" in override: 155 - configs[key]["extract"] = override["extract"] 156 - 157 - # Apply filters 158 103 filtered: dict[str, dict[str, Any]] = {} 159 104 for key, info in configs.items(): 160 105 if not include_disabled and info.get("disabled", False): 161 - continue 162 - if schedule and info.get("schedule") != schedule: 163 106 continue 164 107 if source and info.get("source") != source: 165 108 continue ··· 170 113 171 114 def _to_jsonl_record(key: str, info: dict[str, Any]) -> dict[str, Any]: 172 115 """Build a clean JSONL record from a config entry.""" 173 - record: dict[str, Any] = {"file": _resolve_file_path(key, info)} 116 + record: dict[str, Any] = {"file": _relative_path(str(info["path"]))} 174 117 for k, v in info.items(): 175 - if k not in _SKIP_KEYS: 118 + if k not in _INTERNAL_KEYS: 176 119 record[k] = v 177 120 return record 178 121
+2 -25
think/utils.py
··· 967 967 continue 968 968 969 969 info["source"] = "system" 970 - 971 - # For tool agents, load full config via get_agent() 972 - if "tools" in info: 973 - try: 974 - config = get_agent(name) 975 - config["title"] = config.get("title", name) 976 - config["source"] = "system" 977 - configs[name] = config 978 - except Exception: 979 - pass # Skip configs that can't be loaded 980 - else: 981 - configs[name] = info 970 + configs[name] = info 982 971 983 972 # App configs from apps/*/muse/ 984 973 apps_dir = Path(__file__).parent.parent / "apps" ··· 1000 989 key = f"{app_name}:{item_name}" 1001 990 info["source"] = "app" 1002 991 info["app"] = app_name 1003 - 1004 - # For tool agents, load full config via get_agent() 1005 - if "tools" in info: 1006 - try: 1007 - config = get_agent(key) 1008 - config["title"] = config.get("title", item_name) 1009 - config["source"] = "app" 1010 - config["app"] = app_name 1011 - configs[key] = config 1012 - except Exception: 1013 - pass # Skip configs that can't be loaded 1014 - else: 1015 - configs[key] = info 992 + configs[key] = info 1016 993 1017 994 # Merge journal config overrides (applies to generators) 1018 995 overrides = get_config().get("agents", {})