massgen 0.1.4__py3-none-any.whl → 0.1.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of massgen might be problematic. Click here for more details.

Files changed (84) hide show
  1. massgen/__init__.py +1 -1
  2. massgen/backend/base_with_custom_tool_and_mcp.py +453 -23
  3. massgen/backend/capabilities.py +39 -0
  4. massgen/backend/chat_completions.py +111 -197
  5. massgen/backend/claude.py +210 -181
  6. massgen/backend/gemini.py +1015 -1559
  7. massgen/backend/grok.py +3 -2
  8. massgen/backend/response.py +160 -220
  9. massgen/chat_agent.py +340 -20
  10. massgen/cli.py +399 -25
  11. massgen/config_builder.py +20 -54
  12. massgen/config_validator.py +931 -0
  13. massgen/configs/README.md +95 -10
  14. massgen/configs/memory/gpt5mini_gemini_baseline_research_to_implementation.yaml +94 -0
  15. massgen/configs/memory/gpt5mini_gemini_context_window_management.yaml +187 -0
  16. massgen/configs/memory/gpt5mini_gemini_research_to_implementation.yaml +127 -0
  17. massgen/configs/memory/gpt5mini_high_reasoning_gemini.yaml +107 -0
  18. massgen/configs/memory/single_agent_compression_test.yaml +64 -0
  19. massgen/configs/tools/custom_tools/claude_code_custom_tool_with_mcp_example.yaml +1 -0
  20. massgen/configs/tools/custom_tools/claude_custom_tool_example_no_path.yaml +1 -1
  21. massgen/configs/tools/custom_tools/claude_custom_tool_with_mcp_example.yaml +1 -0
  22. massgen/configs/tools/custom_tools/computer_use_browser_example.yaml +1 -1
  23. massgen/configs/tools/custom_tools/computer_use_docker_example.yaml +1 -1
  24. massgen/configs/tools/custom_tools/gemini_custom_tool_with_mcp_example.yaml +1 -0
  25. massgen/configs/tools/custom_tools/gpt5_nano_custom_tool_with_mcp_example.yaml +1 -0
  26. massgen/configs/tools/custom_tools/gpt_oss_custom_tool_with_mcp_example.yaml +1 -0
  27. massgen/configs/tools/custom_tools/grok3_mini_custom_tool_with_mcp_example.yaml +1 -0
  28. massgen/configs/tools/custom_tools/interop/ag2_and_langgraph_lesson_planner.yaml +65 -0
  29. massgen/configs/tools/custom_tools/interop/ag2_and_openai_assistant_lesson_planner.yaml +65 -0
  30. massgen/configs/tools/custom_tools/interop/ag2_lesson_planner_example.yaml +48 -0
  31. massgen/configs/tools/custom_tools/interop/agentscope_lesson_planner_example.yaml +48 -0
  32. massgen/configs/tools/custom_tools/interop/langgraph_lesson_planner_example.yaml +49 -0
  33. massgen/configs/tools/custom_tools/interop/openai_assistant_lesson_planner_example.yaml +50 -0
  34. massgen/configs/tools/custom_tools/interop/smolagent_lesson_planner_example.yaml +49 -0
  35. massgen/configs/tools/custom_tools/qwen_api_custom_tool_with_mcp_example.yaml +1 -0
  36. massgen/configs/tools/custom_tools/two_models_with_tools_example.yaml +44 -0
  37. massgen/formatter/_gemini_formatter.py +61 -15
  38. massgen/memory/README.md +277 -0
  39. massgen/memory/__init__.py +26 -0
  40. massgen/memory/_base.py +193 -0
  41. massgen/memory/_compression.py +237 -0
  42. massgen/memory/_context_monitor.py +211 -0
  43. massgen/memory/_conversation.py +255 -0
  44. massgen/memory/_fact_extraction_prompts.py +333 -0
  45. massgen/memory/_mem0_adapters.py +257 -0
  46. massgen/memory/_persistent.py +687 -0
  47. massgen/memory/docker-compose.qdrant.yml +36 -0
  48. massgen/memory/docs/DESIGN.md +388 -0
  49. massgen/memory/docs/QUICKSTART.md +409 -0
  50. massgen/memory/docs/SUMMARY.md +319 -0
  51. massgen/memory/docs/agent_use_memory.md +408 -0
  52. massgen/memory/docs/orchestrator_use_memory.md +586 -0
  53. massgen/memory/examples.py +237 -0
  54. massgen/orchestrator.py +207 -7
  55. massgen/tests/memory/test_agent_compression.py +174 -0
  56. massgen/tests/memory/test_context_window_management.py +286 -0
  57. massgen/tests/memory/test_force_compression.py +154 -0
  58. massgen/tests/memory/test_simple_compression.py +147 -0
  59. massgen/tests/test_ag2_lesson_planner.py +223 -0
  60. massgen/tests/test_agent_memory.py +534 -0
  61. massgen/tests/test_config_validator.py +1156 -0
  62. massgen/tests/test_conversation_memory.py +382 -0
  63. massgen/tests/test_langgraph_lesson_planner.py +223 -0
  64. massgen/tests/test_orchestrator_memory.py +620 -0
  65. massgen/tests/test_persistent_memory.py +435 -0
  66. massgen/token_manager/token_manager.py +6 -0
  67. massgen/tool/__init__.py +2 -9
  68. massgen/tool/_decorators.py +52 -0
  69. massgen/tool/_extraframework_agents/ag2_lesson_planner_tool.py +251 -0
  70. massgen/tool/_extraframework_agents/agentscope_lesson_planner_tool.py +303 -0
  71. massgen/tool/_extraframework_agents/langgraph_lesson_planner_tool.py +275 -0
  72. massgen/tool/_extraframework_agents/openai_assistant_lesson_planner_tool.py +247 -0
  73. massgen/tool/_extraframework_agents/smolagent_lesson_planner_tool.py +180 -0
  74. massgen/tool/_manager.py +102 -16
  75. massgen/tool/_registered_tool.py +3 -0
  76. massgen/tool/_result.py +3 -0
  77. {massgen-0.1.4.dist-info → massgen-0.1.6.dist-info}/METADATA +138 -77
  78. {massgen-0.1.4.dist-info → massgen-0.1.6.dist-info}/RECORD +82 -37
  79. massgen/backend/gemini_mcp_manager.py +0 -545
  80. massgen/backend/gemini_trackers.py +0 -344
  81. {massgen-0.1.4.dist-info → massgen-0.1.6.dist-info}/WHEEL +0 -0
  82. {massgen-0.1.4.dist-info → massgen-0.1.6.dist-info}/entry_points.txt +0 -0
  83. {massgen-0.1.4.dist-info → massgen-0.1.6.dist-info}/licenses/LICENSE +0 -0
  84. {massgen-0.1.4.dist-info → massgen-0.1.6.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,620 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Tests for Orchestrator Shared Memory.
5
+
6
+ This module tests the shared memory functionality in the Orchestrator,
7
+ including how agents can access and contribute to shared memory that
8
+ all agents can see.
9
+ """
10
+
11
+ from typing import List
12
+ from unittest.mock import AsyncMock, MagicMock
13
+
14
+ import pytest
15
+
16
+ from massgen.chat_agent import SingleAgent
17
+ from massgen.memory import ConversationMemory
18
+
19
+ # Import orchestrator and memory classes
20
+ from massgen.orchestrator import Orchestrator
21
+
22
+
23
+ # Helper functions
24
+ def create_mock_backend(agent_responses: List[str] = None):
25
+ """Create a mock backend with predefined responses."""
26
+ if agent_responses is None:
27
+ agent_responses = ["Test response"]
28
+
29
+ backend = MagicMock()
30
+ backend.is_stateful = MagicMock(return_value=False)
31
+ backend.set_stage = MagicMock()
32
+ backend.set_planning_mode = MagicMock()
33
+ backend.extract_tool_name = MagicMock(side_effect=lambda tc: tc.get("name", ""))
34
+ backend.extract_tool_arguments = MagicMock(side_effect=lambda tc: tc.get("arguments", {}))
35
+ backend.get_provider_name = MagicMock(return_value="test_provider")
36
+ backend.filesystem_manager = None
37
+
38
+ # Track which response to return
39
+ response_index = [0]
40
+
41
+ async def mock_stream():
42
+ idx = response_index[0]
43
+ response = agent_responses[idx % len(agent_responses)]
44
+ response_index[0] += 1
45
+
46
+ yield MagicMock(type="content", content=response)
47
+ yield MagicMock(
48
+ type="complete_message",
49
+ complete_message={"role": "assistant", "content": response},
50
+ )
51
+ yield MagicMock(type="done")
52
+
53
+ backend.stream_with_tools = MagicMock(return_value=mock_stream())
54
+ return backend
55
+
56
+
57
+ def create_mock_agent(agent_id: str, backend=None):
58
+ """Create a mock agent for testing."""
59
+ if backend is None:
60
+ backend = create_mock_backend()
61
+
62
+ agent = SingleAgent(
63
+ backend=backend,
64
+ agent_id=agent_id,
65
+ system_message=f"You are {agent_id}",
66
+ )
67
+ return agent
68
+
69
+
70
+ def create_mock_persistent_memory():
71
+ """Create a mock persistent memory."""
72
+ memory = MagicMock()
73
+ memory.record = AsyncMock()
74
+ memory.retrieve = AsyncMock(return_value="")
75
+ return memory
76
+
77
+
78
+ @pytest.mark.asyncio
79
+ class TestOrchestratorSharedConversationMemory:
80
+ """Tests for Orchestrator with shared ConversationMemory."""
81
+
82
+ async def test_orchestrator_with_shared_conversation_memory(self):
83
+ """Test that orchestrator initializes with shared conversation memory."""
84
+ shared_memory = ConversationMemory()
85
+ agents = {
86
+ "agent1": create_mock_agent("agent1"),
87
+ "agent2": create_mock_agent("agent2"),
88
+ }
89
+
90
+ orchestrator = Orchestrator(
91
+ agents=agents,
92
+ shared_conversation_memory=shared_memory,
93
+ )
94
+
95
+ assert orchestrator.shared_conversation_memory is shared_memory
96
+ assert orchestrator.shared_persistent_memory is None
97
+ print("✅ Orchestrator with shared conversation memory initialization works")
98
+
99
+ async def test_shared_memory_injection_to_agents(self):
100
+ """Test that shared memory content is injected into agent messages."""
101
+ shared_memory = ConversationMemory()
102
+
103
+ # Add some messages to shared memory first
104
+ await shared_memory.add(
105
+ [
106
+ {"role": "assistant", "content": "Previous insight", "agent_id": "agent1"},
107
+ {"role": "assistant", "content": "Another finding", "agent_id": "agent2"},
108
+ ],
109
+ )
110
+
111
+ agents = {
112
+ "agent1": create_mock_agent("agent1"),
113
+ }
114
+
115
+ orchestrator = Orchestrator(
116
+ agents=agents,
117
+ shared_conversation_memory=shared_memory,
118
+ )
119
+
120
+ # Test the injection method
121
+ original_messages = [
122
+ {"role": "system", "content": "You are an agent"},
123
+ {"role": "user", "content": "Solve this task"},
124
+ ]
125
+
126
+ injected_messages = await orchestrator._inject_shared_memory_context(
127
+ original_messages,
128
+ "agent1",
129
+ )
130
+
131
+ # Should have an additional system message with shared memory
132
+ assert len(injected_messages) > len(original_messages)
133
+
134
+ # Find the memory injection message
135
+ memory_messages = [msg for msg in injected_messages if "SHARED CONVERSATION MEMORY" in msg.get("content", "")]
136
+ assert len(memory_messages) == 1
137
+
138
+ memory_content = memory_messages[0]["content"]
139
+ assert "Previous insight" in memory_content
140
+ assert "Another finding" in memory_content
141
+
142
+ print("✅ Shared memory injection to agents works")
143
+
144
+ async def test_agent_contributions_recorded_to_shared_memory(self):
145
+ """Test that agent contributions are recorded to shared memory."""
146
+ shared_memory = ConversationMemory()
147
+ agents = {
148
+ "agent1": create_mock_agent("agent1"),
149
+ }
150
+
151
+ orchestrator = Orchestrator(
152
+ agents=agents,
153
+ shared_conversation_memory=shared_memory,
154
+ )
155
+
156
+ # Simulate recording to shared memory
157
+ await orchestrator._record_to_shared_memory(
158
+ agent_id="agent1",
159
+ content="I found the solution",
160
+ role="assistant",
161
+ )
162
+
163
+ # Check if the message was recorded
164
+ memory_size = await shared_memory.size()
165
+ assert memory_size == 1
166
+
167
+ messages = await shared_memory.get_messages()
168
+ assert len(messages) == 1
169
+ assert messages[0]["content"] == "I found the solution"
170
+ assert messages[0]["agent_id"] == "agent1"
171
+
172
+ print("✅ Agent contributions recorded to shared memory")
173
+
174
+ async def test_multiple_agents_share_same_memory(self):
175
+ """Test that multiple agents can see the same shared memory."""
176
+ shared_memory = ConversationMemory()
177
+
178
+ # Agent1 contributes to memory
179
+ await shared_memory.add(
180
+ {
181
+ "role": "assistant",
182
+ "content": "Agent1's discovery",
183
+ "agent_id": "agent1",
184
+ },
185
+ )
186
+
187
+ agents = {
188
+ "agent1": create_mock_agent("agent1"),
189
+ "agent2": create_mock_agent("agent2"),
190
+ }
191
+
192
+ orchestrator = Orchestrator(
193
+ agents=agents,
194
+ shared_conversation_memory=shared_memory,
195
+ )
196
+
197
+ # Both agents should see the same memory
198
+ messages1 = await orchestrator._inject_shared_memory_context(
199
+ [{"role": "user", "content": "Task"}],
200
+ "agent1",
201
+ )
202
+ messages2 = await orchestrator._inject_shared_memory_context(
203
+ [{"role": "user", "content": "Task"}],
204
+ "agent2",
205
+ )
206
+
207
+ # Both should have memory injection
208
+ assert any("Agent1's discovery" in msg.get("content", "") for msg in messages1)
209
+ assert any("Agent1's discovery" in msg.get("content", "") for msg in messages2)
210
+
211
+ print("✅ Multiple agents share the same memory")
212
+
213
+ async def test_shared_memory_accumulates_over_time(self):
214
+ """Test that shared memory accumulates contributions from multiple agents."""
215
+ shared_memory = ConversationMemory()
216
+ agents = {
217
+ "agent1": create_mock_agent("agent1"),
218
+ "agent2": create_mock_agent("agent2"),
219
+ "agent3": create_mock_agent("agent3"),
220
+ }
221
+
222
+ orchestrator = Orchestrator(
223
+ agents=agents,
224
+ shared_conversation_memory=shared_memory,
225
+ )
226
+
227
+ # Simulate multiple agents contributing
228
+ await orchestrator._record_to_shared_memory("agent1", "First finding", "assistant")
229
+ await orchestrator._record_to_shared_memory("agent2", "Second finding", "assistant")
230
+ await orchestrator._record_to_shared_memory("agent3", "Third finding", "assistant")
231
+
232
+ # Check memory size
233
+ memory_size = await shared_memory.size()
234
+ assert memory_size == 3
235
+
236
+ # All contributions should be visible
237
+ messages = await shared_memory.get_messages()
238
+ contents = [msg["content"] for msg in messages]
239
+ assert "First finding" in contents
240
+ assert "Second finding" in contents
241
+ assert "Third finding" in contents
242
+
243
+ print("✅ Shared memory accumulates over time")
244
+
245
+
246
+ @pytest.mark.asyncio
247
+ class TestOrchestratorSharedPersistentMemory:
248
+ """Tests for Orchestrator with shared PersistentMemory."""
249
+
250
+ async def test_orchestrator_with_shared_persistent_memory(self):
251
+ """Test that orchestrator initializes with shared persistent memory."""
252
+ persistent_memory = create_mock_persistent_memory()
253
+ agents = {
254
+ "agent1": create_mock_agent("agent1"),
255
+ "agent2": create_mock_agent("agent2"),
256
+ }
257
+
258
+ orchestrator = Orchestrator(
259
+ agents=agents,
260
+ shared_persistent_memory=persistent_memory,
261
+ )
262
+
263
+ assert orchestrator.shared_persistent_memory is persistent_memory
264
+ print("✅ Orchestrator with shared persistent memory initialization works")
265
+
266
+ async def test_persistent_memory_retrieval_for_agents(self):
267
+ """Test that persistent memory is retrieved and injected for agents."""
268
+ persistent_memory = create_mock_persistent_memory()
269
+ persistent_memory.retrieve = AsyncMock(return_value="Historical context: Previous session insights")
270
+
271
+ agents = {
272
+ "agent1": create_mock_agent("agent1"),
273
+ }
274
+
275
+ orchestrator = Orchestrator(
276
+ agents=agents,
277
+ shared_persistent_memory=persistent_memory,
278
+ )
279
+
280
+ # Test injection
281
+ original_messages = [
282
+ {"role": "user", "content": "New task"},
283
+ ]
284
+
285
+ injected_messages = await orchestrator._inject_shared_memory_context(
286
+ original_messages,
287
+ "agent1",
288
+ )
289
+
290
+ # Should have persistent memory context
291
+ memory_content = "\n".join([msg.get("content", "") for msg in injected_messages])
292
+ assert "SHARED PERSISTENT MEMORY" in memory_content
293
+ assert "Historical context" in memory_content
294
+
295
+ print("✅ Persistent memory retrieval for agents works")
296
+
297
+ async def test_persistent_memory_recording(self):
298
+ """Test that agent contributions are recorded to persistent memory."""
299
+ persistent_memory = create_mock_persistent_memory()
300
+ agents = {
301
+ "agent1": create_mock_agent("agent1"),
302
+ }
303
+
304
+ orchestrator = Orchestrator(
305
+ agents=agents,
306
+ shared_persistent_memory=persistent_memory,
307
+ )
308
+
309
+ # Record to memory
310
+ await orchestrator._record_to_shared_memory(
311
+ agent_id="agent1",
312
+ content="Important discovery",
313
+ role="assistant",
314
+ )
315
+
316
+ # Verify record was called
317
+ assert persistent_memory.record.called
318
+ call_args = persistent_memory.record.call_args[0][0]
319
+ assert len(call_args) == 1
320
+ assert call_args[0]["content"] == "Important discovery"
321
+
322
+ print("✅ Persistent memory recording works")
323
+
324
+
325
+ @pytest.mark.asyncio
326
+ class TestOrchestratorBothMemories:
327
+ """Tests for Orchestrator with both shared memories."""
328
+
329
+ async def test_orchestrator_with_both_shared_memories(self):
330
+ """Test orchestrator with both conversation and persistent memory."""
331
+ conv_memory = ConversationMemory()
332
+ persist_memory = create_mock_persistent_memory()
333
+
334
+ agents = {
335
+ "agent1": create_mock_agent("agent1"),
336
+ "agent2": create_mock_agent("agent2"),
337
+ }
338
+
339
+ orchestrator = Orchestrator(
340
+ agents=agents,
341
+ shared_conversation_memory=conv_memory,
342
+ shared_persistent_memory=persist_memory,
343
+ )
344
+
345
+ assert orchestrator.shared_conversation_memory is conv_memory
346
+ assert orchestrator.shared_persistent_memory is persist_memory
347
+
348
+ print("✅ Orchestrator with both shared memories works")
349
+
350
+ async def test_both_memories_used_together(self):
351
+ """Test that both memory types are used together correctly."""
352
+ conv_memory = ConversationMemory()
353
+ await conv_memory.add(
354
+ {
355
+ "role": "assistant",
356
+ "content": "Recent conversation",
357
+ "agent_id": "agent1",
358
+ },
359
+ )
360
+
361
+ persist_memory = create_mock_persistent_memory()
362
+ persist_memory.retrieve = AsyncMock(return_value="Long-term knowledge")
363
+
364
+ agents = {
365
+ "agent1": create_mock_agent("agent1"),
366
+ }
367
+
368
+ orchestrator = Orchestrator(
369
+ agents=agents,
370
+ shared_conversation_memory=conv_memory,
371
+ shared_persistent_memory=persist_memory,
372
+ )
373
+
374
+ # Inject both memories
375
+ injected_messages = await orchestrator._inject_shared_memory_context(
376
+ [{"role": "user", "content": "Task"}],
377
+ "agent1",
378
+ )
379
+
380
+ # Should have both memory types
381
+ all_content = "\n".join([msg.get("content", "") for msg in injected_messages])
382
+ assert "SHARED CONVERSATION MEMORY" in all_content
383
+ assert "Recent conversation" in all_content
384
+ assert "SHARED PERSISTENT MEMORY" in all_content
385
+ assert "Long-term knowledge" in all_content
386
+
387
+ print("✅ Both memories used together works")
388
+
389
+ async def test_recording_to_both_memories(self):
390
+ """Test that recordings go to both memory types."""
391
+ conv_memory = ConversationMemory()
392
+ persist_memory = create_mock_persistent_memory()
393
+
394
+ agents = {
395
+ "agent1": create_mock_agent("agent1"),
396
+ }
397
+
398
+ orchestrator = Orchestrator(
399
+ agents=agents,
400
+ shared_conversation_memory=conv_memory,
401
+ shared_persistent_memory=persist_memory,
402
+ )
403
+
404
+ # Record once
405
+ await orchestrator._record_to_shared_memory(
406
+ agent_id="agent1",
407
+ content="Shared finding",
408
+ role="assistant",
409
+ )
410
+
411
+ # Check conversation memory
412
+ conv_size = await conv_memory.size()
413
+ assert conv_size == 1
414
+
415
+ # Check persistent memory was called
416
+ assert persist_memory.record.called
417
+
418
+ print("✅ Recording to both memories works")
419
+
420
+
421
+ @pytest.mark.asyncio
422
+ class TestSharedMemoryErrorHandling:
423
+ """Tests for error handling in shared memory operations."""
424
+
425
+ async def test_orchestrator_without_shared_memory(self):
426
+ """Test that orchestrator works without shared memory."""
427
+ agents = {
428
+ "agent1": create_mock_agent("agent1"),
429
+ }
430
+
431
+ orchestrator = Orchestrator(agents=agents)
432
+
433
+ assert orchestrator.shared_conversation_memory is None
434
+ assert orchestrator.shared_persistent_memory is None
435
+
436
+ # Injection should return original messages
437
+ messages = [{"role": "user", "content": "Task"}]
438
+ injected = await orchestrator._inject_shared_memory_context(messages, "agent1")
439
+ assert injected == messages
440
+
441
+ # Recording should not fail
442
+ await orchestrator._record_to_shared_memory("agent1", "test", "assistant")
443
+
444
+ print("✅ Orchestrator works without shared memory")
445
+
446
+ async def test_memory_failure_doesnt_crash_orchestrator(self):
447
+ """Test that memory failures don't crash the orchestrator."""
448
+ # Create faulty memory
449
+ conv_memory = MagicMock()
450
+ conv_memory.get_messages = AsyncMock(side_effect=Exception("Memory error"))
451
+ conv_memory.add = AsyncMock(side_effect=Exception("Memory error"))
452
+
453
+ agents = {
454
+ "agent1": create_mock_agent("agent1"),
455
+ }
456
+
457
+ orchestrator = Orchestrator(
458
+ agents=agents,
459
+ shared_conversation_memory=conv_memory,
460
+ )
461
+
462
+ # Injection should not crash even if memory fails
463
+ messages = [{"role": "user", "content": "Task"}]
464
+ injected = await orchestrator._inject_shared_memory_context(messages, "agent1")
465
+ # Should return something (at least original messages)
466
+ assert injected is not None
467
+
468
+ # Recording should not crash
469
+ await orchestrator._record_to_shared_memory("agent1", "test", "assistant")
470
+
471
+ print("✅ Memory failures don't crash orchestrator")
472
+
473
+ async def test_persistent_memory_not_implemented_handled(self):
474
+ """Test that NotImplementedError from persistent memory is handled."""
475
+ persist_memory = MagicMock()
476
+ persist_memory.retrieve = AsyncMock(side_effect=NotImplementedError())
477
+ persist_memory.record = AsyncMock(side_effect=NotImplementedError())
478
+
479
+ agents = {
480
+ "agent1": create_mock_agent("agent1"),
481
+ }
482
+
483
+ orchestrator = Orchestrator(
484
+ agents=agents,
485
+ shared_persistent_memory=persist_memory,
486
+ )
487
+
488
+ # Should handle NotImplementedError gracefully
489
+ messages = [{"role": "user", "content": "Task"}]
490
+ injected = await orchestrator._inject_shared_memory_context(messages, "agent1")
491
+ assert injected is not None
492
+
493
+ await orchestrator._record_to_shared_memory("agent1", "test", "assistant")
494
+
495
+ print("✅ NotImplementedError from persistent memory handled")
496
+
497
+
498
+ @pytest.mark.asyncio
499
+ class TestCrossAgentMemoryVisibility:
500
+ """Tests for verifying agents can see each other's memory contributions."""
501
+
502
+ async def test_agent_can_see_other_agents_contributions(self):
503
+ """Test that one agent can see another agent's contributions in shared memory."""
504
+ shared_memory = ConversationMemory()
505
+
506
+ agents = {
507
+ "agent1": create_mock_agent("agent1"),
508
+ "agent2": create_mock_agent("agent2"),
509
+ }
510
+
511
+ orchestrator = Orchestrator(
512
+ agents=agents,
513
+ shared_conversation_memory=shared_memory,
514
+ )
515
+
516
+ # Agent1 makes a contribution
517
+ await orchestrator._record_to_shared_memory(
518
+ agent_id="agent1",
519
+ content="Agent1 discovered X",
520
+ role="assistant",
521
+ )
522
+
523
+ # Agent2 should see Agent1's contribution
524
+ messages_for_agent2 = await orchestrator._inject_shared_memory_context(
525
+ [{"role": "user", "content": "Continue the work"}],
526
+ "agent2",
527
+ )
528
+
529
+ # Check that agent2 can see agent1's contribution
530
+ all_content = "\n".join([msg.get("content", "") for msg in messages_for_agent2])
531
+ assert "agent1" in all_content.lower()
532
+ assert "Agent1 discovered X" in all_content
533
+
534
+ print("✅ Agent can see other agents' contributions")
535
+
536
+ async def test_memory_shows_agent_attribution(self):
537
+ """Test that shared memory properly attributes contributions to agents."""
538
+ shared_memory = ConversationMemory()
539
+
540
+ agents = {
541
+ "agent1": create_mock_agent("agent1"),
542
+ "agent2": create_mock_agent("agent2"),
543
+ "agent3": create_mock_agent("agent3"),
544
+ }
545
+
546
+ orchestrator = Orchestrator(
547
+ agents=agents,
548
+ shared_conversation_memory=shared_memory,
549
+ )
550
+
551
+ # Multiple agents contribute
552
+ await orchestrator._record_to_shared_memory("agent1", "Finding A", "assistant")
553
+ await orchestrator._record_to_shared_memory("agent2", "Finding B", "assistant")
554
+ await orchestrator._record_to_shared_memory("agent3", "Finding C", "assistant")
555
+
556
+ # Check that any agent can see all contributions with attribution
557
+ messages = await orchestrator._inject_shared_memory_context(
558
+ [{"role": "user", "content": "Task"}],
559
+ "agent1",
560
+ )
561
+
562
+ all_content = "\n".join([msg.get("content", "") for msg in messages])
563
+
564
+ # Should show all agents' contributions with attribution
565
+ assert "[agent1]" in all_content
566
+ assert "Finding A" in all_content
567
+ assert "[agent2]" in all_content
568
+ assert "Finding B" in all_content
569
+ assert "[agent3]" in all_content
570
+ assert "Finding C" in all_content
571
+
572
+ print("✅ Memory shows agent attribution")
573
+
574
+
575
+ if __name__ == "__main__":
576
+ import asyncio
577
+
578
+ async def run_all_tests():
579
+ """Run all tests manually."""
580
+ print("\n=== Running Orchestrator Shared Memory Tests ===\n")
581
+
582
+ # Shared conversation memory tests
583
+ print("\n--- Orchestrator with Shared Conversation Memory ---")
584
+ test_conv = TestOrchestratorSharedConversationMemory()
585
+ await test_conv.test_orchestrator_with_shared_conversation_memory()
586
+ await test_conv.test_shared_memory_injection_to_agents()
587
+ await test_conv.test_agent_contributions_recorded_to_shared_memory()
588
+ await test_conv.test_multiple_agents_share_same_memory()
589
+ await test_conv.test_shared_memory_accumulates_over_time()
590
+
591
+ # Shared persistent memory tests
592
+ print("\n--- Orchestrator with Shared Persistent Memory ---")
593
+ test_persist = TestOrchestratorSharedPersistentMemory()
594
+ await test_persist.test_orchestrator_with_shared_persistent_memory()
595
+ await test_persist.test_persistent_memory_retrieval_for_agents()
596
+ await test_persist.test_persistent_memory_recording()
597
+
598
+ # Both memories tests
599
+ print("\n--- Orchestrator with Both Shared Memories ---")
600
+ test_both = TestOrchestratorBothMemories()
601
+ await test_both.test_orchestrator_with_both_shared_memories()
602
+ await test_both.test_both_memories_used_together()
603
+ await test_both.test_recording_to_both_memories()
604
+
605
+ # Error handling tests
606
+ print("\n--- Shared Memory Error Handling ---")
607
+ test_errors = TestSharedMemoryErrorHandling()
608
+ await test_errors.test_orchestrator_without_shared_memory()
609
+ await test_errors.test_memory_failure_doesnt_crash_orchestrator()
610
+ await test_errors.test_persistent_memory_not_implemented_handled()
611
+
612
+ # Cross-agent visibility tests
613
+ print("\n--- Cross-Agent Memory Visibility ---")
614
+ test_cross = TestCrossAgentMemoryVisibility()
615
+ await test_cross.test_agent_can_see_other_agents_contributions()
616
+ await test_cross.test_memory_shows_agent_attribution()
617
+
618
+ print("\n=== All Orchestrator Shared Memory Tests Passed! ===\n")
619
+
620
+ asyncio.run(run_all_tests())