jarviscore-framework 0.1.1__py3-none-any.whl → 0.2.1__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/autoagent_distributed_example.py +211 -0
- examples/custom_profile_decorator.py +134 -0
- examples/custom_profile_wrap.py +168 -0
- examples/customagent_distributed_example.py +362 -0
- examples/customagent_p2p_example.py +730 -0
- jarviscore/__init__.py +49 -36
- jarviscore/adapter/__init__.py +15 -9
- jarviscore/adapter/decorator.py +23 -19
- jarviscore/adapter/wrapper.py +303 -0
- jarviscore/cli/scaffold.py +1 -1
- jarviscore/cli/smoketest.py +3 -2
- jarviscore/core/agent.py +44 -1
- jarviscore/core/mesh.py +196 -35
- jarviscore/data/examples/autoagent_distributed_example.py +211 -0
- jarviscore/data/examples/customagent_distributed_example.py +362 -0
- jarviscore/data/examples/customagent_p2p_example.py +730 -0
- jarviscore/docs/API_REFERENCE.md +264 -51
- jarviscore/docs/AUTOAGENT_GUIDE.md +198 -0
- jarviscore/docs/CONFIGURATION.md +35 -21
- jarviscore/docs/CUSTOMAGENT_GUIDE.md +1362 -0
- jarviscore/docs/GETTING_STARTED.md +107 -14
- jarviscore/docs/TROUBLESHOOTING.md +145 -7
- jarviscore/docs/USER_GUIDE.md +138 -361
- jarviscore/orchestration/engine.py +20 -8
- jarviscore/p2p/__init__.py +10 -0
- jarviscore/p2p/coordinator.py +129 -0
- jarviscore/p2p/messages.py +87 -0
- jarviscore/p2p/peer_client.py +576 -0
- jarviscore/p2p/peer_tool.py +268 -0
- jarviscore_framework-0.2.1.dist-info/METADATA +144 -0
- jarviscore_framework-0.2.1.dist-info/RECORD +132 -0
- {jarviscore_framework-0.1.1.dist-info → jarviscore_framework-0.2.1.dist-info}/WHEEL +1 -1
- {jarviscore_framework-0.1.1.dist-info → jarviscore_framework-0.2.1.dist-info}/top_level.txt +1 -0
- test_logs/code_registry/functions/data_generator-558779ed_560ebc37.py +7 -0
- test_logs/code_registry/functions/data_generator-5ed3609e_560ebc37.py +7 -0
- test_logs/code_registry/functions/data_generator-66da0356_43970bb9.py +25 -0
- test_logs/code_registry/functions/data_generator-7a2fac83_583709d9.py +36 -0
- test_logs/code_registry/functions/data_generator-888b670f_aa235863.py +9 -0
- test_logs/code_registry/functions/data_generator-9ca5f642_aa235863.py +9 -0
- test_logs/code_registry/functions/data_generator-bfd90775_560ebc37.py +7 -0
- test_logs/code_registry/functions/data_generator-e95d2f7d_aa235863.py +9 -0
- test_logs/code_registry/functions/data_generator-f60ca8a2_327eb8c2.py +29 -0
- test_logs/code_registry/functions/mathematician-02adf9ee_958658d9.py +19 -0
- test_logs/code_registry/functions/mathematician-0706fb57_5df13441.py +23 -0
- test_logs/code_registry/functions/mathematician-153c9c4a_ba59c918.py +83 -0
- test_logs/code_registry/functions/mathematician-287e61c0_41daa793.py +18 -0
- test_logs/code_registry/functions/mathematician-2967af5a_863c2cc6.py +17 -0
- test_logs/code_registry/functions/mathematician-303ca6d6_5df13441.py +23 -0
- test_logs/code_registry/functions/mathematician-308a4afd_cbf5064d.py +73 -0
- test_logs/code_registry/functions/mathematician-353f16e2_0968bcf5.py +18 -0
- test_logs/code_registry/functions/mathematician-3c22475a_41daa793.py +17 -0
- test_logs/code_registry/functions/mathematician-5bac1029_0968bcf5.py +18 -0
- test_logs/code_registry/functions/mathematician-640f76b2_9198780b.py +19 -0
- test_logs/code_registry/functions/mathematician-752fa7ea_863c2cc6.py +17 -0
- test_logs/code_registry/functions/mathematician-baf9ef39_0968bcf5.py +18 -0
- test_logs/code_registry/functions/mathematician-bc8b2a2f_5df13441.py +23 -0
- test_logs/code_registry/functions/mathematician-c31e4686_41daa793.py +18 -0
- test_logs/code_registry/functions/mathematician-cc84c84c_863c2cc6.py +17 -0
- test_logs/code_registry/functions/mathematician-dd7c7144_9198780b.py +19 -0
- test_logs/code_registry/functions/mathematician-e671c256_41ea4487.py +74 -0
- test_logs/code_registry/functions/report_generator-1a878fcc_18d44bdc.py +47 -0
- test_logs/code_registry/functions/report_generator-25c1c331_cea57d0d.py +35 -0
- test_logs/code_registry/functions/report_generator-37552117_e711c2b9.py +35 -0
- test_logs/code_registry/functions/report_generator-bc662768_e711c2b9.py +35 -0
- test_logs/code_registry/functions/report_generator-d6c0e76b_5e7722ec.py +44 -0
- test_logs/code_registry/functions/report_generator-f270fb02_680529c3.py +44 -0
- test_logs/code_registry/functions/text_processor-11393b14_4370d3ed.py +40 -0
- test_logs/code_registry/functions/text_processor-7d02dfc3_d3b569be.py +37 -0
- test_logs/code_registry/functions/text_processor-8adb5e32_9168c5fe.py +13 -0
- test_logs/code_registry/functions/text_processor-c58ffc19_78b4ceac.py +42 -0
- test_logs/code_registry/functions/text_processor-cd5977b1_9168c5fe.py +13 -0
- test_logs/code_registry/functions/text_processor-ec1c8773_9168c5fe.py +13 -0
- tests/test_01_analyst_standalone.py +124 -0
- tests/test_02_assistant_standalone.py +164 -0
- tests/test_03_analyst_with_framework.py +945 -0
- tests/test_04_assistant_with_framework.py +1002 -0
- tests/test_05_integration.py +1301 -0
- tests/test_06_real_llm_integration.py +760 -0
- tests/test_07_distributed_single_node.py +578 -0
- tests/test_08_distributed_multi_node.py +454 -0
- tests/test_09_distributed_autoagent.py +509 -0
- tests/test_10_distributed_customagent.py +787 -0
- tests/test_mesh.py +35 -4
- jarviscore_framework-0.1.1.dist-info/METADATA +0 -137
- jarviscore_framework-0.1.1.dist-info/RECORD +0 -69
- {jarviscore_framework-0.1.1.dist-info → jarviscore_framework-0.2.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -11,6 +11,7 @@ from typing import List, Dict, Any, Optional
|
|
|
11
11
|
from .claimer import StepClaimer
|
|
12
12
|
from .dependency import DependencyManager
|
|
13
13
|
from .status import StatusManager
|
|
14
|
+
from jarviscore.context import create_context
|
|
14
15
|
|
|
15
16
|
logger = logging.getLogger(__name__)
|
|
16
17
|
|
|
@@ -169,9 +170,9 @@ class WorkflowEngine:
|
|
|
169
170
|
task = step.copy()
|
|
170
171
|
|
|
171
172
|
# Inject dependency outputs as context
|
|
173
|
+
dep_outputs = {}
|
|
172
174
|
if depends_on := step.get('depends_on'):
|
|
173
175
|
dep_ids = self._resolve_dependency_ids(depends_on, normalized_steps)
|
|
174
|
-
dep_outputs = {}
|
|
175
176
|
for dep_id in dep_ids:
|
|
176
177
|
if dep_id in self.memory:
|
|
177
178
|
dep_result = self.memory[dep_id]
|
|
@@ -179,13 +180,24 @@ class WorkflowEngine:
|
|
|
179
180
|
output = dep_result.get('output') if isinstance(dep_result, dict) else dep_result
|
|
180
181
|
dep_outputs[dep_id] = output
|
|
181
182
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
183
|
+
# Add dependency outputs to task context
|
|
184
|
+
task['context'] = {
|
|
185
|
+
'previous_step_results': dep_outputs,
|
|
186
|
+
'workflow_id': workflow_id,
|
|
187
|
+
'step_id': step_id
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
# Build and inject JarvisContext for Custom Profile agents
|
|
191
|
+
jarvis_ctx = create_context(
|
|
192
|
+
workflow_id=workflow_id,
|
|
193
|
+
step_id=step_id,
|
|
194
|
+
task=task.get('task', ''),
|
|
195
|
+
params=task.get('params', {}),
|
|
196
|
+
memory_dict=self.memory,
|
|
197
|
+
dependency_manager=self.dependency_manager
|
|
198
|
+
)
|
|
199
|
+
task['_jarvis_context'] = jarvis_ctx
|
|
200
|
+
logger.debug(f"Injected JarvisContext for step {step_id}")
|
|
189
201
|
|
|
190
202
|
# 6. Execute step with context
|
|
191
203
|
logger.info(f"Executing step {step_id} with agent {agent.agent_id}")
|
jarviscore/p2p/__init__.py
CHANGED
|
@@ -6,12 +6,16 @@ Wraps swim_p2p library for distributed agent coordination:
|
|
|
6
6
|
- ZMQ messaging for agent communication
|
|
7
7
|
- Smart keepalive with traffic suppression
|
|
8
8
|
- Step output broadcasting
|
|
9
|
+
- PeerClient for direct agent-to-agent communication
|
|
9
10
|
"""
|
|
10
11
|
|
|
11
12
|
from .coordinator import P2PCoordinator
|
|
12
13
|
from .swim_manager import SWIMThreadManager
|
|
13
14
|
from .keepalive import P2PKeepaliveManager, CircuitState
|
|
14
15
|
from .broadcaster import StepOutputBroadcaster, StepExecutionResult
|
|
16
|
+
from .peer_client import PeerClient
|
|
17
|
+
from .peer_tool import PeerTool
|
|
18
|
+
from .messages import PeerInfo, IncomingMessage, MessageType
|
|
15
19
|
|
|
16
20
|
__all__ = [
|
|
17
21
|
'P2PCoordinator',
|
|
@@ -20,4 +24,10 @@ __all__ = [
|
|
|
20
24
|
'CircuitState',
|
|
21
25
|
'StepOutputBroadcaster',
|
|
22
26
|
'StepExecutionResult',
|
|
27
|
+
# PeerClient API
|
|
28
|
+
'PeerClient',
|
|
29
|
+
'PeerTool',
|
|
30
|
+
'PeerInfo',
|
|
31
|
+
'IncomingMessage',
|
|
32
|
+
'MessageType',
|
|
23
33
|
]
|
jarviscore/p2p/coordinator.py
CHANGED
|
@@ -13,6 +13,7 @@ from typing import List, Dict, Any, Optional
|
|
|
13
13
|
from .swim_manager import SWIMThreadManager
|
|
14
14
|
from .keepalive import P2PKeepaliveManager
|
|
15
15
|
from .broadcaster import StepOutputBroadcaster
|
|
16
|
+
from .messages import IncomingMessage, MessageType
|
|
16
17
|
|
|
17
18
|
logger = logging.getLogger(__name__)
|
|
18
19
|
|
|
@@ -61,6 +62,7 @@ class P2PCoordinator:
|
|
|
61
62
|
# State
|
|
62
63
|
self._started = False
|
|
63
64
|
self._capability_map: Dict[str, List[str]] = {} # capability -> [agent_ids]
|
|
65
|
+
self._agent_peer_clients: Dict[str, Any] = {} # agent_id -> PeerClient
|
|
64
66
|
|
|
65
67
|
async def start(self):
|
|
66
68
|
"""
|
|
@@ -140,6 +142,10 @@ class P2PCoordinator:
|
|
|
140
142
|
"CAPABILITY_QUERY": self._handle_capability_query,
|
|
141
143
|
"P2P_KEEPALIVE": self.keepalive_manager.handle_keepalive_received,
|
|
142
144
|
"P2P_KEEPALIVE_ACK": self.keepalive_manager.handle_keepalive_ack,
|
|
145
|
+
# Peer-to-peer messaging (PeerClient)
|
|
146
|
+
"PEER_NOTIFY": self._handle_peer_notify,
|
|
147
|
+
"PEER_REQUEST": self._handle_peer_request,
|
|
148
|
+
"PEER_RESPONSE": self._handle_peer_response,
|
|
143
149
|
}
|
|
144
150
|
|
|
145
151
|
for msg_type, handler in message_types.items():
|
|
@@ -362,3 +368,126 @@ class P2PCoordinator:
|
|
|
362
368
|
logger.debug(f"Responded to capability query from {sender} for {capability}")
|
|
363
369
|
except Exception as e:
|
|
364
370
|
logger.error(f"Error handling capability query: {e}")
|
|
371
|
+
|
|
372
|
+
# ─────────────────────────────────────────────────────────────────
|
|
373
|
+
# Peer-to-peer messaging handlers (PeerClient support)
|
|
374
|
+
# ─────────────────────────────────────────────────────────────────
|
|
375
|
+
|
|
376
|
+
def register_peer_client(self, agent_id: str, peer_client):
|
|
377
|
+
"""
|
|
378
|
+
Register a PeerClient for an agent.
|
|
379
|
+
|
|
380
|
+
Called by Mesh when injecting PeerClients into agents.
|
|
381
|
+
"""
|
|
382
|
+
self._agent_peer_clients[agent_id] = peer_client
|
|
383
|
+
logger.debug(f"Registered PeerClient for agent: {agent_id}")
|
|
384
|
+
|
|
385
|
+
def unregister_peer_client(self, agent_id: str):
|
|
386
|
+
"""Unregister a PeerClient when agent leaves mesh."""
|
|
387
|
+
self._agent_peer_clients.pop(agent_id, None)
|
|
388
|
+
logger.debug(f"Unregistered PeerClient for agent: {agent_id}")
|
|
389
|
+
|
|
390
|
+
async def _handle_peer_notify(self, sender, message):
|
|
391
|
+
"""Handle peer notification message."""
|
|
392
|
+
try:
|
|
393
|
+
payload = message.get('payload', {})
|
|
394
|
+
target = payload.get('target')
|
|
395
|
+
|
|
396
|
+
# Find target agent's PeerClient
|
|
397
|
+
target_client = self._find_peer_client_by_role_or_id(target)
|
|
398
|
+
if not target_client:
|
|
399
|
+
logger.warning(f"Peer notify: target '{target}' not found")
|
|
400
|
+
return
|
|
401
|
+
|
|
402
|
+
# Create incoming message and deliver
|
|
403
|
+
incoming = IncomingMessage(
|
|
404
|
+
sender=payload.get('sender', sender),
|
|
405
|
+
sender_node=payload.get('sender_node', sender),
|
|
406
|
+
type=MessageType.NOTIFY,
|
|
407
|
+
data=payload.get('data', {}),
|
|
408
|
+
correlation_id=payload.get('correlation_id'),
|
|
409
|
+
timestamp=payload.get('timestamp', 0)
|
|
410
|
+
)
|
|
411
|
+
|
|
412
|
+
await target_client._deliver_message(incoming)
|
|
413
|
+
logger.debug(f"Delivered peer notify to {target}")
|
|
414
|
+
|
|
415
|
+
except Exception as e:
|
|
416
|
+
logger.error(f"Error handling peer notify: {e}")
|
|
417
|
+
|
|
418
|
+
async def _handle_peer_request(self, sender, message):
|
|
419
|
+
"""Handle peer request message (expects response)."""
|
|
420
|
+
try:
|
|
421
|
+
payload = message.get('payload', {})
|
|
422
|
+
target = payload.get('target')
|
|
423
|
+
|
|
424
|
+
# Find target agent's PeerClient
|
|
425
|
+
target_client = self._find_peer_client_by_role_or_id(target)
|
|
426
|
+
if not target_client:
|
|
427
|
+
logger.warning(f"Peer request: target '{target}' not found")
|
|
428
|
+
return
|
|
429
|
+
|
|
430
|
+
# Create incoming message and deliver
|
|
431
|
+
incoming = IncomingMessage(
|
|
432
|
+
sender=payload.get('sender', sender),
|
|
433
|
+
sender_node=payload.get('sender_node', sender),
|
|
434
|
+
type=MessageType.REQUEST,
|
|
435
|
+
data=payload.get('data', {}),
|
|
436
|
+
correlation_id=payload.get('correlation_id'),
|
|
437
|
+
timestamp=payload.get('timestamp', 0)
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
await target_client._deliver_message(incoming)
|
|
441
|
+
logger.debug(f"Delivered peer request to {target}")
|
|
442
|
+
|
|
443
|
+
except Exception as e:
|
|
444
|
+
logger.error(f"Error handling peer request: {e}")
|
|
445
|
+
|
|
446
|
+
async def _handle_peer_response(self, sender, message):
|
|
447
|
+
"""Handle peer response message."""
|
|
448
|
+
try:
|
|
449
|
+
payload = message.get('payload', {})
|
|
450
|
+
target = payload.get('target')
|
|
451
|
+
|
|
452
|
+
# Find target agent's PeerClient
|
|
453
|
+
target_client = self._find_peer_client_by_role_or_id(target)
|
|
454
|
+
if not target_client:
|
|
455
|
+
logger.warning(f"Peer response: target '{target}' not found")
|
|
456
|
+
return
|
|
457
|
+
|
|
458
|
+
# Create incoming message and deliver
|
|
459
|
+
incoming = IncomingMessage(
|
|
460
|
+
sender=payload.get('sender', sender),
|
|
461
|
+
sender_node=payload.get('sender_node', sender),
|
|
462
|
+
type=MessageType.RESPONSE,
|
|
463
|
+
data=payload.get('data', {}),
|
|
464
|
+
correlation_id=payload.get('correlation_id'),
|
|
465
|
+
timestamp=payload.get('timestamp', 0)
|
|
466
|
+
)
|
|
467
|
+
|
|
468
|
+
await target_client._deliver_message(incoming)
|
|
469
|
+
logger.debug(f"Delivered peer response to {target}")
|
|
470
|
+
|
|
471
|
+
except Exception as e:
|
|
472
|
+
logger.error(f"Error handling peer response: {e}")
|
|
473
|
+
|
|
474
|
+
def _find_peer_client_by_role_or_id(self, target: str):
|
|
475
|
+
"""
|
|
476
|
+
Find a PeerClient by agent role or agent_id.
|
|
477
|
+
|
|
478
|
+
Args:
|
|
479
|
+
target: Role name or agent_id
|
|
480
|
+
|
|
481
|
+
Returns:
|
|
482
|
+
PeerClient instance or None
|
|
483
|
+
"""
|
|
484
|
+
# Try direct agent_id lookup
|
|
485
|
+
if target in self._agent_peer_clients:
|
|
486
|
+
return self._agent_peer_clients[target]
|
|
487
|
+
|
|
488
|
+
# Try role lookup via agents
|
|
489
|
+
for agent in self.agents:
|
|
490
|
+
if agent.role == target or agent.agent_id == target:
|
|
491
|
+
return self._agent_peer_clients.get(agent.agent_id)
|
|
492
|
+
|
|
493
|
+
return None
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""
|
|
2
|
+
P2P Message Types for JarvisCore Framework
|
|
3
|
+
|
|
4
|
+
Defines dataclasses for peer information and messages exchanged
|
|
5
|
+
between agents via the PeerClient API.
|
|
6
|
+
"""
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from typing import List, Dict, Any, Optional
|
|
9
|
+
from enum import Enum
|
|
10
|
+
import time
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class MessageType(Enum):
|
|
14
|
+
"""Types of peer-to-peer messages."""
|
|
15
|
+
NOTIFY = "notify" # Fire-and-forget notification
|
|
16
|
+
REQUEST = "request" # Request expecting response
|
|
17
|
+
RESPONSE = "response" # Response to a request
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class PeerInfo:
|
|
22
|
+
"""
|
|
23
|
+
Information about a peer agent in the mesh.
|
|
24
|
+
|
|
25
|
+
Attributes:
|
|
26
|
+
agent_id: Unique identifier for the agent
|
|
27
|
+
role: The agent's role (e.g., "scout", "analyst")
|
|
28
|
+
capabilities: List of capabilities the agent provides
|
|
29
|
+
node_id: P2P node identifier (host:port)
|
|
30
|
+
status: Current status (alive, suspected, dead)
|
|
31
|
+
"""
|
|
32
|
+
agent_id: str
|
|
33
|
+
role: str
|
|
34
|
+
capabilities: List[str]
|
|
35
|
+
node_id: str = ""
|
|
36
|
+
status: str = "alive"
|
|
37
|
+
|
|
38
|
+
def has_capability(self, capability: str) -> bool:
|
|
39
|
+
"""Check if peer has a specific capability."""
|
|
40
|
+
return capability in self.capabilities
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass
|
|
44
|
+
class IncomingMessage:
|
|
45
|
+
"""
|
|
46
|
+
A message received from a peer agent.
|
|
47
|
+
|
|
48
|
+
Attributes:
|
|
49
|
+
sender: Agent ID or role of the sender
|
|
50
|
+
sender_node: P2P node ID of the sender
|
|
51
|
+
type: Message type (notify, request, response)
|
|
52
|
+
data: Message payload
|
|
53
|
+
correlation_id: ID linking request to response (for request-response pattern)
|
|
54
|
+
timestamp: When the message was sent
|
|
55
|
+
"""
|
|
56
|
+
sender: str
|
|
57
|
+
sender_node: str
|
|
58
|
+
type: MessageType
|
|
59
|
+
data: Dict[str, Any]
|
|
60
|
+
correlation_id: Optional[str] = None
|
|
61
|
+
timestamp: float = field(default_factory=time.time)
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def is_request(self) -> bool:
|
|
65
|
+
"""Check if this message expects a response."""
|
|
66
|
+
return self.type == MessageType.REQUEST
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def is_notify(self) -> bool:
|
|
70
|
+
"""Check if this is a fire-and-forget notification."""
|
|
71
|
+
return self.type == MessageType.NOTIFY
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@dataclass
|
|
75
|
+
class OutgoingMessage:
|
|
76
|
+
"""
|
|
77
|
+
A message to be sent to a peer agent.
|
|
78
|
+
|
|
79
|
+
Used internally by PeerClient for message construction.
|
|
80
|
+
"""
|
|
81
|
+
target: str # Target agent role or ID
|
|
82
|
+
type: MessageType
|
|
83
|
+
data: Dict[str, Any]
|
|
84
|
+
correlation_id: Optional[str] = None
|
|
85
|
+
timestamp: float = field(default_factory=time.time)
|
|
86
|
+
sender: str = "" # Filled in by PeerClient
|
|
87
|
+
sender_node: str = "" # Filled in by PeerClient
|