personal memory agent
at main 195 lines 7.0 kB view raw
1# SPDX-License-Identifier: AGPL-3.0-only 2# Copyright (c) 2026 sol pbc 3 4from pathlib import Path 5 6from talent.chat_context import pre_process 7 8 9TEMPLATE_VAR_KEYS = { 10 "recent_conversation", 11 "active_routines", 12 "routine_suggestion", 13 "sol_awareness", 14} 15 16 17def _assert_template_vars_result(result): 18 assert isinstance(result, dict) 19 assert "template_vars" in result 20 assert "user_instruction" not in result 21 assert set(result["template_vars"]) == TEMPLATE_VAR_KEYS 22 return result["template_vars"] 23 24 25def _read_chat_md() -> str: 26 chat_md = Path(__file__).resolve().parents[1] / "talent" / "chat.md" 27 return chat_md.read_text(encoding="utf-8") 28 29 30def test_chat_context_appends_conversation_memory(monkeypatch, tmp_path): 31 """Conversation memory is appended when recent exchanges exist.""" 32 from think.conversation import record_exchange 33 from think.utils import now_ms 34 35 monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 36 37 record_exchange( 38 ts=now_ms(), 39 facet="work", 40 user_message="hello", 41 agent_response="hi there!", 42 talent="unified", 43 ) 44 45 result = pre_process({"user_instruction": "Base instruction.", "facet": "work"}) 46 47 template_vars = _assert_template_vars_result(result) 48 assert "## Recent Conversation" in template_vars["recent_conversation"] 49 assert "hello" in template_vars["recent_conversation"] 50 assert "hi there!" in template_vars["recent_conversation"] 51 52 53def test_chat_context_no_memory(monkeypatch, tmp_path): 54 """Recent conversation is empty when no conversation history exists.""" 55 monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 56 57 result = pre_process({"user_instruction": "Base instruction."}) 58 59 template_vars = _assert_template_vars_result(result) 60 assert template_vars["recent_conversation"] == "" 61 62 63def test_chat_md_contains_location_context(): 64 """Location context lives in the static chat prompt.""" 65 chat_md = _read_chat_md() 66 assert "## Location Context" in chat_md 67 68 69def test_chat_md_contains_system_health(): 70 """System health guidance lives in the static chat prompt.""" 71 chat_md = _read_chat_md() 72 assert "## System Health" in chat_md 73 74 75def test_chat_md_contains_behavioral_defaults(): 76 """Behavioral defaults live in the static chat prompt.""" 77 chat_md = _read_chat_md() 78 assert "## Behavioral Defaults" in chat_md 79 80 81def test_chat_md_contains_static_import_guidance(): 82 """Import guidance lives in the static chat prompt.""" 83 chat_md = _read_chat_md() 84 assert "## Import Awareness" in chat_md 85 86 87def test_chat_md_contains_static_naming_guidance(): 88 """Naming guidance lives in the static chat prompt.""" 89 chat_md = _read_chat_md() 90 assert "## Naming Awareness" in chat_md 91 92 93def test_chat_context_awareness_error_graceful(monkeypatch): 94 """Awareness failures still return the full template var shape.""" 95 monkeypatch.setattr("think.conversation.build_memory_context", lambda **kw: "") 96 monkeypatch.setattr("think.routines.get_routine_state", lambda: []) 97 monkeypatch.setattr("think.routines.get_config", lambda: {"_meta": {"suggestions": {}}}) 98 monkeypatch.setattr( 99 "think.utils.get_config", 100 lambda: {"agent": {"name": "aria", "name_status": "default"}}, 101 ) 102 monkeypatch.setattr("think.utils.get_journal", lambda: "/nonexistent") 103 104 result = pre_process({"user_instruction": "Base instruction."}) 105 106 template_vars = _assert_template_vars_result(result) 107 assert all(template_vars[key] == "" for key in TEMPLATE_VAR_KEYS) 108 109 110def test_chat_context_sol_awareness_injected(monkeypatch, tmp_path): 111 """Sol awareness is injected when awareness.md has content.""" 112 monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 113 monkeypatch.setattr("think.conversation.build_memory_context", lambda **kw: "") 114 115 sol_dir = tmp_path / "sol" 116 sol_dir.mkdir() 117 (sol_dir / "awareness.md").write_text( 118 "as of: 2026-04-05T10:00:00\n\n## capture\n- status: active\n" 119 ) 120 121 result = pre_process({"user_instruction": "Base instruction."}) 122 123 template_vars = _assert_template_vars_result(result) 124 assert "## Awareness" in template_vars["sol_awareness"] 125 assert "capture" in template_vars["sol_awareness"] 126 127 128def test_chat_context_sol_awareness_cold_start(monkeypatch, tmp_path): 129 """Sol awareness is empty when awareness.md has placeholder content.""" 130 monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 131 monkeypatch.setattr("think.conversation.build_memory_context", lambda **kw: "") 132 133 sol_dir = tmp_path / "sol" 134 sol_dir.mkdir() 135 (sol_dir / "awareness.md").write_text("not yet updated\n") 136 137 result = pre_process({"user_instruction": "Base instruction."}) 138 139 template_vars = _assert_template_vars_result(result) 140 assert template_vars["sol_awareness"] == "" 141 142 143def test_chat_context_routines_injected(monkeypatch): 144 """Active routines section is appended when routines exist.""" 145 monkeypatch.setattr("think.conversation.build_memory_context", lambda **kw: "") 146 monkeypatch.setattr( 147 "think.routines.get_routine_state", 148 lambda: [ 149 { 150 "name": "Morning Briefing", 151 "cadence": "0 9 * * *", 152 "last_run": None, 153 "enabled": True, 154 "paused_until": None, 155 "output_summary": None, 156 } 157 ], 158 ) 159 160 result = pre_process({"user_instruction": "Base instruction."}) 161 162 template_vars = _assert_template_vars_result(result) 163 assert "## Active Routines" in template_vars["active_routines"] 164 assert "Morning Briefing" in template_vars["active_routines"] 165 166 167def test_chat_context_routines_omitted_when_empty(monkeypatch): 168 """Active routines section is omitted when no routines configured.""" 169 monkeypatch.setattr("think.conversation.build_memory_context", lambda **kw: "") 170 monkeypatch.setattr("think.routines.get_routine_state", lambda: []) 171 172 result = pre_process({"user_instruction": "Base instruction."}) 173 174 template_vars = _assert_template_vars_result(result) 175 assert template_vars["active_routines"] == "" 176 177 178def test_chat_context_routines_error_graceful(monkeypatch): 179 """Routine state failures still return the full template var shape.""" 180 monkeypatch.setattr("think.conversation.build_memory_context", lambda **kw: "") 181 monkeypatch.setattr( 182 "think.routines.get_routine_state", 183 lambda: (_ for _ in ()).throw(RuntimeError("boom")), 184 ) 185 monkeypatch.setattr("think.routines.get_config", lambda: {"_meta": {"suggestions": {}}}) 186 monkeypatch.setattr( 187 "think.utils.get_config", 188 lambda: {"agent": {"name": "aria", "name_status": "default"}}, 189 ) 190 191 result = pre_process({"user_instruction": "Base instruction."}) 192 193 template_vars = _assert_template_vars_result(result) 194 assert template_vars["active_routines"] == "" 195 assert set(template_vars) == TEMPLATE_VAR_KEYS