jarviscore-framework 0.3.0__py3-none-any.whl → 0.3.2__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.
Files changed (43) hide show
  1. examples/cloud_deployment_example.py +3 -3
  2. examples/{listeneragent_cognitive_discovery_example.py → customagent_cognitive_discovery_example.py} +55 -14
  3. examples/customagent_distributed_example.py +140 -1
  4. examples/fastapi_integration_example.py +74 -11
  5. jarviscore/__init__.py +8 -11
  6. jarviscore/cli/smoketest.py +1 -1
  7. jarviscore/core/mesh.py +158 -0
  8. jarviscore/data/examples/cloud_deployment_example.py +3 -3
  9. jarviscore/data/examples/custom_profile_decorator.py +134 -0
  10. jarviscore/data/examples/custom_profile_wrap.py +168 -0
  11. jarviscore/data/examples/{listeneragent_cognitive_discovery_example.py → customagent_cognitive_discovery_example.py} +55 -14
  12. jarviscore/data/examples/customagent_distributed_example.py +140 -1
  13. jarviscore/data/examples/fastapi_integration_example.py +74 -11
  14. jarviscore/docs/API_REFERENCE.md +576 -47
  15. jarviscore/docs/CHANGELOG.md +131 -0
  16. jarviscore/docs/CONFIGURATION.md +1 -1
  17. jarviscore/docs/CUSTOMAGENT_GUIDE.md +591 -153
  18. jarviscore/docs/GETTING_STARTED.md +186 -329
  19. jarviscore/docs/TROUBLESHOOTING.md +1 -1
  20. jarviscore/docs/USER_GUIDE.md +292 -12
  21. jarviscore/integrations/fastapi.py +4 -4
  22. jarviscore/p2p/coordinator.py +36 -7
  23. jarviscore/p2p/messages.py +13 -0
  24. jarviscore/p2p/peer_client.py +380 -21
  25. jarviscore/p2p/peer_tool.py +17 -11
  26. jarviscore/profiles/__init__.py +2 -4
  27. jarviscore/profiles/customagent.py +302 -74
  28. jarviscore/testing/__init__.py +35 -0
  29. jarviscore/testing/mocks.py +578 -0
  30. {jarviscore_framework-0.3.0.dist-info → jarviscore_framework-0.3.2.dist-info}/METADATA +61 -46
  31. {jarviscore_framework-0.3.0.dist-info → jarviscore_framework-0.3.2.dist-info}/RECORD +42 -34
  32. tests/test_13_dx_improvements.py +37 -37
  33. tests/test_15_llm_cognitive_discovery.py +18 -18
  34. tests/test_16_unified_dx_flow.py +3 -3
  35. tests/test_17_session_context.py +489 -0
  36. tests/test_18_mesh_diagnostics.py +465 -0
  37. tests/test_19_async_requests.py +516 -0
  38. tests/test_20_load_balancing.py +546 -0
  39. tests/test_21_mock_testing.py +776 -0
  40. jarviscore/profiles/listeneragent.py +0 -292
  41. {jarviscore_framework-0.3.0.dist-info → jarviscore_framework-0.3.2.dist-info}/WHEEL +0 -0
  42. {jarviscore_framework-0.3.0.dist-info → jarviscore_framework-0.3.2.dist-info}/licenses/LICENSE +0 -0
  43. {jarviscore_framework-0.3.0.dist-info → jarviscore_framework-0.3.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,489 @@
1
+ """
2
+ Test 17: Session Context Propagation (Feature F6)
3
+
4
+ Tests the context propagation feature:
5
+ - Context parameter in notify(), request(), respond(), broadcast()
6
+ - Auto-propagation of context in respond()
7
+ - Context accessible via IncomingMessage.context
8
+
9
+ Run with: pytest tests/test_17_session_context.py -v -s
10
+ """
11
+ import asyncio
12
+ import sys
13
+ import pytest
14
+ import logging
15
+ from unittest.mock import AsyncMock, MagicMock, patch
16
+
17
+ sys.path.insert(0, '.')
18
+
19
+ # Setup logging
20
+ logging.basicConfig(level=logging.INFO)
21
+ logger = logging.getLogger(__name__)
22
+
23
+
24
+ # =============================================================================
25
+ # TEST: CONTEXT IN OUTGOING MESSAGES
26
+ # =============================================================================
27
+
28
+ class TestContextInNotify:
29
+ """Test context parameter in notify()."""
30
+
31
+ @pytest.mark.asyncio
32
+ async def test_notify_with_context(self):
33
+ """Test notify() accepts and sends context."""
34
+ from jarviscore.testing import MockPeerClient
35
+
36
+ client = MockPeerClient(
37
+ agent_id="sender-1",
38
+ agent_role="sender",
39
+ mock_peers=[{"role": "receiver", "capabilities": ["receiving"]}]
40
+ )
41
+
42
+ context = {"mission_id": "mission-123", "priority": "high"}
43
+ result = await client.notify("receiver", {"event": "test"}, context=context)
44
+
45
+ assert result is True
46
+ notifications = client.get_sent_notifications()
47
+ assert len(notifications) == 1
48
+ assert notifications[0]["context"] == context
49
+ assert notifications[0]["message"] == {"event": "test"}
50
+
51
+ @pytest.mark.asyncio
52
+ async def test_notify_without_context(self):
53
+ """Test notify() works without context (None by default)."""
54
+ from jarviscore.testing import MockPeerClient
55
+
56
+ client = MockPeerClient(
57
+ agent_id="sender-1",
58
+ agent_role="sender",
59
+ mock_peers=[{"role": "receiver", "capabilities": ["receiving"]}]
60
+ )
61
+
62
+ result = await client.notify("receiver", {"event": "test"})
63
+
64
+ assert result is True
65
+ notifications = client.get_sent_notifications()
66
+ assert len(notifications) == 1
67
+ assert notifications[0]["context"] is None
68
+
69
+
70
+ class TestContextInRequest:
71
+ """Test context parameter in request()."""
72
+
73
+ @pytest.mark.asyncio
74
+ async def test_request_with_context(self):
75
+ """Test request() accepts and sends context."""
76
+ from jarviscore.testing import MockPeerClient
77
+
78
+ client = MockPeerClient(
79
+ agent_id="sender-1",
80
+ agent_role="sender",
81
+ mock_peers=[{"role": "analyst", "capabilities": ["analysis"]}]
82
+ )
83
+ client.set_mock_response("analyst", {"result": "analyzed"})
84
+
85
+ context = {"mission_id": "mission-456", "trace_id": "trace-abc"}
86
+ response = await client.request("analyst", {"query": "test"}, context=context)
87
+
88
+ assert response == {"result": "analyzed"}
89
+ requests = client.get_sent_requests()
90
+ assert len(requests) == 1
91
+ assert requests[0]["context"] == context
92
+
93
+ @pytest.mark.asyncio
94
+ async def test_request_without_context(self):
95
+ """Test request() works without context."""
96
+ from jarviscore.testing import MockPeerClient
97
+
98
+ client = MockPeerClient(
99
+ agent_id="sender-1",
100
+ agent_role="sender",
101
+ mock_peers=[{"role": "analyst", "capabilities": ["analysis"]}]
102
+ )
103
+
104
+ response = await client.request("analyst", {"query": "test"})
105
+
106
+ assert response is not None
107
+ requests = client.get_sent_requests()
108
+ assert len(requests) == 1
109
+ assert requests[0]["context"] is None
110
+
111
+
112
+ class TestContextInBroadcast:
113
+ """Test context parameter in broadcast()."""
114
+
115
+ @pytest.mark.asyncio
116
+ async def test_broadcast_with_context(self):
117
+ """Test broadcast() accepts and sends context to all peers."""
118
+ from jarviscore.testing import MockPeerClient
119
+
120
+ client = MockPeerClient(
121
+ agent_id="broadcaster-1",
122
+ agent_role="broadcaster",
123
+ mock_peers=[
124
+ {"role": "peer1", "capabilities": ["cap1"]},
125
+ {"role": "peer2", "capabilities": ["cap2"]}
126
+ ]
127
+ )
128
+
129
+ context = {"broadcast_id": "bc-789", "source": "alert_system"}
130
+ count = await client.broadcast({"alert": "important"}, context=context)
131
+
132
+ assert count == 2
133
+ broadcasts = client.get_sent_broadcasts()
134
+ assert len(broadcasts) == 1
135
+ assert broadcasts[0]["context"] == context
136
+
137
+
138
+ # =============================================================================
139
+ # TEST: CONTEXT IN INCOMING MESSAGES
140
+ # =============================================================================
141
+
142
+ class TestContextInIncomingMessage:
143
+ """Test context accessible in IncomingMessage."""
144
+
145
+ def test_incoming_message_has_context_field(self):
146
+ """Test IncomingMessage dataclass has context field."""
147
+ from jarviscore.p2p.messages import IncomingMessage, MessageType
148
+
149
+ msg = IncomingMessage(
150
+ sender="sender-1",
151
+ sender_node="localhost:7946",
152
+ type=MessageType.NOTIFY,
153
+ data={"event": "test"},
154
+ context={"mission_id": "m-123"}
155
+ )
156
+
157
+ assert msg.context == {"mission_id": "m-123"}
158
+
159
+ def test_incoming_message_context_default_none(self):
160
+ """Test IncomingMessage context defaults to None."""
161
+ from jarviscore.p2p.messages import IncomingMessage, MessageType
162
+
163
+ msg = IncomingMessage(
164
+ sender="sender-1",
165
+ sender_node="localhost:7946",
166
+ type=MessageType.NOTIFY,
167
+ data={"event": "test"}
168
+ )
169
+
170
+ assert msg.context is None
171
+
172
+
173
+ class TestContextInOutgoingMessage:
174
+ """Test context in OutgoingMessage dataclass."""
175
+
176
+ def test_outgoing_message_has_context_field(self):
177
+ """Test OutgoingMessage dataclass has context field."""
178
+ from jarviscore.p2p.messages import OutgoingMessage, MessageType
179
+
180
+ msg = OutgoingMessage(
181
+ target="receiver",
182
+ type=MessageType.REQUEST,
183
+ data={"query": "test"},
184
+ context={"priority": "high"}
185
+ )
186
+
187
+ assert msg.context == {"priority": "high"}
188
+
189
+
190
+ # =============================================================================
191
+ # TEST: CONTEXT AUTO-PROPAGATION IN RESPOND
192
+ # =============================================================================
193
+
194
+ class TestContextAutoPropagation:
195
+ """Test context auto-propagation in respond()."""
196
+
197
+ @pytest.mark.asyncio
198
+ async def test_respond_auto_propagates_context(self):
199
+ """Test respond() auto-propagates request context if not overridden."""
200
+ from jarviscore.p2p.peer_client import PeerClient
201
+ from jarviscore.p2p.messages import IncomingMessage, MessageType
202
+
203
+ # Create a mock coordinator
204
+ mock_coordinator = MagicMock()
205
+ mock_coordinator._send_p2p_message = AsyncMock(return_value=True)
206
+ mock_coordinator._remote_agent_registry = {}
207
+
208
+ # Create mock agent registry with a sender agent
209
+ class MockAgent:
210
+ def __init__(self):
211
+ self.agent_id = "sender-1"
212
+ self.role = "sender"
213
+ self.capabilities = ["sending"]
214
+ self.peers = MagicMock()
215
+ self.peers._deliver_message = AsyncMock()
216
+
217
+ mock_sender = MockAgent()
218
+ agent_registry = {"sender": [mock_sender]}
219
+
220
+ # Create the responder PeerClient
221
+ client = PeerClient(
222
+ coordinator=mock_coordinator,
223
+ agent_id="responder-1",
224
+ agent_role="responder",
225
+ agent_registry=agent_registry,
226
+ node_id="localhost:7946"
227
+ )
228
+
229
+ # Create incoming request with context
230
+ incoming = IncomingMessage(
231
+ sender="sender-1",
232
+ sender_node="localhost:7946",
233
+ type=MessageType.REQUEST,
234
+ data={"query": "test"},
235
+ correlation_id="corr-123",
236
+ context={"mission_id": "m-999", "trace_id": "t-abc"}
237
+ )
238
+
239
+ # Respond without explicitly providing context
240
+ result = await client.respond(incoming, {"answer": "42"})
241
+
242
+ assert result is True
243
+
244
+ # Verify the response was delivered with propagated context
245
+ mock_sender.peers._deliver_message.assert_called_once()
246
+ delivered_msg = mock_sender.peers._deliver_message.call_args[0][0]
247
+ assert delivered_msg.context == {"mission_id": "m-999", "trace_id": "t-abc"}
248
+
249
+ @pytest.mark.asyncio
250
+ async def test_respond_override_context(self):
251
+ """Test respond() can override context."""
252
+ from jarviscore.p2p.peer_client import PeerClient
253
+ from jarviscore.p2p.messages import IncomingMessage, MessageType
254
+
255
+ mock_coordinator = MagicMock()
256
+ mock_coordinator._send_p2p_message = AsyncMock(return_value=True)
257
+ mock_coordinator._remote_agent_registry = {}
258
+
259
+ class MockAgent:
260
+ def __init__(self):
261
+ self.agent_id = "sender-1"
262
+ self.role = "sender"
263
+ self.capabilities = ["sending"]
264
+ self.peers = MagicMock()
265
+ self.peers._deliver_message = AsyncMock()
266
+
267
+ mock_sender = MockAgent()
268
+ agent_registry = {"sender": [mock_sender]}
269
+
270
+ client = PeerClient(
271
+ coordinator=mock_coordinator,
272
+ agent_id="responder-1",
273
+ agent_role="responder",
274
+ agent_registry=agent_registry,
275
+ node_id="localhost:7946"
276
+ )
277
+
278
+ # Incoming with original context
279
+ incoming = IncomingMessage(
280
+ sender="sender-1",
281
+ sender_node="localhost:7946",
282
+ type=MessageType.REQUEST,
283
+ data={"query": "test"},
284
+ correlation_id="corr-456",
285
+ context={"original": "context"}
286
+ )
287
+
288
+ # Respond with overridden context
289
+ result = await client.respond(
290
+ incoming,
291
+ {"answer": "42"},
292
+ context={"overridden": "context", "status": "complete"}
293
+ )
294
+
295
+ assert result is True
296
+ delivered_msg = mock_sender.peers._deliver_message.call_args[0][0]
297
+ assert delivered_msg.context == {"overridden": "context", "status": "complete"}
298
+
299
+
300
+ # =============================================================================
301
+ # TEST: CONTEXT THROUGH LOCAL DELIVERY
302
+ # =============================================================================
303
+
304
+ class TestContextLocalDelivery:
305
+ """Test context flows through local message delivery."""
306
+
307
+ @pytest.mark.asyncio
308
+ async def test_context_in_local_notify(self):
309
+ """Test context preserved in local notify delivery."""
310
+ from jarviscore.p2p.peer_client import PeerClient
311
+ from jarviscore.p2p.messages import MessageType
312
+
313
+ mock_coordinator = MagicMock()
314
+ mock_coordinator._remote_agent_registry = {}
315
+
316
+ # Create receiver agent with PeerClient
317
+ class MockReceiverAgent:
318
+ def __init__(self):
319
+ self.agent_id = "receiver-1"
320
+ self.role = "receiver"
321
+ self.capabilities = ["receiving"]
322
+ self.peers = None
323
+
324
+ receiver = MockReceiverAgent()
325
+ receiver_client = PeerClient(
326
+ coordinator=mock_coordinator,
327
+ agent_id="receiver-1",
328
+ agent_role="receiver",
329
+ agent_registry={},
330
+ node_id="localhost:7946"
331
+ )
332
+ receiver.peers = receiver_client
333
+
334
+ # Create sender with receiver in registry
335
+ agent_registry = {"receiver": [receiver]}
336
+ sender_client = PeerClient(
337
+ coordinator=mock_coordinator,
338
+ agent_id="sender-1",
339
+ agent_role="sender",
340
+ agent_registry=agent_registry,
341
+ node_id="localhost:7946"
342
+ )
343
+
344
+ # Send notify with context
345
+ context = {"session_id": "sess-123", "user_id": "user-456"}
346
+ result = await sender_client.notify("receiver", {"event": "test"}, context=context)
347
+
348
+ assert result is True
349
+
350
+ # Receive and check context
351
+ msg = await receiver_client.receive(timeout=1)
352
+ assert msg is not None
353
+ assert msg.type == MessageType.NOTIFY
354
+ assert msg.context == context
355
+
356
+
357
+ # =============================================================================
358
+ # TEST: CONTEXT IN MOCK PEER CLIENT
359
+ # =============================================================================
360
+
361
+ class TestMockPeerClientContext:
362
+ """Test MockPeerClient supports context in all operations."""
363
+
364
+ @pytest.mark.asyncio
365
+ async def test_mock_inject_message_with_context(self):
366
+ """Test MockPeerClient.inject_message() supports context."""
367
+ from jarviscore.testing import MockPeerClient
368
+ from jarviscore.p2p.messages import MessageType
369
+
370
+ client = MockPeerClient()
371
+
372
+ context = {"injected": "context", "test_id": "inject-1"}
373
+ client.inject_message(
374
+ sender="injector",
375
+ message_type=MessageType.NOTIFY,
376
+ data={"injected": True},
377
+ context=context
378
+ )
379
+
380
+ msg = await client.receive(timeout=1)
381
+ assert msg is not None
382
+ assert msg.context == context
383
+ assert msg.data == {"injected": True}
384
+
385
+ @pytest.mark.asyncio
386
+ async def test_mock_tracks_context_in_sent_messages(self):
387
+ """Test MockPeerClient tracks context in all sent messages."""
388
+ from jarviscore.testing import MockPeerClient
389
+
390
+ client = MockPeerClient(
391
+ mock_peers=[{"role": "target", "capabilities": []}]
392
+ )
393
+
394
+ # Send notification with context
395
+ await client.notify("target", {"n": 1}, context={"ctx": "notify"})
396
+ assert client.get_sent_notifications()[0]["context"] == {"ctx": "notify"}
397
+
398
+ # Send request with context
399
+ await client.request("target", {"r": 2}, context={"ctx": "request"})
400
+ assert client.get_sent_requests()[0]["context"] == {"ctx": "request"}
401
+
402
+ # Send broadcast with context
403
+ await client.broadcast({"b": 3}, context={"ctx": "broadcast"})
404
+ assert client.get_sent_broadcasts()[0]["context"] == {"ctx": "broadcast"}
405
+
406
+
407
+ # =============================================================================
408
+ # TEST: CONTEXT IN CUSTOM AGENT HANDLERS
409
+ # =============================================================================
410
+
411
+ class TestContextInCustomAgentHandlers:
412
+ """Test context accessible in CustomAgent message handlers."""
413
+
414
+ @pytest.mark.asyncio
415
+ async def test_context_in_on_peer_request(self):
416
+ """Test context is accessible in on_peer_request handler."""
417
+ from jarviscore.profiles import CustomAgent
418
+ from jarviscore.p2p.messages import IncomingMessage, MessageType
419
+
420
+ received_context = None
421
+
422
+ class TestAgent(CustomAgent):
423
+ role = "context_handler"
424
+ capabilities = ["handling"]
425
+
426
+ async def on_peer_request(self, msg):
427
+ nonlocal received_context
428
+ received_context = msg.context
429
+ return {"received_context": msg.context}
430
+
431
+ agent = TestAgent()
432
+ agent._logger = MagicMock()
433
+ agent.peers = MagicMock()
434
+ agent.peers.respond = AsyncMock()
435
+
436
+ msg = IncomingMessage(
437
+ sender="sender",
438
+ sender_node="localhost:7946",
439
+ type=MessageType.REQUEST,
440
+ data={"query": "test"},
441
+ correlation_id="corr-123",
442
+ context={"mission_id": "m-handler", "stage": "processing"}
443
+ )
444
+
445
+ await agent._dispatch_message(msg)
446
+
447
+ assert received_context == {"mission_id": "m-handler", "stage": "processing"}
448
+
449
+ @pytest.mark.asyncio
450
+ async def test_context_in_on_peer_notify(self):
451
+ """Test context is accessible in on_peer_notify handler."""
452
+ from jarviscore.profiles import CustomAgent
453
+ from jarviscore.p2p.messages import IncomingMessage, MessageType
454
+
455
+ received_context = None
456
+
457
+ class TestAgent(CustomAgent):
458
+ role = "context_handler"
459
+ capabilities = ["handling"]
460
+
461
+ async def on_peer_notify(self, msg):
462
+ nonlocal received_context
463
+ received_context = msg.context
464
+
465
+ async def on_peer_request(self, msg):
466
+ return {}
467
+
468
+ agent = TestAgent()
469
+ agent._logger = MagicMock()
470
+
471
+ msg = IncomingMessage(
472
+ sender="sender",
473
+ sender_node="localhost:7946",
474
+ type=MessageType.NOTIFY,
475
+ data={"event": "test"},
476
+ context={"notification_source": "event_system"}
477
+ )
478
+
479
+ await agent._dispatch_message(msg)
480
+
481
+ assert received_context == {"notification_source": "event_system"}
482
+
483
+
484
+ # =============================================================================
485
+ # RUN TESTS
486
+ # =============================================================================
487
+
488
+ if __name__ == "__main__":
489
+ pytest.main([__file__, "-v", "-s"])