jarviscore-framework 0.3.1__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 (31) hide show
  1. examples/customagent_cognitive_discovery_example.py +49 -8
  2. examples/customagent_distributed_example.py +140 -1
  3. examples/fastapi_integration_example.py +70 -7
  4. jarviscore/__init__.py +1 -1
  5. jarviscore/core/mesh.py +149 -0
  6. jarviscore/data/examples/customagent_cognitive_discovery_example.py +49 -8
  7. jarviscore/data/examples/customagent_distributed_example.py +140 -1
  8. jarviscore/data/examples/fastapi_integration_example.py +70 -7
  9. jarviscore/docs/API_REFERENCE.md +547 -5
  10. jarviscore/docs/CHANGELOG.md +89 -0
  11. jarviscore/docs/CONFIGURATION.md +1 -1
  12. jarviscore/docs/CUSTOMAGENT_GUIDE.md +347 -2
  13. jarviscore/docs/TROUBLESHOOTING.md +1 -1
  14. jarviscore/docs/USER_GUIDE.md +286 -5
  15. jarviscore/p2p/coordinator.py +36 -7
  16. jarviscore/p2p/messages.py +13 -0
  17. jarviscore/p2p/peer_client.py +355 -23
  18. jarviscore/p2p/peer_tool.py +17 -11
  19. jarviscore/profiles/customagent.py +9 -2
  20. jarviscore/testing/__init__.py +35 -0
  21. jarviscore/testing/mocks.py +578 -0
  22. {jarviscore_framework-0.3.1.dist-info → jarviscore_framework-0.3.2.dist-info}/METADATA +2 -2
  23. {jarviscore_framework-0.3.1.dist-info → jarviscore_framework-0.3.2.dist-info}/RECORD +31 -24
  24. tests/test_17_session_context.py +489 -0
  25. tests/test_18_mesh_diagnostics.py +465 -0
  26. tests/test_19_async_requests.py +516 -0
  27. tests/test_20_load_balancing.py +546 -0
  28. tests/test_21_mock_testing.py +776 -0
  29. {jarviscore_framework-0.3.1.dist-info → jarviscore_framework-0.3.2.dist-info}/WHEEL +0 -0
  30. {jarviscore_framework-0.3.1.dist-info → jarviscore_framework-0.3.2.dist-info}/licenses/LICENSE +0 -0
  31. {jarviscore_framework-0.3.1.dist-info → jarviscore_framework-0.3.2.dist-info}/top_level.txt +0 -0
