jarviscore-framework 0.1.1__py3-none-any.whl → 0.2.0__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 (85) hide show
  1. examples/autoagent_distributed_example.py +211 -0
  2. examples/custom_profile_decorator.py +134 -0
  3. examples/custom_profile_wrap.py +168 -0
  4. examples/customagent_distributed_example.py +362 -0
  5. examples/customagent_p2p_example.py +347 -0
  6. jarviscore/__init__.py +49 -36
  7. jarviscore/adapter/__init__.py +15 -9
  8. jarviscore/adapter/decorator.py +23 -19
  9. jarviscore/adapter/wrapper.py +303 -0
  10. jarviscore/cli/scaffold.py +1 -1
  11. jarviscore/cli/smoketest.py +3 -2
  12. jarviscore/core/agent.py +44 -1
  13. jarviscore/core/mesh.py +196 -35
  14. jarviscore/data/examples/autoagent_distributed_example.py +211 -0
  15. jarviscore/data/examples/customagent_distributed_example.py +362 -0
  16. jarviscore/data/examples/customagent_p2p_example.py +347 -0
  17. jarviscore/docs/API_REFERENCE.md +264 -51
  18. jarviscore/docs/AUTOAGENT_GUIDE.md +198 -0
  19. jarviscore/docs/CONFIGURATION.md +35 -21
  20. jarviscore/docs/CUSTOMAGENT_GUIDE.md +415 -0
  21. jarviscore/docs/GETTING_STARTED.md +106 -13
  22. jarviscore/docs/TROUBLESHOOTING.md +144 -6
  23. jarviscore/docs/USER_GUIDE.md +138 -361
  24. jarviscore/orchestration/engine.py +20 -8
  25. jarviscore/p2p/__init__.py +10 -0
  26. jarviscore/p2p/coordinator.py +129 -0
  27. jarviscore/p2p/messages.py +87 -0
  28. jarviscore/p2p/peer_client.py +576 -0
  29. jarviscore/p2p/peer_tool.py +268 -0
  30. {jarviscore_framework-0.1.1.dist-info → jarviscore_framework-0.2.0.dist-info}/METADATA +60 -54
  31. jarviscore_framework-0.2.0.dist-info/RECORD +132 -0
  32. {jarviscore_framework-0.1.1.dist-info → jarviscore_framework-0.2.0.dist-info}/WHEEL +1 -1
  33. {jarviscore_framework-0.1.1.dist-info → jarviscore_framework-0.2.0.dist-info}/top_level.txt +1 -0
  34. test_logs/code_registry/functions/data_generator-558779ed_560ebc37.py +7 -0
  35. test_logs/code_registry/functions/data_generator-5ed3609e_560ebc37.py +7 -0
  36. test_logs/code_registry/functions/data_generator-66da0356_43970bb9.py +25 -0
  37. test_logs/code_registry/functions/data_generator-7a2fac83_583709d9.py +36 -0
  38. test_logs/code_registry/functions/data_generator-888b670f_aa235863.py +9 -0
  39. test_logs/code_registry/functions/data_generator-9ca5f642_aa235863.py +9 -0
  40. test_logs/code_registry/functions/data_generator-bfd90775_560ebc37.py +7 -0
  41. test_logs/code_registry/functions/data_generator-e95d2f7d_aa235863.py +9 -0
  42. test_logs/code_registry/functions/data_generator-f60ca8a2_327eb8c2.py +29 -0
  43. test_logs/code_registry/functions/mathematician-02adf9ee_958658d9.py +19 -0
  44. test_logs/code_registry/functions/mathematician-0706fb57_5df13441.py +23 -0
  45. test_logs/code_registry/functions/mathematician-153c9c4a_ba59c918.py +83 -0
  46. test_logs/code_registry/functions/mathematician-287e61c0_41daa793.py +18 -0
  47. test_logs/code_registry/functions/mathematician-2967af5a_863c2cc6.py +17 -0
  48. test_logs/code_registry/functions/mathematician-303ca6d6_5df13441.py +23 -0
  49. test_logs/code_registry/functions/mathematician-308a4afd_cbf5064d.py +73 -0
  50. test_logs/code_registry/functions/mathematician-353f16e2_0968bcf5.py +18 -0
  51. test_logs/code_registry/functions/mathematician-3c22475a_41daa793.py +17 -0
  52. test_logs/code_registry/functions/mathematician-5bac1029_0968bcf5.py +18 -0
  53. test_logs/code_registry/functions/mathematician-640f76b2_9198780b.py +19 -0
  54. test_logs/code_registry/functions/mathematician-752fa7ea_863c2cc6.py +17 -0
  55. test_logs/code_registry/functions/mathematician-baf9ef39_0968bcf5.py +18 -0
  56. test_logs/code_registry/functions/mathematician-bc8b2a2f_5df13441.py +23 -0
  57. test_logs/code_registry/functions/mathematician-c31e4686_41daa793.py +18 -0
  58. test_logs/code_registry/functions/mathematician-cc84c84c_863c2cc6.py +17 -0
  59. test_logs/code_registry/functions/mathematician-dd7c7144_9198780b.py +19 -0
  60. test_logs/code_registry/functions/mathematician-e671c256_41ea4487.py +74 -0
  61. test_logs/code_registry/functions/report_generator-1a878fcc_18d44bdc.py +47 -0
  62. test_logs/code_registry/functions/report_generator-25c1c331_cea57d0d.py +35 -0
  63. test_logs/code_registry/functions/report_generator-37552117_e711c2b9.py +35 -0
  64. test_logs/code_registry/functions/report_generator-bc662768_e711c2b9.py +35 -0
  65. test_logs/code_registry/functions/report_generator-d6c0e76b_5e7722ec.py +44 -0
  66. test_logs/code_registry/functions/report_generator-f270fb02_680529c3.py +44 -0
  67. test_logs/code_registry/functions/text_processor-11393b14_4370d3ed.py +40 -0
  68. test_logs/code_registry/functions/text_processor-7d02dfc3_d3b569be.py +37 -0
  69. test_logs/code_registry/functions/text_processor-8adb5e32_9168c5fe.py +13 -0
  70. test_logs/code_registry/functions/text_processor-c58ffc19_78b4ceac.py +42 -0
  71. test_logs/code_registry/functions/text_processor-cd5977b1_9168c5fe.py +13 -0
  72. test_logs/code_registry/functions/text_processor-ec1c8773_9168c5fe.py +13 -0
  73. tests/test_01_analyst_standalone.py +124 -0
  74. tests/test_02_assistant_standalone.py +164 -0
  75. tests/test_03_analyst_with_framework.py +945 -0
  76. tests/test_04_assistant_with_framework.py +1002 -0
  77. tests/test_05_integration.py +1301 -0
  78. tests/test_06_real_llm_integration.py +760 -0
  79. tests/test_07_distributed_single_node.py +578 -0
  80. tests/test_08_distributed_multi_node.py +454 -0
  81. tests/test_09_distributed_autoagent.py +509 -0
  82. tests/test_10_distributed_customagent.py +787 -0
  83. tests/test_mesh.py +35 -4
  84. jarviscore_framework-0.1.1.dist-info/RECORD +0 -69
  85. {jarviscore_framework-0.1.1.dist-info → jarviscore_framework-0.2.0.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
- # Add dependency outputs to task context
183
- task['context'] = {
184
- 'previous_step_results': dep_outputs,
185
- 'workflow_id': workflow_id,
186
- 'step_id': step_id
187
- }
188
- logger.debug(f"Injected context with {len(dep_outputs)} dependencies")
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}")
@@ -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
  ]
@@ -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