Skip to content

Commit f41aaca

Browse files
committed
Remove 'message' memory type from tool creation/editing schemas
Restrict 'message' memory type to server-side use only. LLM tools can no longer create or edit memories with type 'message' through add_memory_to_working_memory, create_long_term_memory, or edit_long_term_memory. Search tools still allow filtering by 'message' type to find conversation history. Add comprehensive unit tests for new tool schemas (create_long_term_memory, edit_long_term_memory, delete_long_term_memories) and validate that message type exclusion works correctly across all creation/editing tools in both OpenAI and Anthropic formats.
1 parent 2cca4af commit f41aaca

File tree

3 files changed

+408
-6
lines changed

3 files changed

+408
-6
lines changed

agent-memory-client/agent_memory_client/client.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1469,8 +1469,8 @@ def get_add_memory_tool_schema(cls) -> dict[str, Any]:
14691469
},
14701470
"memory_type": {
14711471
"type": "string",
1472-
"enum": ["episodic", "semantic", "message"],
1473-
"description": "Type of memory: 'episodic' (events/experiences), 'semantic' (facts/preferences), 'message' (conversation snippets)",
1472+
"enum": ["episodic", "semantic"],
1473+
"description": "Type of memory: 'episodic' (events/experiences), 'semantic' (facts/preferences)",
14741474
},
14751475
"topics": {
14761476
"type": "array",
@@ -1587,8 +1587,8 @@ def edit_long_term_memory_tool_schema(cls) -> dict[str, Any]:
15871587
},
15881588
"memory_type": {
15891589
"type": "string",
1590-
"enum": ["episodic", "semantic", "message"],
1591-
"description": "Updated memory type: 'episodic' (events/experiences), 'semantic' (facts/preferences), 'message' (conversation snippets)",
1590+
"enum": ["episodic", "semantic"],
1591+
"description": "Updated memory type: 'episodic' (events/experiences), 'semantic' (facts/preferences)",
15921592
},
15931593
"namespace": {
15941594
"type": "string",
@@ -1645,8 +1645,8 @@ def create_long_term_memory_tool_schema(cls) -> dict[str, Any]:
16451645
},
16461646
"memory_type": {
16471647
"type": "string",
1648-
"enum": ["episodic", "semantic", "message"],
1649-
"description": "Type of memory: 'episodic' (events/experiences), 'semantic' (facts/preferences), 'message' (conversation snippets)",
1648+
"enum": ["episodic", "semantic"],
1649+
"description": "Type of memory: 'episodic' (events/experiences), 'semantic' (facts/preferences)",
16501650
},
16511651
"topics": {
16521652
"type": "array",
Lines changed: 312 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,312 @@
1+
"""
2+
Test file for memory tool schemas.
3+
4+
Tests that tool schemas are correctly structured and that the 'message' memory type
5+
is not exposed to LLM tools (it should only be used server-side).
6+
"""
7+
8+
from agent_memory_client import MemoryAPIClient
9+
10+
11+
class TestToolSchemaStructure:
12+
"""Tests for tool schema structure and completeness."""
13+
14+
def test_get_memory_search_tool_schema(self):
15+
"""Test memory search tool schema structure."""
16+
schema = MemoryAPIClient.get_memory_search_tool_schema()
17+
18+
assert schema["type"] == "function"
19+
assert schema["function"]["name"] == "search_memory"
20+
assert "description" in schema["function"]
21+
assert "parameters" in schema["function"]
22+
assert schema["function"]["parameters"]["type"] == "object"
23+
assert "query" in schema["function"]["parameters"]["properties"]
24+
assert "query" in schema["function"]["parameters"]["required"]
25+
26+
def test_get_add_memory_tool_schema(self):
27+
"""Test add_memory_to_working_memory tool schema structure."""
28+
schema = MemoryAPIClient.get_add_memory_tool_schema()
29+
30+
assert schema["type"] == "function"
31+
assert schema["function"]["name"] == "add_memory_to_working_memory"
32+
assert "description" in schema["function"]
33+
assert "parameters" in schema["function"]
34+
35+
params = schema["function"]["parameters"]
36+
assert params["type"] == "object"
37+
assert "text" in params["properties"]
38+
assert "memory_type" in params["properties"]
39+
assert "text" in params["required"]
40+
assert "memory_type" in params["required"]
41+
42+
def test_create_long_term_memory_tool_schema(self):
43+
"""Test create_long_term_memory tool schema structure."""
44+
schema = MemoryAPIClient.create_long_term_memory_tool_schema()
45+
46+
assert schema["type"] == "function"
47+
assert schema["function"]["name"] == "create_long_term_memory"
48+
assert "description" in schema["function"]
49+
assert "parameters" in schema["function"]
50+
51+
params = schema["function"]["parameters"]
52+
assert params["type"] == "object"
53+
assert "memories" in params["properties"]
54+
assert "memories" in params["required"]
55+
56+
# Check nested structure
57+
memory_items = params["properties"]["memories"]["items"]
58+
assert "text" in memory_items["properties"]
59+
assert "memory_type" in memory_items["properties"]
60+
assert "text" in memory_items["required"]
61+
assert "memory_type" in memory_items["required"]
62+
63+
def test_edit_long_term_memory_tool_schema(self):
64+
"""Test edit_long_term_memory tool schema structure."""
65+
schema = MemoryAPIClient.edit_long_term_memory_tool_schema()
66+
67+
assert schema["type"] == "function"
68+
assert schema["function"]["name"] == "edit_long_term_memory"
69+
assert "description" in schema["function"]
70+
assert "parameters" in schema["function"]
71+
72+
params = schema["function"]["parameters"]
73+
assert params["type"] == "object"
74+
assert "memory_id" in params["properties"]
75+
assert "memory_id" in params["required"]
76+
assert "text" in params["properties"]
77+
assert "memory_type" in params["properties"]
78+
79+
def test_delete_long_term_memories_tool_schema(self):
80+
"""Test delete_long_term_memories tool schema structure."""
81+
schema = MemoryAPIClient.delete_long_term_memories_tool_schema()
82+
83+
assert schema["type"] == "function"
84+
assert schema["function"]["name"] == "delete_long_term_memories"
85+
assert "description" in schema["function"]
86+
assert "parameters" in schema["function"]
87+
88+
params = schema["function"]["parameters"]
89+
assert params["type"] == "object"
90+
assert "memory_ids" in params["properties"]
91+
assert "memory_ids" in params["required"]
92+
93+
def test_get_all_memory_tool_schemas(self):
94+
"""Test getting all memory tool schemas."""
95+
schemas = MemoryAPIClient.get_all_memory_tool_schemas()
96+
97+
# Should have multiple tools
98+
assert len(schemas) > 0
99+
100+
# Check that all expected tools are present
101+
function_names = {schema["function"]["name"] for schema in schemas}
102+
expected_tools = {
103+
"search_memory",
104+
"get_or_create_working_memory",
105+
"add_memory_to_working_memory",
106+
"update_working_memory_data",
107+
"get_long_term_memory",
108+
"create_long_term_memory",
109+
"edit_long_term_memory",
110+
"delete_long_term_memories",
111+
"get_current_datetime",
112+
}
113+
assert expected_tools.issubset(function_names)
114+
115+
116+
class TestMemoryTypeEnumExclusion:
117+
"""Tests that 'message' memory type is NOT exposed in creation/editing tool schemas.
118+
119+
Note: search_memory CAN include 'message' in its filter enum since it's for
120+
searching/reading existing memories, not creating new ones. The restriction
121+
only applies to tools that create or modify memories.
122+
"""
123+
124+
def test_add_memory_excludes_message_type(self):
125+
"""Test that add_memory_to_working_memory excludes 'message' type."""
126+
schema = MemoryAPIClient.get_add_memory_tool_schema()
127+
128+
params = schema["function"]["parameters"]
129+
memory_type_prop = params["properties"]["memory_type"]
130+
131+
# Should only have episodic and semantic
132+
assert memory_type_prop["enum"] == ["episodic", "semantic"]
133+
assert "message" not in memory_type_prop["enum"]
134+
135+
def test_create_long_term_memory_excludes_message_type(self):
136+
"""Test that create_long_term_memory excludes 'message' type."""
137+
schema = MemoryAPIClient.create_long_term_memory_tool_schema()
138+
139+
params = schema["function"]["parameters"]
140+
memory_items = params["properties"]["memories"]["items"]
141+
memory_type_prop = memory_items["properties"]["memory_type"]
142+
143+
# Should only have episodic and semantic
144+
assert memory_type_prop["enum"] == ["episodic", "semantic"]
145+
assert "message" not in memory_type_prop["enum"]
146+
147+
def test_edit_long_term_memory_excludes_message_type(self):
148+
"""Test that edit_long_term_memory excludes 'message' type."""
149+
schema = MemoryAPIClient.edit_long_term_memory_tool_schema()
150+
151+
params = schema["function"]["parameters"]
152+
memory_type_prop = params["properties"]["memory_type"]
153+
154+
# Should only have episodic and semantic
155+
assert memory_type_prop["enum"] == ["episodic", "semantic"]
156+
assert "message" not in memory_type_prop["enum"]
157+
158+
def test_search_memory_allows_message_type_filter(self):
159+
"""Test that search_memory DOES allow 'message' type for filtering.
160+
161+
This is intentional - search tools should be able to filter by message type
162+
to find conversation history, but creation/editing tools should not be able
163+
to create or modify message-type memories.
164+
"""
165+
schema = MemoryAPIClient.get_memory_search_tool_schema()
166+
167+
params = schema["function"]["parameters"]
168+
memory_type_prop = params["properties"]["memory_type"]
169+
170+
# Search should include all types including message
171+
assert "episodic" in memory_type_prop["enum"]
172+
assert "semantic" in memory_type_prop["enum"]
173+
assert "message" in memory_type_prop["enum"]
174+
175+
def test_creation_and_editing_tools_exclude_message_type(self):
176+
"""Test that creation and editing tools (not search) exclude 'message'."""
177+
all_schemas = MemoryAPIClient.get_all_memory_tool_schemas()
178+
179+
# Tools that should NOT expose message type (creation/editing tools)
180+
restricted_tools = {
181+
"add_memory_to_working_memory",
182+
"create_long_term_memory",
183+
"edit_long_term_memory",
184+
}
185+
186+
# Tools that CAN expose message type (search/read tools)
187+
allowed_tools = {
188+
"search_memory",
189+
"get_long_term_memory",
190+
}
191+
192+
for schema in all_schemas:
193+
function_name = schema["function"]["name"]
194+
params = schema["function"]["parameters"]
195+
196+
# Check direct memory_type property
197+
if "memory_type" in params["properties"]:
198+
memory_type_prop = params["properties"]["memory_type"]
199+
if "enum" in memory_type_prop:
200+
if function_name in restricted_tools:
201+
assert (
202+
"message" not in memory_type_prop["enum"]
203+
), f"Creation/editing tool '{function_name}' should not expose 'message' memory type"
204+
elif function_name in allowed_tools:
205+
# These tools are allowed to have message in enum for filtering
206+
pass
207+
208+
# Check nested properties (like in create_long_term_memory)
209+
if "memories" in params["properties"]:
210+
items = params["properties"]["memories"].get("items", {})
211+
if (
212+
"properties" in items
213+
and "memory_type" in items["properties"]
214+
and "enum" in items["properties"]["memory_type"]
215+
and function_name in restricted_tools
216+
):
217+
memory_type_prop = items["properties"]["memory_type"]
218+
assert (
219+
"message" not in memory_type_prop["enum"]
220+
), f"Creation/editing tool '{function_name}' should not expose 'message' memory type in nested properties"
221+
222+
223+
class TestAnthropicSchemas:
224+
"""Tests for Anthropic-formatted tool schemas."""
225+
226+
def test_get_memory_search_tool_schema_anthropic(self):
227+
"""Test memory search tool schema in Anthropic format."""
228+
schema = MemoryAPIClient.get_memory_search_tool_schema_anthropic()
229+
230+
assert schema["name"] == "search_memory"
231+
assert "description" in schema
232+
assert "input_schema" in schema
233+
assert schema["input_schema"]["type"] == "object"
234+
assert "query" in schema["input_schema"]["properties"]
235+
assert "query" in schema["input_schema"]["required"]
236+
237+
def test_create_long_term_memory_tool_schema_anthropic(self):
238+
"""Test create_long_term_memory tool schema in Anthropic format."""
239+
schema = MemoryAPIClient.create_long_term_memory_tool_schema_anthropic()
240+
241+
assert schema["name"] == "create_long_term_memory"
242+
assert "description" in schema
243+
assert "input_schema" in schema
244+
assert schema["input_schema"]["type"] == "object"
245+
assert "memories" in schema["input_schema"]["properties"]
246+
247+
def test_edit_long_term_memory_tool_schema_anthropic(self):
248+
"""Test edit_long_term_memory tool schema in Anthropic format."""
249+
schema = MemoryAPIClient.edit_long_term_memory_tool_schema_anthropic()
250+
251+
assert schema["name"] == "edit_long_term_memory"
252+
assert "description" in schema
253+
assert "input_schema" in schema
254+
assert schema["input_schema"]["type"] == "object"
255+
assert "memory_id" in schema["input_schema"]["properties"]
256+
257+
def test_delete_long_term_memories_tool_schema_anthropic(self):
258+
"""Test delete_long_term_memories tool schema in Anthropic format."""
259+
schema = MemoryAPIClient.delete_long_term_memories_tool_schema_anthropic()
260+
261+
assert schema["name"] == "delete_long_term_memories"
262+
assert "description" in schema
263+
assert "input_schema" in schema
264+
assert schema["input_schema"]["type"] == "object"
265+
assert "memory_ids" in schema["input_schema"]["properties"]
266+
267+
def test_anthropic_schemas_exclude_message_type_for_creation(self):
268+
"""Test that Anthropic creation/editing schemas exclude 'message' type."""
269+
all_schemas = MemoryAPIClient.get_all_memory_tool_schemas_anthropic()
270+
271+
# Tools that should NOT expose message type (creation/editing tools)
272+
restricted_tools = {
273+
"add_memory_to_working_memory",
274+
"create_long_term_memory",
275+
"edit_long_term_memory",
276+
}
277+
278+
# Tools that CAN expose message type (search/read tools)
279+
allowed_tools = {
280+
"search_memory",
281+
"get_long_term_memory",
282+
}
283+
284+
for schema in all_schemas:
285+
function_name = schema["name"]
286+
params = schema["input_schema"]
287+
288+
# Check direct memory_type property
289+
if "memory_type" in params["properties"]:
290+
memory_type_prop = params["properties"]["memory_type"]
291+
if "enum" in memory_type_prop:
292+
if function_name in restricted_tools:
293+
assert (
294+
"message" not in memory_type_prop["enum"]
295+
), f"Anthropic creation/editing tool '{function_name}' should not expose 'message' memory type"
296+
elif function_name in allowed_tools:
297+
# These tools are allowed to have message in enum for filtering
298+
pass
299+
300+
# Check nested properties
301+
if "memories" in params["properties"]:
302+
items = params["properties"]["memories"].get("items", {})
303+
if (
304+
"properties" in items
305+
and "memory_type" in items["properties"]
306+
and "enum" in items["properties"]["memory_type"]
307+
and function_name in restricted_tools
308+
):
309+
memory_type_prop = items["properties"]["memory_type"]
310+
assert (
311+
"message" not in memory_type_prop["enum"]
312+
), f"Anthropic creation/editing tool '{function_name}' should not expose 'message' memory type in nested properties"

0 commit comments

Comments
 (0)