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.
- examples/cloud_deployment_example.py +3 -3
- examples/{listeneragent_cognitive_discovery_example.py → customagent_cognitive_discovery_example.py} +55 -14
- examples/customagent_distributed_example.py +140 -1
- examples/fastapi_integration_example.py +74 -11
- jarviscore/__init__.py +8 -11
- jarviscore/cli/smoketest.py +1 -1
- jarviscore/core/mesh.py +158 -0
- jarviscore/data/examples/cloud_deployment_example.py +3 -3
- jarviscore/data/examples/custom_profile_decorator.py +134 -0
- jarviscore/data/examples/custom_profile_wrap.py +168 -0
- jarviscore/data/examples/{listeneragent_cognitive_discovery_example.py → customagent_cognitive_discovery_example.py} +55 -14
- jarviscore/data/examples/customagent_distributed_example.py +140 -1
- jarviscore/data/examples/fastapi_integration_example.py +74 -11
- jarviscore/docs/API_REFERENCE.md +576 -47
- jarviscore/docs/CHANGELOG.md +131 -0
- jarviscore/docs/CONFIGURATION.md +1 -1
- jarviscore/docs/CUSTOMAGENT_GUIDE.md +591 -153
- jarviscore/docs/GETTING_STARTED.md +186 -329
- jarviscore/docs/TROUBLESHOOTING.md +1 -1
- jarviscore/docs/USER_GUIDE.md +292 -12
- jarviscore/integrations/fastapi.py +4 -4
- jarviscore/p2p/coordinator.py +36 -7
- jarviscore/p2p/messages.py +13 -0
- jarviscore/p2p/peer_client.py +380 -21
- jarviscore/p2p/peer_tool.py +17 -11
- jarviscore/profiles/__init__.py +2 -4
- jarviscore/profiles/customagent.py +302 -74
- jarviscore/testing/__init__.py +35 -0
- jarviscore/testing/mocks.py +578 -0
- {jarviscore_framework-0.3.0.dist-info → jarviscore_framework-0.3.2.dist-info}/METADATA +61 -46
- {jarviscore_framework-0.3.0.dist-info → jarviscore_framework-0.3.2.dist-info}/RECORD +42 -34
- tests/test_13_dx_improvements.py +37 -37
- tests/test_15_llm_cognitive_discovery.py +18 -18
- tests/test_16_unified_dx_flow.py +3 -3
- tests/test_17_session_context.py +489 -0
- tests/test_18_mesh_diagnostics.py +465 -0
- tests/test_19_async_requests.py +516 -0
- tests/test_20_load_balancing.py +546 -0
- tests/test_21_mock_testing.py +776 -0
- jarviscore/profiles/listeneragent.py +0 -292
- {jarviscore_framework-0.3.0.dist-info → jarviscore_framework-0.3.2.dist-info}/WHEEL +0 -0
- {jarviscore_framework-0.3.0.dist-info → jarviscore_framework-0.3.2.dist-info}/licenses/LICENSE +0 -0
- {jarviscore_framework-0.3.0.dist-info → jarviscore_framework-0.3.2.dist-info}/top_level.txt +0 -0
jarviscore/docs/USER_GUIDE.md
CHANGED
|
@@ -19,9 +19,14 @@ Practical guide to building agent systems with JarvisCore.
|
|
|
19
19
|
11. [FastAPI Integration (v0.3.0)](#fastapi-integration-v030)
|
|
20
20
|
12. [Cloud Deployment (v0.3.0)](#cloud-deployment-v030)
|
|
21
21
|
13. [Cognitive Discovery (v0.3.0)](#cognitive-discovery-v030)
|
|
22
|
-
14. [
|
|
23
|
-
15. [
|
|
24
|
-
16. [
|
|
22
|
+
14. [Session Context (v0.3.2)](#session-context-v032)
|
|
23
|
+
15. [Async Requests (v0.3.2)](#async-requests-v032)
|
|
24
|
+
16. [Load Balancing (v0.3.2)](#load-balancing-v032)
|
|
25
|
+
17. [Mesh Diagnostics (v0.3.2)](#mesh-diagnostics-v032)
|
|
26
|
+
18. [Testing with MockMesh (v0.3.2)](#testing-with-mockmesh-v032)
|
|
27
|
+
19. [Best Practices](#best-practices)
|
|
28
|
+
20. [Common Patterns](#common-patterns)
|
|
29
|
+
21. [Troubleshooting](#troubleshooting)
|
|
25
30
|
|
|
26
31
|
---
|
|
27
32
|
|
|
@@ -146,8 +151,7 @@ mesh = Mesh(mode="distributed", config={'bind_port': 7950})
|
|
|
146
151
|
| Profile | Best For | How It Works |
|
|
147
152
|
|---------|----------|--------------|
|
|
148
153
|
| **AutoAgent** | Rapid prototyping | LLM generates + executes code from prompts |
|
|
149
|
-
| **CustomAgent** |
|
|
150
|
-
| **ListenerAgent** | API-first agents | Just implement `on_peer_request()` handlers |
|
|
154
|
+
| **CustomAgent** | Your own code | Implement `on_peer_request()` for P2P or `execute_task()` for workflows |
|
|
151
155
|
|
|
152
156
|
See [AutoAgent Guide](AUTOAGENT_GUIDE.md) and [CustomAgent Guide](CUSTOMAGENT_GUIDE.md) for details.
|
|
153
157
|
|
|
@@ -227,7 +231,7 @@ asyncio.run(data_analyst_demo())
|
|
|
227
231
|
|
|
228
232
|
## Custom Profile Tutorial
|
|
229
233
|
|
|
230
|
-
|
|
234
|
+
Use **CustomAgent** profile.
|
|
231
235
|
|
|
232
236
|
See [CustomAgent Guide](CUSTOMAGENT_GUIDE.md) for:
|
|
233
237
|
- Converting standalone agents to JarvisCore
|
|
@@ -546,10 +550,10 @@ Deploy agents as FastAPI services with minimal boilerplate:
|
|
|
546
550
|
|
|
547
551
|
```python
|
|
548
552
|
from fastapi import FastAPI, Request
|
|
549
|
-
from jarviscore.profiles import
|
|
553
|
+
from jarviscore.profiles import CustomAgent
|
|
550
554
|
from jarviscore.integrations.fastapi import JarvisLifespan
|
|
551
555
|
|
|
552
|
-
class ProcessorAgent(
|
|
556
|
+
class ProcessorAgent(CustomAgent):
|
|
553
557
|
role = "processor"
|
|
554
558
|
capabilities = ["processing"]
|
|
555
559
|
|
|
@@ -583,9 +587,9 @@ Deploy agents to containers without a central orchestrator:
|
|
|
583
587
|
```python
|
|
584
588
|
# In your container entrypoint
|
|
585
589
|
import asyncio
|
|
586
|
-
from jarviscore.profiles import
|
|
590
|
+
from jarviscore.profiles import CustomAgent
|
|
587
591
|
|
|
588
|
-
class MyAgent(
|
|
592
|
+
class MyAgent(CustomAgent):
|
|
589
593
|
role = "worker"
|
|
590
594
|
capabilities = ["processing"]
|
|
591
595
|
|
|
@@ -680,6 +684,282 @@ Use the `ask_peer` tool to delegate tasks to these specialists.
|
|
|
680
684
|
|
|
681
685
|
---
|
|
682
686
|
|
|
687
|
+
## Session Context (v0.3.2)
|
|
688
|
+
|
|
689
|
+
Pass metadata through your message flows for tracing, priority, and session tracking:
|
|
690
|
+
|
|
691
|
+
### Basic Usage
|
|
692
|
+
|
|
693
|
+
```python
|
|
694
|
+
# Send request with context
|
|
695
|
+
response = await self.peers.request(
|
|
696
|
+
"analyst",
|
|
697
|
+
{"query": "analyze sales data"},
|
|
698
|
+
context={"mission_id": "m-123", "priority": "high", "user_id": "u-456"}
|
|
699
|
+
)
|
|
700
|
+
|
|
701
|
+
# Context is available in the handler
|
|
702
|
+
async def on_peer_request(self, msg):
|
|
703
|
+
mission_id = msg.context.get("mission_id") # "m-123"
|
|
704
|
+
print(f"Processing request for mission: {mission_id}")
|
|
705
|
+
return {"result": "analysis complete"}
|
|
706
|
+
```
|
|
707
|
+
|
|
708
|
+
### Auto-Propagation
|
|
709
|
+
|
|
710
|
+
When you `respond()`, context automatically propagates from the request:
|
|
711
|
+
|
|
712
|
+
```python
|
|
713
|
+
async def on_peer_request(self, msg):
|
|
714
|
+
# msg.context = {"mission_id": "m-123", ...}
|
|
715
|
+
result = process(msg.data)
|
|
716
|
+
|
|
717
|
+
# Context auto-propagates - no need to pass it!
|
|
718
|
+
await self.peers.respond(msg, {"result": result})
|
|
719
|
+
|
|
720
|
+
# Or override with custom context
|
|
721
|
+
await self.peers.respond(msg, {"result": result},
|
|
722
|
+
context={"status": "completed"})
|
|
723
|
+
```
|
|
724
|
+
|
|
725
|
+
### All Methods Support Context
|
|
726
|
+
|
|
727
|
+
```python
|
|
728
|
+
# notify
|
|
729
|
+
await self.peers.notify("logger", {"event": "started"}, context={"trace_id": "t-1"})
|
|
730
|
+
|
|
731
|
+
# request
|
|
732
|
+
response = await self.peers.request("worker", {"task": "..."}, context={"priority": "low"})
|
|
733
|
+
|
|
734
|
+
# broadcast
|
|
735
|
+
await self.peers.broadcast({"alert": "system ready"}, context={"source": "coordinator"})
|
|
736
|
+
|
|
737
|
+
# ask_async
|
|
738
|
+
req_id = await self.peers.ask_async("analyst", {"q": "..."}, context={"batch_id": "b-1"})
|
|
739
|
+
```
|
|
740
|
+
|
|
741
|
+
---
|
|
742
|
+
|
|
743
|
+
## Async Requests (v0.3.2)
|
|
744
|
+
|
|
745
|
+
Fire-and-collect pattern for parallel requests without blocking:
|
|
746
|
+
|
|
747
|
+
### Basic Pattern
|
|
748
|
+
|
|
749
|
+
```python
|
|
750
|
+
# Fire off multiple requests (non-blocking)
|
|
751
|
+
request_ids = []
|
|
752
|
+
for analyst in self.peers.discover(role="analyst"):
|
|
753
|
+
req_id = await self.peers.ask_async(analyst.agent_id, {"task": "analyze"})
|
|
754
|
+
request_ids.append(req_id)
|
|
755
|
+
|
|
756
|
+
# Do other work while analysts process...
|
|
757
|
+
await self.do_other_work()
|
|
758
|
+
|
|
759
|
+
# Collect responses
|
|
760
|
+
results = []
|
|
761
|
+
for req_id in request_ids:
|
|
762
|
+
response = await self.peers.check_inbox(req_id, timeout=10)
|
|
763
|
+
if response:
|
|
764
|
+
results.append(response)
|
|
765
|
+
```
|
|
766
|
+
|
|
767
|
+
### API Reference
|
|
768
|
+
|
|
769
|
+
```python
|
|
770
|
+
# Send async request - returns immediately
|
|
771
|
+
req_id = await self.peers.ask_async(target, message, timeout=120, context=None)
|
|
772
|
+
|
|
773
|
+
# Check for response (non-blocking if timeout=0)
|
|
774
|
+
response = await self.peers.check_inbox(req_id, timeout=0)
|
|
775
|
+
|
|
776
|
+
# Check with wait
|
|
777
|
+
response = await self.peers.check_inbox(req_id, timeout=5)
|
|
778
|
+
|
|
779
|
+
# List pending requests
|
|
780
|
+
pending = self.peers.get_pending_async_requests()
|
|
781
|
+
# [{"request_id": "...", "target": "analyst", "sent_at": 1234567890.0}]
|
|
782
|
+
|
|
783
|
+
# Clear inbox
|
|
784
|
+
self.peers.clear_inbox(req_id) # Specific
|
|
785
|
+
self.peers.clear_inbox() # All
|
|
786
|
+
```
|
|
787
|
+
|
|
788
|
+
---
|
|
789
|
+
|
|
790
|
+
## Load Balancing (v0.3.2)
|
|
791
|
+
|
|
792
|
+
Distribute requests across multiple peers with discovery strategies:
|
|
793
|
+
|
|
794
|
+
### Strategies
|
|
795
|
+
|
|
796
|
+
```python
|
|
797
|
+
# Default: first in discovery order
|
|
798
|
+
peers = self.peers.discover(role="worker", strategy="first")
|
|
799
|
+
|
|
800
|
+
# Random: shuffle for basic load distribution
|
|
801
|
+
peers = self.peers.discover(role="worker", strategy="random")
|
|
802
|
+
|
|
803
|
+
# Round-robin: rotate through peers on each call
|
|
804
|
+
peers = self.peers.discover(role="worker", strategy="round_robin")
|
|
805
|
+
|
|
806
|
+
# Least-recent: prefer peers not used recently
|
|
807
|
+
peers = self.peers.discover(role="worker", strategy="least_recent")
|
|
808
|
+
```
|
|
809
|
+
|
|
810
|
+
### Convenience Method
|
|
811
|
+
|
|
812
|
+
```python
|
|
813
|
+
# Get single peer with strategy
|
|
814
|
+
worker = self.peers.discover_one(role="worker", strategy="round_robin")
|
|
815
|
+
if worker:
|
|
816
|
+
await self.peers.request(worker.agent_id, {"task": "..."})
|
|
817
|
+
```
|
|
818
|
+
|
|
819
|
+
### Track Usage for least_recent
|
|
820
|
+
|
|
821
|
+
```python
|
|
822
|
+
peer = self.peers.discover_one(role="worker", strategy="least_recent")
|
|
823
|
+
response = await self.peers.request(peer.agent_id, {"task": "..."})
|
|
824
|
+
|
|
825
|
+
# Update usage timestamp after successful communication
|
|
826
|
+
self.peers.record_peer_usage(peer.agent_id)
|
|
827
|
+
```
|
|
828
|
+
|
|
829
|
+
### Example: Round-Robin Work Distribution
|
|
830
|
+
|
|
831
|
+
```python
|
|
832
|
+
async def distribute_tasks(self, tasks):
|
|
833
|
+
results = []
|
|
834
|
+
for task in tasks:
|
|
835
|
+
# Each call rotates to next worker
|
|
836
|
+
worker = self.peers.discover_one(role="worker", strategy="round_robin")
|
|
837
|
+
if worker:
|
|
838
|
+
response = await self.peers.request(worker.agent_id, {"task": task})
|
|
839
|
+
results.append(response)
|
|
840
|
+
return results
|
|
841
|
+
```
|
|
842
|
+
|
|
843
|
+
---
|
|
844
|
+
|
|
845
|
+
## Mesh Diagnostics (v0.3.2)
|
|
846
|
+
|
|
847
|
+
Monitor mesh health and debug connectivity issues:
|
|
848
|
+
|
|
849
|
+
### Get Diagnostics
|
|
850
|
+
|
|
851
|
+
```python
|
|
852
|
+
diag = mesh.get_diagnostics()
|
|
853
|
+
|
|
854
|
+
print(f"Mode: {diag['local_node']['mode']}")
|
|
855
|
+
print(f"Status: {diag['connectivity_status']}")
|
|
856
|
+
print(f"Agents: {diag['local_node']['agent_count']}")
|
|
857
|
+
|
|
858
|
+
for agent in diag['local_agents']:
|
|
859
|
+
print(f" - {agent['role']}: {agent['capabilities']}")
|
|
860
|
+
|
|
861
|
+
for peer in diag['known_peers']:
|
|
862
|
+
print(f" - {peer['role']} @ {peer['node_id']}: {peer['status']}")
|
|
863
|
+
```
|
|
864
|
+
|
|
865
|
+
### Connectivity Status Values
|
|
866
|
+
|
|
867
|
+
| Status | Meaning |
|
|
868
|
+
|--------|---------|
|
|
869
|
+
| `healthy` | P2P active with connected peers |
|
|
870
|
+
| `isolated` | P2P active but no peers found |
|
|
871
|
+
| `degraded` | Some connectivity issues |
|
|
872
|
+
| `not_started` | Mesh not yet started |
|
|
873
|
+
| `local_only` | Autonomous mode (no P2P) |
|
|
874
|
+
|
|
875
|
+
### FastAPI Health Endpoint
|
|
876
|
+
|
|
877
|
+
```python
|
|
878
|
+
@app.get("/health")
|
|
879
|
+
async def health(request: Request):
|
|
880
|
+
mesh = request.app.state.jarvis_mesh
|
|
881
|
+
diag = mesh.get_diagnostics()
|
|
882
|
+
return {
|
|
883
|
+
"status": diag["connectivity_status"],
|
|
884
|
+
"agents": diag["local_node"]["agent_count"],
|
|
885
|
+
"peers": len(diag["known_peers"])
|
|
886
|
+
}
|
|
887
|
+
```
|
|
888
|
+
|
|
889
|
+
---
|
|
890
|
+
|
|
891
|
+
## Testing with MockMesh (v0.3.2)
|
|
892
|
+
|
|
893
|
+
Unit test your agents without real P2P infrastructure:
|
|
894
|
+
|
|
895
|
+
### Basic Setup
|
|
896
|
+
|
|
897
|
+
```python
|
|
898
|
+
import pytest
|
|
899
|
+
from jarviscore.testing import MockMesh
|
|
900
|
+
from jarviscore.profiles import CustomAgent
|
|
901
|
+
|
|
902
|
+
class MyAgent(CustomAgent):
|
|
903
|
+
role = "processor"
|
|
904
|
+
capabilities = ["processing"]
|
|
905
|
+
|
|
906
|
+
async def on_peer_request(self, msg):
|
|
907
|
+
# Delegate to analyst
|
|
908
|
+
analysis = await self.peers.request("analyst", {"data": msg.data})
|
|
909
|
+
return {"processed": True, "analysis": analysis}
|
|
910
|
+
|
|
911
|
+
@pytest.mark.asyncio
|
|
912
|
+
async def test_processor_delegates():
|
|
913
|
+
mesh = MockMesh()
|
|
914
|
+
mesh.add(MyAgent)
|
|
915
|
+
await mesh.start()
|
|
916
|
+
|
|
917
|
+
agent = mesh.get_agent("processor")
|
|
918
|
+
|
|
919
|
+
# Configure mock response for analyst
|
|
920
|
+
agent.peers.set_mock_response("analyst", {"result": "analyzed"})
|
|
921
|
+
|
|
922
|
+
# Test the agent
|
|
923
|
+
response = await agent.peers.request("analyst", {"test": "data"})
|
|
924
|
+
|
|
925
|
+
# Verify
|
|
926
|
+
assert response["result"] == "analyzed"
|
|
927
|
+
agent.peers.assert_requested("analyst")
|
|
928
|
+
|
|
929
|
+
await mesh.stop()
|
|
930
|
+
```
|
|
931
|
+
|
|
932
|
+
### MockPeerClient Features
|
|
933
|
+
|
|
934
|
+
```python
|
|
935
|
+
# Configure responses
|
|
936
|
+
agent.peers.set_mock_response("analyst", {"result": "..."})
|
|
937
|
+
agent.peers.set_default_response({"status": "ok"})
|
|
938
|
+
|
|
939
|
+
# Custom handler for dynamic responses
|
|
940
|
+
async def handler(target, message, context):
|
|
941
|
+
return {"echo": message, "target": target}
|
|
942
|
+
agent.peers.set_request_handler(handler)
|
|
943
|
+
|
|
944
|
+
# Inject messages for handler testing
|
|
945
|
+
from jarviscore.p2p.messages import MessageType
|
|
946
|
+
agent.peers.inject_message("sender", MessageType.REQUEST, {"data": "test"})
|
|
947
|
+
|
|
948
|
+
# Assertions
|
|
949
|
+
agent.peers.assert_notified("target")
|
|
950
|
+
agent.peers.assert_requested("analyst", message_contains={"query": "test"})
|
|
951
|
+
agent.peers.assert_broadcasted()
|
|
952
|
+
|
|
953
|
+
# Track what was sent
|
|
954
|
+
notifications = agent.peers.get_sent_notifications()
|
|
955
|
+
requests = agent.peers.get_sent_requests()
|
|
956
|
+
|
|
957
|
+
# Reset between tests
|
|
958
|
+
agent.peers.reset()
|
|
959
|
+
```
|
|
960
|
+
|
|
961
|
+
---
|
|
962
|
+
|
|
683
963
|
## Best Practices
|
|
684
964
|
|
|
685
965
|
### 1. Always Use Context Managers
|
|
@@ -905,6 +1185,6 @@ mesh = Mesh(config=config)
|
|
|
905
1185
|
|
|
906
1186
|
## Version
|
|
907
1187
|
|
|
908
|
-
User Guide for JarvisCore v0.3.
|
|
1188
|
+
User Guide for JarvisCore v0.3.2
|
|
909
1189
|
|
|
910
|
-
Last Updated: 2026-
|
|
1190
|
+
Last Updated: 2026-02-03
|
|
@@ -49,9 +49,9 @@ class JarvisLifespan:
|
|
|
49
49
|
Example - Single Agent:
|
|
50
50
|
from fastapi import FastAPI, Request
|
|
51
51
|
from jarviscore.integrations.fastapi import JarvisLifespan
|
|
52
|
-
from jarviscore.profiles import
|
|
52
|
+
from jarviscore.profiles import CustomAgent
|
|
53
53
|
|
|
54
|
-
class MyAgent(
|
|
54
|
+
class MyAgent(CustomAgent):
|
|
55
55
|
role = "processor"
|
|
56
56
|
capabilities = ["processing"]
|
|
57
57
|
|
|
@@ -220,9 +220,9 @@ def create_jarvis_app(
|
|
|
220
220
|
|
|
221
221
|
Example:
|
|
222
222
|
from jarviscore.integrations.fastapi import create_jarvis_app
|
|
223
|
-
from jarviscore.profiles import
|
|
223
|
+
from jarviscore.profiles import CustomAgent
|
|
224
224
|
|
|
225
|
-
class MyAgent(
|
|
225
|
+
class MyAgent(CustomAgent):
|
|
226
226
|
role = "processor"
|
|
227
227
|
capabilities = ["processing"]
|
|
228
228
|
|
jarviscore/p2p/coordinator.py
CHANGED
|
@@ -702,7 +702,8 @@ class P2PCoordinator:
|
|
|
702
702
|
type=MessageType.NOTIFY,
|
|
703
703
|
data=payload.get('data', {}),
|
|
704
704
|
correlation_id=payload.get('correlation_id'),
|
|
705
|
-
timestamp=payload.get('timestamp', 0)
|
|
705
|
+
timestamp=payload.get('timestamp', 0),
|
|
706
|
+
context=payload.get('context')
|
|
706
707
|
)
|
|
707
708
|
|
|
708
709
|
await target_client._deliver_message(incoming)
|
|
@@ -714,15 +715,34 @@ class P2PCoordinator:
|
|
|
714
715
|
async def _handle_peer_request(self, sender, message):
|
|
715
716
|
"""Handle peer request message (expects response)."""
|
|
716
717
|
try:
|
|
717
|
-
|
|
718
|
+
logger.info(f"[COORDINATOR] Received PEER_REQUEST from {sender}")
|
|
719
|
+
|
|
720
|
+
# Parse payload - it comes as JSON string in message['payload']
|
|
721
|
+
import json
|
|
722
|
+
payload_raw = message.get('payload', {})
|
|
723
|
+
if isinstance(payload_raw, str):
|
|
724
|
+
try:
|
|
725
|
+
payload = json.loads(payload_raw)
|
|
726
|
+
logger.info(f"[COORDINATOR] Parsed JSON payload")
|
|
727
|
+
except json.JSONDecodeError as e:
|
|
728
|
+
logger.error(f"[COORDINATOR] Failed to parse payload JSON: {e}")
|
|
729
|
+
return
|
|
730
|
+
else:
|
|
731
|
+
payload = payload_raw
|
|
732
|
+
|
|
718
733
|
target = payload.get('target')
|
|
734
|
+
logger.info(f"[COORDINATOR] Target: {target}, Payload keys: {list(payload.keys())}")
|
|
719
735
|
|
|
720
736
|
# Find target agent's PeerClient
|
|
721
737
|
target_client = self._find_peer_client_by_role_or_id(target)
|
|
722
738
|
if not target_client:
|
|
723
739
|
logger.warning(f"Peer request: target '{target}' not found")
|
|
740
|
+
logger.warning(f"Available agents: {[a.agent_id for a in self.agents]}")
|
|
741
|
+
logger.warning(f"Available peer clients: {list(self._agent_peer_clients.keys())}")
|
|
724
742
|
return
|
|
725
743
|
|
|
744
|
+
logger.info(f"[COORDINATOR] Found target_client for {target}, delivering message...")
|
|
745
|
+
|
|
726
746
|
# Create incoming message and deliver
|
|
727
747
|
incoming = IncomingMessage(
|
|
728
748
|
sender=payload.get('sender', sender),
|
|
@@ -730,19 +750,27 @@ class P2PCoordinator:
|
|
|
730
750
|
type=MessageType.REQUEST,
|
|
731
751
|
data=payload.get('data', {}),
|
|
732
752
|
correlation_id=payload.get('correlation_id'),
|
|
733
|
-
timestamp=payload.get('timestamp', 0)
|
|
753
|
+
timestamp=payload.get('timestamp', 0),
|
|
754
|
+
context=payload.get('context')
|
|
734
755
|
)
|
|
735
756
|
|
|
736
757
|
await target_client._deliver_message(incoming)
|
|
737
|
-
logger.
|
|
758
|
+
logger.info(f"[COORDINATOR] Delivered peer request to {target}")
|
|
738
759
|
|
|
739
760
|
except Exception as e:
|
|
740
|
-
logger.error(f"Error handling peer request: {e}")
|
|
761
|
+
logger.error(f"Error handling peer request: {e}", exc_info=True)
|
|
741
762
|
|
|
742
763
|
async def _handle_peer_response(self, sender, message):
|
|
743
764
|
"""Handle peer response message."""
|
|
744
765
|
try:
|
|
745
|
-
|
|
766
|
+
# Parse payload - it comes as JSON string
|
|
767
|
+
import json
|
|
768
|
+
payload_raw = message.get('payload', {})
|
|
769
|
+
if isinstance(payload_raw, str):
|
|
770
|
+
payload = json.loads(payload_raw)
|
|
771
|
+
else:
|
|
772
|
+
payload = payload_raw
|
|
773
|
+
|
|
746
774
|
target = payload.get('target')
|
|
747
775
|
|
|
748
776
|
# Find target agent's PeerClient
|
|
@@ -758,7 +786,8 @@ class P2PCoordinator:
|
|
|
758
786
|
type=MessageType.RESPONSE,
|
|
759
787
|
data=payload.get('data', {}),
|
|
760
788
|
correlation_id=payload.get('correlation_id'),
|
|
761
|
-
timestamp=payload.get('timestamp', 0)
|
|
789
|
+
timestamp=payload.get('timestamp', 0),
|
|
790
|
+
context=payload.get('context')
|
|
762
791
|
)
|
|
763
792
|
|
|
764
793
|
await target_client._deliver_message(incoming)
|
jarviscore/p2p/messages.py
CHANGED
|
@@ -52,6 +52,7 @@ class IncomingMessage:
|
|
|
52
52
|
data: Message payload
|
|
53
53
|
correlation_id: ID linking request to response (for request-response pattern)
|
|
54
54
|
timestamp: When the message was sent
|
|
55
|
+
context: Optional metadata for the message (mission_id, priority, trace_id, etc.)
|
|
55
56
|
"""
|
|
56
57
|
sender: str
|
|
57
58
|
sender_node: str
|
|
@@ -59,6 +60,7 @@ class IncomingMessage:
|
|
|
59
60
|
data: Dict[str, Any]
|
|
60
61
|
correlation_id: Optional[str] = None
|
|
61
62
|
timestamp: float = field(default_factory=time.time)
|
|
63
|
+
context: Optional[Dict[str, Any]] = None
|
|
62
64
|
|
|
63
65
|
@property
|
|
64
66
|
def is_request(self) -> bool:
|
|
@@ -77,6 +79,16 @@ class OutgoingMessage:
|
|
|
77
79
|
A message to be sent to a peer agent.
|
|
78
80
|
|
|
79
81
|
Used internally by PeerClient for message construction.
|
|
82
|
+
|
|
83
|
+
Attributes:
|
|
84
|
+
target: Target agent role or ID
|
|
85
|
+
type: Message type
|
|
86
|
+
data: Message payload
|
|
87
|
+
correlation_id: ID linking request to response
|
|
88
|
+
timestamp: When the message was created
|
|
89
|
+
sender: Agent ID of sender (filled by PeerClient)
|
|
90
|
+
sender_node: P2P node ID of sender (filled by PeerClient)
|
|
91
|
+
context: Optional metadata for the message (mission_id, priority, trace_id, etc.)
|
|
80
92
|
"""
|
|
81
93
|
target: str # Target agent role or ID
|
|
82
94
|
type: MessageType
|
|
@@ -85,3 +97,4 @@ class OutgoingMessage:
|
|
|
85
97
|
timestamp: float = field(default_factory=time.time)
|
|
86
98
|
sender: str = "" # Filled in by PeerClient
|
|
87
99
|
sender_node: str = "" # Filled in by PeerClient
|
|
100
|
+
context: Optional[Dict[str, Any]] = None
|