personal memory agent
0
fork

Configure Feed

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

Require explicit instructions config for all muse agents

Change default instruction configuration from implicit to explicit opt-in:
- system: "journal" → None (empty string if not specified)
- facets: true → false
- sources.audio/screen: true → false

This ensures every agent explicitly declares what instructions it needs
rather than relying on hidden defaults. Generators must now specify their
sources, and tool agents must opt-in to system prompts and facet context.

Updated 14 muse prompt files to preserve their existing behavior by adding
explicit instructions configs. Utility prompts (occurrence.md, anticipation.md)
intentionally left without instructions as they don't use the system.

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

+68 -57
+2 -1
apps/entities/muse/entities.md
··· 7 7 "priority": 55, 8 8 "tools": "journal, entities", 9 9 "multi_facet": true, 10 - "group": "Entities" 10 + "group": "Entities", 11 + "instructions": {"system": "journal", "facets": true} 11 12 12 13 } 13 14
+2 -1
apps/entities/muse/entities_review.md
··· 7 7 "priority": 56, 8 8 "tools": "journal, entities", 9 9 "multi_facet": true, 10 - "group": "Entities" 10 + "group": "Entities", 11 + "instructions": {"system": "journal", "facets": true} 11 12 12 13 } 13 14
+2 -1
apps/entities/muse/entity_assist.md
··· 4 4 "description": "Quick entity addition with intelligent type detection and automatic description generation", 5 5 "color": "#00695c", 6 6 "tools": "journal, entities", 7 - "group": "Entities" 7 + "group": "Entities", 8 + "instructions": {"system": "journal", "facets": true} 8 9 9 10 } 10 11
+2 -1
apps/entities/muse/entity_describe.md
··· 4 4 "description": "Research and generate single-sentence descriptions for attached entities", 5 5 "color": "#26a69a", 6 6 "tools": "journal", 7 - "group": "Entities" 7 + "group": "Entities", 8 + "instructions": {"system": "journal", "facets": true} 8 9 9 10 } 10 11
+2 -1
apps/entities/muse/entity_observer.md
··· 7 7 "priority": 57, 8 8 "tools": "journal, entities", 9 9 "multi_facet": true, 10 - "group": "Entities" 10 + "group": "Entities", 11 + "instructions": {"system": "journal", "facets": true} 11 12 12 13 } 13 14
+2 -1
apps/todos/muse/review.md
··· 7 7 "priority": 60, 8 8 "tools": "journal, todo", 9 9 "multi_facet": true, 10 - "group": "Todos" 10 + "group": "Todos", 11 + "instructions": {"system": "journal", "facets": true} 11 12 12 13 } 13 14
+2 -1
apps/todos/muse/todo.md
··· 7 7 "priority": 50, 8 8 "tools": "journal, todo", 9 9 "multi_facet": true, 10 - "group": "Todos" 10 + "group": "Todos", 11 + "instructions": {"system": "journal", "facets": true} 11 12 12 13 } 13 14
+2 -1
apps/todos/muse/weekly.md
··· 4 4 "description": "Audits the past week's journal follow-ups to confirm completions and surface the next five high-impact todos for today.", 5 5 "color": "#f4511e", 6 6 "tools": "journal, todo", 7 - "group": "Todos" 7 + "group": "Todos", 8 + "instructions": {"system": "journal", "facets": true} 8 9 9 10 } 10 11
+2 -1
muse/daily_news.md
··· 5 5 "color": "#1565c0", 6 6 "schedule": "daily", 7 7 "priority": 45, 8 - "tools": "journal, facets" 8 + "tools": "journal, facets", 9 + "instructions": {"system": "journal", "facets": true} 9 10 10 11 } 11 12
+2 -1
muse/decisionalizer.md
··· 6 6 "schedule": "daily", 7 7 "priority": 60, 8 8 "output": "md", 9 - "tools": "default" 9 + "tools": "default", 10 + "instructions": {"system": "journal", "facets": true} 10 11 11 12 } 12 13
+2 -1
muse/default.md
··· 5 5 "color": "#455a64", 6 6 "label": "Chat Messages", 7 7 "group": "Apps", 8 - "tools": "journal, todo, entities" 8 + "tools": "journal, todo, entities", 9 + "instructions": {"system": "journal", "facets": true} 9 10 10 11 } 11 12
+2 -1
muse/facet_newsletter.md
··· 6 6 "schedule": "daily", 7 7 "priority": 40, 8 8 "multi_facet": true, 9 - "tools": "journal, facets" 9 + "tools": "journal, facets", 10 + "instructions": {"system": "journal", "facets": true} 10 11 11 12 } 12 13
+2 -1
muse/importer.md
··· 4 4 "description": "Analyzes imported audio transcripts to extract knowledge, entities, and action items into a comprehensive summary", 5 5 "color": "#1976d2", 6 6 "extract": false, 7 - "output": "md" 7 + "output": "md", 8 + "instructions": {"system": "journal", "facets": true} 8 9 9 10 } 10 11
+2 -1
muse/joke_bot.md
··· 5 5 "color": "#f9a825", 6 6 "schedule": "daily", 7 7 "priority": 99, 8 - "tools": "default" 8 + "tools": "default", 9 + "instructions": {"system": "journal", "facets": true} 9 10 10 11 } 11 12
+2 -2
tests/test_entity_agents.py
··· 121 121 assert "Full Featured Facet" in extra_context 122 122 assert "A facet for testing all features" in extra_context 123 123 124 - # Should include full entity details from the focused facet 125 - assert "## Entities" in extra_context 124 + # Should include entity details from the focused facet (inline format) 125 + assert "**Entities**:" in extra_context or "Entities:" in extra_context 126 126 assert "Entity 1" in extra_context or "First test entity" in extra_context 127 127 128 128
+8 -8
tests/test_generate_full.py
··· 73 73 mod = importlib.import_module("think.agents") 74 74 copy_day(tmp_path) 75 75 76 - # Create a test generator in muse directory 76 + # Create a test generator in muse directory (with explicit sources) 77 77 muse_dir = Path(mod.__file__).resolve().parent.parent / "muse" 78 78 test_generator = muse_dir / "test_gen.md" 79 79 test_generator.write_text( 80 - '{\n "schedule": "daily",\n "priority": 10,\n "output": "md"\n}\n\nTest prompt' 80 + '{\n "schedule": "daily",\n "priority": 10,\n "output": "md",\n "instructions": {"system": "journal", "sources": {"audio": true, "screen": true}}\n}\n\nTest prompt' 81 81 ) 82 82 83 83 try: ··· 144 144 return None 145 145 """) 146 146 147 - # Create generator with hook (new format) 147 + # Create generator with hook (new format, with explicit sources) 148 148 test_generator = muse_dir / "hooked_gen.md" 149 149 test_generator.write_text( 150 - '{\n "title": "Hooked",\n "schedule": "daily",\n "priority": 10,\n "output": "md",\n "hook": {"post": "test_hook"}\n}\n\nTest prompt' 150 + '{\n "title": "Hooked",\n "schedule": "daily",\n "priority": 10,\n "output": "md",\n "hook": {"post": "test_hook"},\n "instructions": {"system": "journal", "sources": {"audio": true, "screen": true}}\n}\n\nTest prompt' 151 151 ) 152 152 153 153 try: ··· 199 199 mod = importlib.import_module("think.agents") 200 200 copy_day(tmp_path) 201 201 202 - # Create generator without hook 202 + # Create generator without hook (with explicit sources) 203 203 muse_dir = Path(mod.__file__).resolve().parent.parent / "muse" 204 204 test_generator = muse_dir / "nohook_gen.md" 205 205 test_generator.write_text( 206 - '{\n "schedule": "daily",\n "priority": 10,\n "output": "md"\n}\n\nNo hook prompt' 206 + '{\n "schedule": "daily",\n "priority": 10,\n "output": "md",\n "instructions": {"system": "journal", "sources": {"audio": true, "screen": true}}\n}\n\nNo hook prompt' 207 207 ) 208 208 209 209 try: ··· 268 268 day_dir = day_path("20240101") 269 269 day_dir.mkdir(parents=True, exist_ok=True) 270 270 271 - # Create a test generator 271 + # Create a test generator (with explicit sources) 272 272 muse_dir = Path(mod.__file__).resolve().parent.parent / "muse" 273 273 test_generator = muse_dir / "empty_gen.md" 274 274 test_generator.write_text( 275 - '{\n "schedule": "daily",\n "priority": 10,\n "output": "md"\n}\n\nTest prompt' 275 + '{\n "schedule": "daily",\n "priority": 10,\n "output": "md",\n "instructions": {"system": "journal", "sources": {"audio": true, "screen": true}}\n}\n\nTest prompt' 276 276 ) 277 277 278 278 try:
+5 -5
tests/test_output_hooks.py
··· 179 179 180 180 prompt_file = muse_dir / "hooked_test.md" 181 181 prompt_file.write_text( 182 - '{\n "title": "Hooked",\n "schedule": "daily",\n "priority": 10,\n "output": "md",\n "hook": {"post": "hooked_test"}\n}\n\nTest prompt' 182 + '{\n "title": "Hooked",\n "schedule": "daily",\n "priority": 10,\n "output": "md",\n "hook": {"post": "hooked_test"},\n "instructions": {"system": "journal", "sources": {"audio": true, "screen": true}}\n}\n\nTest prompt' 183 183 ) 184 184 185 185 hook_file = muse_dir / "hooked_test.py" ··· 237 237 238 238 prompt_file = muse_dir / "noop_test.md" 239 239 prompt_file.write_text( 240 - '{\n "title": "Noop",\n "schedule": "daily",\n "priority": 10,\n "output": "md",\n "hook": {"post": "noop_test"}\n}\n\nTest prompt' 240 + '{\n "title": "Noop",\n "schedule": "daily",\n "priority": 10,\n "output": "md",\n "hook": {"post": "noop_test"},\n "instructions": {"system": "journal", "sources": {"audio": true, "screen": true}}\n}\n\nTest prompt' 241 241 ) 242 242 243 243 hook_file = muse_dir / "noop_test.py" ··· 287 287 288 288 prompt_file = muse_dir / "broken_test.md" 289 289 prompt_file.write_text( 290 - '{\n "title": "Broken",\n "schedule": "daily",\n "priority": 10,\n "output": "md",\n "hook": {"post": "broken_test"}\n}\n\nTest prompt' 290 + '{\n "title": "Broken",\n "schedule": "daily",\n "priority": 10,\n "output": "md",\n "hook": {"post": "broken_test"},\n "instructions": {"system": "journal", "sources": {"audio": true, "screen": true}}\n}\n\nTest prompt' 291 291 ) 292 292 293 293 hook_file = muse_dir / "broken_test.py" ··· 574 574 575 575 prompt_file = muse_dir / "prehooked_test.md" 576 576 prompt_file.write_text( 577 - '{\n "title": "Prehooked",\n "schedule": "daily",\n "priority": 10,\n "output": "md",\n "hook": {"pre": "prehooked_test"}\n}\n\nOriginal prompt' 577 + '{\n "title": "Prehooked",\n "schedule": "daily",\n "priority": 10,\n "output": "md",\n "hook": {"pre": "prehooked_test"},\n "instructions": {"system": "journal", "sources": {"audio": true, "screen": true}}\n}\n\nOriginal prompt' 578 578 ) 579 579 580 580 hook_file = muse_dir / "prehooked_test.py" ··· 634 634 635 635 prompt_file = muse_dir / "both_hooks_test.md" 636 636 prompt_file.write_text( 637 - '{\n "title": "Both Hooks",\n "schedule": "daily",\n "priority": 10,\n "output": "md",\n "hook": {"pre": "both_hooks_test", "post": "both_hooks_test"}\n}\n\nOriginal prompt' 637 + '{\n "title": "Both Hooks",\n "schedule": "daily",\n "priority": 10,\n "output": "md",\n "hook": {"pre": "both_hooks_test", "post": "both_hooks_test"},\n "instructions": {"system": "journal", "sources": {"audio": true, "screen": true}}\n}\n\nOriginal prompt' 638 638 ) 639 639 640 640 hook_file = muse_dir / "both_hooks_test.py"
+10 -17
tests/test_think_utils.py
··· 715 715 class TestComposeInstructions: 716 716 """Tests for compose_instructions function.""" 717 717 718 - def test_default_system_instruction_is_journal(self, monkeypatch, tmp_path): 719 - """Test that default system instruction loads from journal.md.""" 720 - # Create journal.md in think/ directory 718 + def test_default_system_instruction_is_none(self, monkeypatch, tmp_path): 719 + """Test that default system instruction is empty (agents must opt-in).""" 721 720 think_dir = tmp_path / "think" 722 721 think_dir.mkdir() 723 - journal_txt = think_dir / "journal.md" 724 - journal_txt.write_text("Test system instruction content") 725 722 726 - # Mock the think module's parent to use our temp dir 727 723 import think.utils 728 724 729 725 original_file = think.utils.__file__ ··· 736 732 monkeypatch.setattr(think.utils, "__file__", original_file) 737 733 738 734 assert "system_instruction" in result 739 - assert result["system_prompt_name"] == "journal" 735 + assert result["system_instruction"] == "" 736 + assert result["system_prompt_name"] == "" 740 737 741 738 def test_custom_system_instruction(self, monkeypatch, tmp_path): 742 739 """Test that custom system prompt can be loaded.""" ··· 851 848 assert "Current Date and Time" in result["extra_context"] 852 849 853 850 def test_sources_returned_from_defaults(self, monkeypatch, tmp_path): 854 - """Test that sources config is returned with defaults.""" 851 + """Test that sources config is returned with defaults (all false).""" 855 852 think_dir = tmp_path / "think" 856 853 think_dir.mkdir() 857 - journal_txt = think_dir / "journal.md" 858 - journal_txt.write_text("System instruction") 859 854 860 855 import think.utils 861 856 ··· 865 860 result = compose_instructions() 866 861 867 862 assert "sources" in result 868 - assert result["sources"]["audio"] is True 869 - assert result["sources"]["screen"] is True 863 + assert result["sources"]["audio"] is False 864 + assert result["sources"]["screen"] is False 870 865 assert result["sources"]["agents"] is False 871 866 872 867 def test_sources_can_be_overridden(self, monkeypatch, tmp_path): 873 868 """Test that sources config can be overridden.""" 874 869 think_dir = tmp_path / "think" 875 870 think_dir.mkdir() 876 - journal_txt = think_dir / "journal.md" 877 - journal_txt.write_text("System instruction") 878 871 879 872 import think.utils 880 873 ··· 883 876 884 877 result = compose_instructions( 885 878 config_overrides={ 886 - "sources": {"audio": False, "agents": True}, 879 + "sources": {"audio": True, "agents": True}, 887 880 }, 888 881 ) 889 882 890 - assert result["sources"]["audio"] is False 891 - assert result["sources"]["screen"] is True # Default preserved 883 + assert result["sources"]["audio"] is True # Overridden 884 + assert result["sources"]["screen"] is False # Default preserved 892 885 assert result["sources"]["agents"] is True # Overridden 893 886 894 887
+15 -11
think/utils.py
··· 1047 1047 return agent_dir, agent_name 1048 1048 1049 1049 1050 - # Default instruction configuration 1050 + # Default instruction configuration - all false, agents must explicitly opt-in 1051 1051 _DEFAULT_INSTRUCTIONS = { 1052 - "system": "journal", 1053 - "facets": True, 1052 + "system": None, 1053 + "facets": False, 1054 1054 "sources": { 1055 - "audio": True, 1056 - "screen": True, 1055 + "audio": False, 1056 + "screen": False, 1057 1057 "agents": False, 1058 1058 }, 1059 1059 } ··· 1149 1149 1150 1150 result: dict = {} 1151 1151 1152 - # Load system instruction 1153 - system_name = cfg.get("system", "journal") 1154 - system_prompt = load_prompt(system_name) 1155 - result["system_instruction"] = system_prompt.text 1156 - result["system_prompt_name"] = system_name 1152 + # Load system instruction (None means no system prompt) 1153 + system_name = cfg.get("system") 1154 + if system_name: 1155 + system_prompt = load_prompt(system_name) 1156 + result["system_instruction"] = system_prompt.text 1157 + result["system_prompt_name"] = system_name 1158 + else: 1159 + result["system_instruction"] = "" 1160 + result["system_prompt_name"] = "" 1157 1161 1158 1162 # Load user instruction if specified 1159 1163 if user_prompt: ··· 1166 1170 # Build extra_context based on facets setting 1167 1171 # Values: false (skip), true (names only), "full" (with descriptions) 1168 1172 extra_parts = [] 1169 - facets_setting = cfg.get("facets", True) 1173 + facets_setting = cfg.get("facets", False) 1170 1174 facets_full = facets_setting == "full" 1171 1175 1172 1176 if facets_setting: