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
|
@@ -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}
|
|
File without changes
|
{jarviscore_framework-0.3.0.dist-info → jarviscore_framework-0.3.2.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|
|
File without changes
|