personal memory agent
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