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
@@ -1,292 +0,0 @@
1
- """
2
- ListenerAgent - Agent profile for API-first services with secondary P2P.
3
-
4
- For agents where HTTP API is primary and P2P listening is background
5
- functionality. Abstracts away the message loop - developers just
6
- implement handlers.
7
-
8
- Example:
9
- class MyAPIAgent(ListenerAgent):
10
- role = "api_processor"
11
- capabilities = ["data_processing"]
12
-
13
- async def on_peer_request(self, msg):
14
- result = await self.process(msg.data)
15
- return {"status": "success", "result": result}
16
-
17
- async def on_peer_notify(self, msg):
18
- await self.log_event(msg.data)
19
- """
20
- from abc import abstractmethod
21
- from typing import Any, Optional, Dict
22
- import asyncio
23
- import logging
24
-
25
- from .customagent import CustomAgent
26
-
27
- logger = logging.getLogger(__name__)
28
-
29
-
30
- class ListenerAgent(CustomAgent):
31
- """
32
- Agent that listens for peer messages without requiring a custom run() loop.
33
-
34
- Designed for API-first agents where:
35
- - The HTTP server (FastAPI, etc.) is the primary interface
36
- - P2P mesh participation is secondary/background functionality
37
- - You just want to handle incoming peer messages without loop boilerplate
38
-
39
- Instead of writing a run() loop, implement message handlers:
40
- - on_peer_request(msg) - Handle request-response messages (return value sent back)
41
- - on_peer_notify(msg) - Handle fire-and-forget notifications
42
-
43
- Configuration Attributes:
44
- listen_timeout: Seconds to wait for messages before checking shutdown (default: 1.0)
45
- auto_respond: Automatically send on_peer_request return value as response (default: True)
46
-
47
- Example - Basic Usage:
48
- class MyAPIAgent(ListenerAgent):
49
- role = "api_processor"
50
- capabilities = ["processing"]
51
-
52
- async def on_peer_request(self, msg):
53
- # Handle incoming requests from other agents
54
- result = await self.process(msg.data)
55
- return {"status": "success", "result": result}
56
-
57
- async def on_peer_notify(self, msg):
58
- # Handle fire-and-forget notifications
59
- await self.log_event(msg.data)
60
-
61
- Example - With FastAPI:
62
- from fastapi import FastAPI
63
- from jarviscore.integrations.fastapi import JarvisLifespan
64
- from jarviscore.profiles import ListenerAgent
65
-
66
- class ProcessorAgent(ListenerAgent):
67
- role = "processor"
68
- capabilities = ["data_processing"]
69
-
70
- async def on_peer_request(self, msg):
71
- if msg.data.get("action") == "process":
72
- return {"result": await self.process(msg.data["payload"])}
73
- return {"error": "unknown action"}
74
-
75
- agent = ProcessorAgent()
76
- app = FastAPI(lifespan=JarvisLifespan(agent, mode="p2p"))
77
-
78
- @app.post("/process")
79
- async def process_endpoint(data: dict, request: Request):
80
- # HTTP endpoint - primary interface
81
- agent = request.app.state.jarvis_agents["processor"]
82
- return await agent.process(data)
83
- """
84
-
85
- # Configuration - can be overridden in subclasses
86
- listen_timeout: float = 1.0 # Seconds to wait for messages
87
- auto_respond: bool = True # Automatically send response for requests
88
-
89
- async def run(self):
90
- """
91
- Default listener loop - handles peer messages automatically.
92
-
93
- Runs in background, dispatches incoming messages to:
94
- - on_peer_request() for request-response messages
95
- - on_peer_notify() for fire-and-forget notifications
96
-
97
- You typically don't need to override this. Just implement the handlers.
98
- """
99
- self._logger.info(f"[{self.role}] Listener loop started")
100
-
101
- while not self.shutdown_requested:
102
- try:
103
- # Wait for incoming message with timeout
104
- # Timeout allows periodic shutdown_requested checks
105
- msg = await self.peers.receive(timeout=self.listen_timeout)
106
-
107
- if msg is None:
108
- # Timeout - no message, continue loop to check shutdown
109
- continue
110
-
111
- # Dispatch to appropriate handler
112
- await self._dispatch_message(msg)
113
-
114
- except asyncio.CancelledError:
115
- self._logger.debug(f"[{self.role}] Listener loop cancelled")
116
- raise
117
- except Exception as e:
118
- self._logger.error(f"[{self.role}] Listener loop error: {e}")
119
- await self.on_error(e, None)
120
-
121
- self._logger.info(f"[{self.role}] Listener loop stopped")
122
-
123
- async def _dispatch_message(self, msg):
124
- """
125
- Dispatch message to appropriate handler based on message type.
126
-
127
- Handles:
128
- - REQUEST messages: calls on_peer_request, sends response if auto_respond=True
129
- - NOTIFY messages: calls on_peer_notify
130
- """
131
- from jarviscore.p2p.messages import MessageType
132
-
133
- try:
134
- # Check if this is a request (expects response)
135
- is_request = (
136
- msg.type == MessageType.REQUEST or
137
- getattr(msg, 'is_request', False) or
138
- msg.correlation_id is not None
139
- )
140
-
141
- if is_request:
142
- # Request-response: call handler, optionally send response
143
- response = await self.on_peer_request(msg)
144
-
145
- if self.auto_respond and response is not None:
146
- await self.peers.respond(msg, response)
147
- self._logger.debug(
148
- f"[{self.role}] Sent response to {msg.sender}"
149
- )
150
- else:
151
- # Notification: fire-and-forget
152
- await self.on_peer_notify(msg)
153
-
154
- except Exception as e:
155
- self._logger.error(
156
- f"[{self.role}] Error handling message from {msg.sender}: {e}"
157
- )
158
- await self.on_error(e, msg)
159
-
160
- # ─────────────────────────────────────────────────────────────────
161
- # Override these methods in your agent
162
- # ─────────────────────────────────────────────────────────────────
163
-
164
- @abstractmethod
165
- async def on_peer_request(self, msg) -> Any:
166
- """
167
- Handle incoming peer request.
168
-
169
- Override this to process request-response messages from other agents.
170
- The return value is automatically sent as response (if auto_respond=True).
171
-
172
- Args:
173
- msg: IncomingMessage with:
174
- - msg.sender: Sender agent ID or role
175
- - msg.data: Request payload (dict)
176
- - msg.correlation_id: For response matching (handled automatically)
177
-
178
- Returns:
179
- Response data (dict) to send back to the requester.
180
- Return None to skip sending a response.
181
-
182
- Example:
183
- async def on_peer_request(self, msg):
184
- action = msg.data.get("action")
185
-
186
- if action == "analyze":
187
- result = await self.analyze(msg.data["payload"])
188
- return {"status": "success", "result": result}
189
-
190
- elif action == "status":
191
- return {"status": "ok", "queue_size": self.queue_size}
192
-
193
- return {"status": "error", "message": f"Unknown action: {action}"}
194
- """
195
- pass
196
-
197
- async def on_peer_notify(self, msg) -> None:
198
- """
199
- Handle incoming peer notification.
200
-
201
- Override this to process fire-and-forget messages from other agents.
202
- No response is expected or sent.
203
-
204
- Args:
205
- msg: IncomingMessage with:
206
- - msg.sender: Sender agent ID or role
207
- - msg.data: Notification payload (dict)
208
-
209
- Example:
210
- async def on_peer_notify(self, msg):
211
- event = msg.data.get("event")
212
-
213
- if event == "task_complete":
214
- await self.update_dashboard(msg.data)
215
- self._logger.info(f"Task completed by {msg.sender}")
216
-
217
- elif event == "peer_joined":
218
- self._logger.info(f"New peer in mesh: {msg.data.get('role')}")
219
- """
220
- # Default: log and ignore
221
- self._logger.debug(
222
- f"[{self.role}] Received notify from {msg.sender}: "
223
- f"{list(msg.data.keys()) if isinstance(msg.data, dict) else 'data'}"
224
- )
225
-
226
- async def on_error(self, error: Exception, msg=None) -> None:
227
- """
228
- Handle errors during message processing.
229
-
230
- Override to customize error handling (logging, alerting, metrics, etc.)
231
- Default implementation logs the error and continues processing.
232
-
233
- Args:
234
- error: The exception that occurred
235
- msg: The message being processed when error occurred (may be None)
236
-
237
- Example:
238
- async def on_error(self, error, msg):
239
- # Log with context
240
- self._logger.error(
241
- f"Error processing message: {error}",
242
- extra={"sender": msg.sender if msg else None}
243
- )
244
-
245
- # Send to error tracking service
246
- await self.error_tracker.capture(error, context={"msg": msg})
247
-
248
- # Optionally notify the sender of failure
249
- if msg and msg.correlation_id:
250
- await self.peers.respond(msg, {
251
- "status": "error",
252
- "error": str(error)
253
- })
254
- """
255
- if msg:
256
- self._logger.error(
257
- f"[{self.role}] Error processing message from {msg.sender}: {error}"
258
- )
259
- else:
260
- self._logger.error(f"[{self.role}] Error in listener loop: {error}")
261
-
262
- # ─────────────────────────────────────────────────────────────────
263
- # Workflow compatibility
264
- # ─────────────────────────────────────────────────────────────────
265
-
266
- async def execute_task(self, task: Dict[str, Any]) -> Dict[str, Any]:
267
- """
268
- Execute a task (for workflow/distributed modes).
269
-
270
- Delegates to on_peer_request for consistency, allowing the same
271
- agent to work in both P2P and workflow modes.
272
-
273
- Args:
274
- task: Task specification dict
275
-
276
- Returns:
277
- Result dict with status and output
278
- """
279
- from jarviscore.p2p.messages import IncomingMessage, MessageType
280
-
281
- # Create a synthetic message to pass to the handler
282
- synthetic_msg = IncomingMessage(
283
- sender="workflow",
284
- sender_node="local",
285
- type=MessageType.REQUEST,
286
- data=task,
287
- correlation_id=None,
288
- timestamp=0
289
- )
290
-
291
- result = await self.on_peer_request(synthetic_msg)
292
- return {"status": "success", "output": result}