personal memory agent
1# SPDX-License-Identifier: AGPL-3.0-only
2# Copyright (c) 2026 sol pbc
3
4from pathlib import Path
5
6import pytest
7
8from solstone.think import cogitate_contract
9from solstone.think.cogitate_contract import (
10 COGITATE_ACCESS_TIERS,
11 COGITATE_DIAGNOSTIC_PREAMBLE,
12 COGITATE_READ_TOOL_NAMES,
13 COGITATE_RUNTIME_PREAMBLE,
14 FUTURE_ACCESS_TIERS,
15 TALENT_ACCESS_TIERS,
16 TALENT_FINALIZATION_MODES,
17 expects_emit_final,
18)
19from solstone.think.providers.cli import assemble_prompt
20
21
22def test_cogitate_preamble_injected_with_and_without_system_instruction():
23 _, system = assemble_prompt({"system_instruction": "X"}, sol_tool_name="sol")
24 assert system is not None
25 assert system.startswith(COGITATE_RUNTIME_PREAMBLE)
26
27 _, system = assemble_prompt({}, sol_tool_name="sol")
28 assert system is not None
29 assert system.startswith(COGITATE_RUNTIME_PREAMBLE)
30
31
32def test_cogitate_preamble_ordering_with_scope_hint():
33 _, system = assemble_prompt(
34 {"system_instruction": "X", "read_scope": ["c"]},
35 sol_tool_name="sol",
36 )
37
38 assert system is not None
39 assert system.startswith(COGITATE_RUNTIME_PREAMBLE)
40 assert (
41 system.index(COGITATE_RUNTIME_PREAMBLE.rstrip("\n"))
42 < system.index("X")
43 < system.index("through the `sol` tool")
44 < system.index("Limit filesystem reads to today's segment dir")
45 )
46
47
48def test_non_cogitate_prompt_omits_preamble():
49 _, system = assemble_prompt({"system_instruction": "X"}, sol_tool_name=None)
50
51 assert system == "X"
52 assert COGITATE_RUNTIME_PREAMBLE not in system
53
54
55def test_prompt_body_unchanged_under_cogitate_injection():
56 body, _ = assemble_prompt(
57 {
58 "transcript": "t",
59 "extra_context": "e",
60 "user_instruction": "u",
61 "prompt": "p",
62 "system_instruction": "X",
63 },
64 sol_tool_name="sol",
65 )
66
67 assert body == "t\n\ne\n\nu\n\np"
68
69
70def test_cogitate_vocabulary_lock():
71 assert COGITATE_ACCESS_TIERS == (
72 "normal",
73 "system-read",
74 "outbound",
75 "synthesis",
76 "diagnostic",
77 )
78 assert COGITATE_READ_TOOL_NAMES == (
79 "read_file",
80 "list_directory",
81 "glob",
82 "grep_search",
83 )
84 assert FUTURE_ACCESS_TIERS == ("code-agent",)
85 assert TALENT_ACCESS_TIERS == (
86 "normal",
87 "system-read",
88 "outbound",
89 "synthesis",
90 )
91 assert TALENT_FINALIZATION_MODES == ("emit_final", "FinishTool", "quiet")
92 assert "repair" not in COGITATE_ACCESS_TIERS
93 assert "repair" not in FUTURE_ACCESS_TIERS
94
95
96def test_access_tier_capability_mapping_matches_vocabulary():
97 assert set(cogitate_contract._ACCESS_TIER_CAPABILITIES) == set(
98 COGITATE_ACCESS_TIERS
99 )
100
101
102@pytest.mark.parametrize(
103 ("config", "expected"),
104 [
105 ({"output_path": "/tmp/out.md"}, True),
106 ({"schedule": "daily"}, True),
107 ({"schedule": "weekly"}, True),
108 ({"schedule": "activity"}, True),
109 ({"diagnostic": True}, True),
110 ({"diagnostic": False}, False),
111 ({"schedule": "segment"}, False),
112 ({"schedule": "none"}, False),
113 ({}, False),
114 ],
115)
116def test_expects_emit_final(config, expected):
117 assert expects_emit_final(config) is expected
118
119
120@pytest.mark.parametrize(
121 ("tier", "expected"),
122 [
123 ("normal", (True, True, False)),
124 ("system-read", (True, True, False)),
125 ("outbound", (True, False, True)),
126 ("synthesis", (True, False, False)),
127 ("diagnostic", (False, False, False)),
128 ],
129)
130def test_capabilities_for_access_tier_real_tiers(tier, expected):
131 caps = cogitate_contract.capabilities_for_access_tier(tier)
132
133 assert (caps.sol, caps.reads, caps.submit) == expected
134
135
136def test_outbound_tier_has_no_read_tools():
137 assert cogitate_contract.capabilities_for_access_tier("outbound").reads is False
138
139
140@pytest.mark.parametrize("tier", ["repair", "code-agent", "bogus"])
141def test_capabilities_for_access_tier_unknown_names_tier(tier):
142 with pytest.raises(ValueError, match=tier):
143 cogitate_contract.capabilities_for_access_tier(tier)
144
145
146def test_cogitate_runtime_preamble_content_guard():
147 assert "sol call ..." in COGITATE_RUNTIME_PREAMBLE
148 assert "single parsed command-line invocation" in COGITATE_RUNTIME_PREAMBLE
149 assert "journal root" in COGITATE_RUNTIME_PREAMBLE
150 assert "node_modules" in COGITATE_RUNTIME_PREAMBLE
151 assert "emit_final" in COGITATE_RUNTIME_PREAMBLE
152 assert "finish tool" in COGITATE_RUNTIME_PREAMBLE
153
154
155def test_diagnostic_preamble_omits_journal_tooling():
156 _, system = assemble_prompt(
157 {"system_instruction": "X", "read_scope": ["chronicle/<day>"]},
158 sol_tool_name="sol",
159 diagnostic=True,
160 )
161
162 assert system is not None
163 assert system.startswith(COGITATE_DIAGNOSTIC_PREAMBLE)
164 assert "emit_final" in system
165 assert "X" in system
166 assert "read_file" not in system
167 assert "grep_search" not in system
168 assert "through the `sol` tool" not in system
169 assert "Limit filesystem reads" not in system
170 assert "through a `sol` domain command" in COGITATE_RUNTIME_PREAMBLE
171 assert "no MCP tools" in COGITATE_RUNTIME_PREAMBLE
172 assert "no bare `journal ...` commands" in COGITATE_RUNTIME_PREAMBLE
173 assert "no shell composition" in COGITATE_RUNTIME_PREAMBLE
174 assert "read_file" in COGITATE_RUNTIME_PREAMBLE
175 assert "list_directory" in COGITATE_RUNTIME_PREAMBLE
176 assert "glob" in COGITATE_RUNTIME_PREAMBLE
177 assert "grep_search" in COGITATE_RUNTIME_PREAMBLE
178 assert "when this run provides a read tool" not in COGITATE_RUNTIME_PREAMBLE
179
180
181def test_cogitate_doc_preamble_block_matches_source_constant():
182 docs_path = Path(__file__).resolve().parents[1] / "docs" / "COGITATE.md"
183 text = docs_path.read_text(encoding="utf-8")
184 heading = "## The in-context preamble (named source constant)"
185 _, heading_found, tail = text.partition(heading)
186 assert heading_found
187 _, verbatim_found, tail = tail.partition("verbatim text:")
188 assert verbatim_found
189 _, fence_found, tail = tail.partition("```\n")
190 assert fence_found
191 block, closing_fence, _tail = tail.partition("\n```")
192 assert closing_fence
193
194 assert block == COGITATE_RUNTIME_PREAMBLE.rstrip("\n")