@@ -21,6 +21,11 @@ CustomAgent lets you integrate your **existing agent code** with JarvisCore's ne
21
21
  10. [Multi-Node Deployment](#multi-node-deployment)
22
22
  11. [Error Handling](#error-handling)
23
23
  12. [Troubleshooting](#troubleshooting)
24
+ 13. [Session Context Propagation (v0.3.2)](#session-context-propagation-v032) - Request tracking and metadata
25
+ 14. [Async Request Pattern (v0.3.2)](#async-request-pattern-v032) - Non-blocking parallel requests
26
+ 15. [Load Balancing Strategies (v0.3.2)](#load-balancing-strategies-v032) - Round-robin and random selection
27
+ 16. [Mesh Diagnostics (v0.3.2)](#mesh-diagnostics-v032) - Health monitoring and debugging
28
+ 17. [Testing with MockMesh (v0.3.2)](#testing-with-mockmesh-v032) - Unit testing patterns
24
29
 
25
30
  ---
26
31
 
@@ -1926,6 +1931,346 @@ if agent.peers:
1926
1931
 
1927
1932
  ---
1928
1933
 
1934
+ ## Session Context Propagation (v0.3.2)
1935
+
1936
+ Pass metadata (mission IDs, trace IDs, priorities) through message flows:
1937
+
1938
+ ### Sending Context
1939
+
1940
+ ```python
1941
+ # All messaging methods accept context parameter
1942
+ await self.peers.notify("logger", {"event": "started"},
1943
+ context={"mission_id": "m-123", "trace_id": "t-abc"})
1944
+
1945
+ response = await self.peers.request("analyst", {"query": "..."},
1946
+ context={"priority": "high", "user_id": "u-456"})
1947
+
1948
+ await self.peers.broadcast({"alert": "ready"},
1949
+ context={"source": "coordinator"})
1950
+ ```
1951
+
1952
+ ### Receiving Context
1953
+
1954
+ ```python
1955
+ async def on_peer_request(self, msg):
1956
+ # Context is available on the message
1957
+ mission_id = msg.context.get("mission_id") if msg.context else None
1958
+ trace_id = msg.context.get("trace_id") if msg.context else None
1959
+
1960
+ self._logger.info(f"Request for mission {mission_id}, trace {trace_id}")
1961
+
1962
+ return {"result": "processed"}
1963
+ ```
1964
+
1965
+ ### Auto-Propagation in respond()
1966
+
1967
+ Context automatically propagates from request to response:
1968
+
1969
+ ```python
1970
+ async def on_peer_request(self, msg):
1971
+ # msg.context = {"mission_id": "m-123", "trace_id": "t-abc"}
1972
+ result = await self.process(msg.data)
1973
+
1974
+ # Context auto-propagates - original sender receives same context
1975
+ await self.peers.respond(msg, {"result": result})
1976
+
1977
+ # Override if needed
1978
+ await self.peers.respond(msg, {"result": result},
1979
+ context={"status": "completed", "mission_id": msg.context.get("mission_id")})
1980
+ ```
1981
+
1982
+ ---
1983
+
1984
+ ## Async Request Pattern (v0.3.2)
1985
+
1986
+ Fire multiple requests without blocking, collect responses later:
1987
+
1988
+ ### Fire-and-Collect Pattern
1989
+
1990
+ ```python
1991
+ async def parallel_analysis(self, data_chunks):
1992
+ # Fire off requests to all available analysts
1993
+ analysts = self.peers.discover(role="analyst")
1994
+ request_ids = []
1995
+
1996
+ for i, (analyst, chunk) in enumerate(zip(analysts, data_chunks)):
1997
+ req_id = await self.peers.ask_async(
1998
+ analyst.agent_id,
1999
+ {"chunk_id": i, "data": chunk},
2000
+ context={"batch_id": "batch-001"}
2001
+ )
2002
+ request_ids.append((req_id, analyst.agent_id))
2003
+
2004
+ # Do other work while analysts process
2005
+ await self.update_status("processing")
2006
+
2007
+ # Collect results
2008
+ results = []
2009
+ for req_id, analyst_id in request_ids:
2010
+ response = await self.peers.check_inbox(req_id, timeout=30)
2011
+ if response:
2012
+ results.append(response)
2013
+ else:
2014
+ self._logger.warning(f"Timeout waiting for {analyst_id}")
2015
+
2016
+ return results
2017
+ ```
2018
+
2019
+ ### API Methods
2020
+
2021
+ ```python
2022
+ # Fire async request - returns immediately with request_id
2023
+ req_id = await self.peers.ask_async(target, message, timeout=120, context=None)
2024
+
2025
+ # Check for response
2026
+ response = await self.peers.check_inbox(req_id, timeout=0) # Non-blocking
2027
+ response = await self.peers.check_inbox(req_id, timeout=10) # Wait up to 10s
2028
+
2029
+ # Manage pending requests
2030
+ pending = self.peers.get_pending_async_requests()
2031
+ self.peers.clear_inbox(req_id) # Clear specific
2032
+ self.peers.clear_inbox() # Clear all
2033
+ ```
2034
+
2035
+ ---
2036
+
2037
+ ## Load Balancing Strategies (v0.3.2)
2038
+
2039
+ Distribute work across multiple peers:
2040
+
2041
+ ### Discovery Strategies
2042
+
2043
+ ```python
2044
+ # Default: first in discovery order (deterministic)
2045
+ workers = self.peers.discover(role="worker", strategy="first")
2046
+
2047
+ # Random: shuffle for basic distribution
2048
+ workers = self.peers.discover(role="worker", strategy="random")
2049
+
2050
+ # Round-robin: rotate through workers on each call
2051
+ workers = self.peers.discover(role="worker", strategy="round_robin")
2052
+
2053
+ # Least-recent: prefer workers not used recently
2054
+ workers = self.peers.discover(role="worker", strategy="least_recent")
2055
+ ```
2056
+
2057
+ ### discover_one() Convenience
2058
+
2059
+ ```python
2060
+ # Get single peer with strategy applied
2061
+ worker = self.peers.discover_one(role="worker", strategy="round_robin")
2062
+ if worker:
2063
+ response = await self.peers.request(worker.agent_id, {"task": "..."})
2064
+ ```
2065
+
2066
+ ### Tracking Usage for least_recent
2067
+
2068
+ ```python
2069
+ # Track usage to influence least_recent ordering
2070
+ worker = self.peers.discover_one(role="worker", strategy="least_recent")
2071
+ response = await self.peers.request(worker.agent_id, {"task": "..."})
2072
+ self.peers.record_peer_usage(worker.agent_id) # Update timestamp
2073
+ ```
2074
+
2075
+ ### Example: Load-Balanced Task Distribution
2076
+
2077
+ ```python
2078
+ class Coordinator(CustomAgent):
2079
+ role = "coordinator"
2080
+ capabilities = ["coordination"]
2081
+
2082
+ async def distribute_work(self, tasks):
2083
+ results = []
2084
+ for task in tasks:
2085
+ # Round-robin automatically rotates through workers
2086
+ worker = self.peers.discover_one(
2087
+ capability="processing",
2088
+ strategy="round_robin"
2089
+ )
2090
+ if worker:
2091
+ response = await self.peers.request(
2092
+ worker.agent_id,
2093
+ {"task": task}
2094
+ )
2095
+ results.append(response)
2096
+ return results
2097
+ ```
2098
+
2099
+ ---
2100
+
2101
+ ## Mesh Diagnostics (v0.3.2)
2102
+
2103
+ Monitor mesh health for debugging and operations:
2104
+
2105
+ ### Getting Diagnostics
2106
+
2107
+ ```python
2108
+ # From mesh
2109
+ diag = mesh.get_diagnostics()
2110
+
2111
+ # Structure:
2112
+ # {
2113
+ # "local_node": {
2114
+ # "mode": "p2p",
2115
+ # "started": True,
2116
+ # "agent_count": 3,
2117
+ # "bind_address": "127.0.0.1:7950"
2118
+ # },
2119
+ # "known_peers": [
2120
+ # {"role": "analyst", "node_id": "10.0.0.2:7950", "status": "alive"}
2121
+ # ],
2122
+ # "local_agents": [
2123
+ # {"role": "coordinator", "agent_id": "...", "capabilities": [...]}
2124
+ # ],
2125
+ # "connectivity_status": "healthy"
2126
+ # }
2127
+ ```
2128
+
2129
+ ### Connectivity Status
2130
+
2131
+ | Status | Meaning |
2132
+ |--------|---------|
2133
+ | `healthy` | P2P active, peers connected |
2134
+ | `isolated` | P2P active, no peers found |
2135
+ | `degraded` | Some connectivity issues |
2136
+ | `not_started` | Mesh not started yet |
2137
+ | `local_only` | Autonomous mode (no P2P) |
2138
+
2139
+ ### FastAPI Health Endpoint
2140
+
2141
+ ```python
2142
+ from fastapi import FastAPI, Request
2143
+ from jarviscore.integrations.fastapi import JarvisLifespan
2144
+
2145
+ app = FastAPI(lifespan=JarvisLifespan(agent, mode="p2p"))
2146
+
2147
+ @app.get("/health")
2148
+ async def health(request: Request):
2149
+ mesh = request.app.state.jarvis_mesh
2150
+ diag = mesh.get_diagnostics()
2151
+ return {
2152
+ "status": diag["connectivity_status"],
2153
+ "agents": diag["local_node"]["agent_count"],
2154
+ "peers": len(diag.get("known_peers", []))
2155
+ }
2156
+ ```
2157
+
2158
+ ---
2159
+
2160
+ ## Testing with MockMesh (v0.3.2)
2161
+
2162
+ Unit test agents without real P2P infrastructure:
2163
+
2164
+ ### Basic Test Setup
2165
+
2166
+ ```python
2167
+ import pytest
2168
+ from jarviscore.testing import MockMesh, MockPeerClient
2169
+ from jarviscore.profiles import CustomAgent
2170
+
2171
+ class AnalystAgent(CustomAgent):
2172
+ role = "analyst"
2173
+ capabilities = ["analysis"]
2174
+
2175
+ async def on_peer_request(self, msg):
2176
+ return {"analysis": f"Analyzed: {msg.data.get('query')}"}
2177
+
2178
+ @pytest.mark.asyncio
2179
+ async def test_analyst_responds():
2180
+ mesh = MockMesh()
2181
+ mesh.add(AnalystAgent)
2182
+ await mesh.start()
2183
+
2184
+ analyst = mesh.get_agent("analyst")
2185
+
2186
+ # Inject a test message
2187
+ from jarviscore.p2p.messages import MessageType
2188
+ analyst.peers.inject_message(
2189
+ sender="tester",
2190
+ message_type=MessageType.REQUEST,
2191
+ data={"query": "test data"},
2192
+ correlation_id="test-123"
2193
+ )
2194
+
2195
+ # Receive and verify
2196
+ msg = await analyst.peers.receive(timeout=1)
2197
+ assert msg is not None
2198
+ assert msg.data["query"] == "test data"
2199
+
2200
+ await mesh.stop()
2201
+ ```
2202
+
2203
+ ### Mocking Peer Responses
2204
+
2205
+ ```python
2206
+ @pytest.mark.asyncio
2207
+ async def test_coordinator_delegates():
2208
+ class CoordinatorAgent(CustomAgent):
2209
+ role = "coordinator"
2210
+ capabilities = ["coordination"]
2211
+
2212
+ async def on_peer_request(self, msg):
2213
+ # This agent delegates to analyst
2214
+ analysis = await self.peers.request("analyst", {"data": msg.data})
2215
+ return {"coordinated": True, "analysis": analysis}
2216
+
2217
+ mesh = MockMesh()
2218
+ mesh.add(CoordinatorAgent)
2219
+ await mesh.start()
2220
+
2221
+ coordinator = mesh.get_agent("coordinator")
2222
+
2223
+ # Mock the analyst response
2224
+ coordinator.peers.add_mock_peer("analyst", capabilities=["analysis"])
2225
+ coordinator.peers.set_mock_response("analyst", {"result": "mocked analysis"})
2226
+
2227
+ # Test the flow
2228
+ response = await coordinator.peers.request("analyst", {"test": "data"})
2229
+
2230
+ assert response["result"] == "mocked analysis"
2231
+ coordinator.peers.assert_requested("analyst")
2232
+
2233
+ await mesh.stop()
2234
+ ```
2235
+
2236
+ ### Assertion Helpers
2237
+
2238
+ ```python
2239
+ # Verify notifications were sent
2240
+ agent.peers.assert_notified("target_role")
2241
+ agent.peers.assert_notified("target", message_contains={"event": "completed"})
2242
+
2243
+ # Verify requests were sent
2244
+ agent.peers.assert_requested("analyst")
2245
+ agent.peers.assert_requested("analyst", message_contains={"query": "test"})
2246
+
2247
+ # Verify broadcasts
2248
+ agent.peers.assert_broadcasted()
2249
+ agent.peers.assert_broadcasted(message_contains={"alert": "important"})
2250
+
2251
+ # Access sent messages for custom assertions
2252
+ notifications = agent.peers.get_sent_notifications()
2253
+ requests = agent.peers.get_sent_requests()
2254
+ broadcasts = agent.peers.get_sent_broadcasts()
2255
+
2256
+ # Reset between tests
2257
+ agent.peers.reset()
2258
+ ```
2259
+
2260
+ ### Custom Response Handler
2261
+
2262
+ ```python
2263
+ async def dynamic_handler(target, message, context):
2264
+ """Return different responses based on message content."""
2265
+ if "urgent" in message.get("query", ""):
2266
+ return {"priority": "high", "result": "fast response"}
2267
+ return {"priority": "normal", "result": "standard response"}
2268
+
2269
+ agent.peers.set_request_handler(dynamic_handler)
2270
+ ```
2271
+
2272
+ ---
2273
+
1929
2274
  ## API Reference
1930
2275
 
1931
2276
  ### CustomAgent Class Attributes
@@ -2265,10 +2610,10 @@ For complete, runnable examples, see:
2265
2610
 
2266
2611
  - `examples/customagent_p2p_example.py` - P2P mode with LLM-driven peer communication
2267
2612
  - `examples/customagent_distributed_example.py` - Distributed mode with workflows
2268
- - `examples/customagent_cognitive_discovery_example.py` - CustomAgent + cognitive discovery (v0.4.0)
2613
+ - `examples/customagent_cognitive_discovery_example.py` - CustomAgent + cognitive discovery (v0.3.0)
2269
2614
  - `examples/fastapi_integration_example.py` - FastAPI + JarvisLifespan (v0.3.0)
2270
2615
  - `examples/cloud_deployment_example.py` - Self-registration with join_mesh (v0.3.0)
2271
2616
 
2272
2617
  ---
2273
2618
 
2274
- *CustomAgent Guide - JarvisCore Framework v0.3.1*
2619
+ *CustomAgent Guide - JarvisCore Framework v0.3.2*
@@ -563,4 +563,4 @@ If significantly slower:
563
563
 
564
564
  ## Version
565
565
 
566
- Troubleshooting Guide for JarvisCore v0.3.1
566
+ Troubleshooting Guide for JarvisCore v0.3.2
@@ -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. [Best Practices](#best-practices)
23
- 15. [Common Patterns](#common-patterns)
24
- 16. [Troubleshooting](#troubleshooting)
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
 
@@ -679,6 +684,282 @@ Use the `ask_peer` tool to delegate tasks to these specialists.
679
684
 
680
685
  ---
681
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
+
682
963
  ## Best Practices
683
964
 
684
965
  ### 1. Always Use Context Managers
@@ -904,6 +1185,6 @@ mesh = Mesh(config=config)
904
1185
 
905
1186
  ## Version
906
1187
 
907
- User Guide for JarvisCore v0.3.1
1188
+ User Guide for JarvisCore v0.3.2
908
1189
 
909
- Last Updated: 2026-02-02
1190
+ Last Updated: 2026-02-03