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
@@ -0,0 +1,303 @@
1
+ """
2
+ wrap() function - Wrap an existing instance as a JarvisCore agent.
3
+
4
+ Alternative to @jarvis_agent decorator when you have an existing instance
5
+ rather than a class definition.
6
+
7
+ Example:
8
+ from jarviscore import wrap, Mesh
9
+
10
+ # Your existing agent instance
11
+ my_langchain_agent = LangChainAgent(llm=my_llm)
12
+
13
+ # Wrap it for JarvisCore
14
+ wrapped = wrap(
15
+ my_langchain_agent,
16
+ role="researcher",
17
+ capabilities=["research", "analysis"],
18
+ execute_method="invoke"
19
+ )
20
+
21
+ mesh = Mesh(mode="autonomous")
22
+ mesh.add(wrapped)
23
+ await mesh.start()
24
+ """
25
+ from typing import List, Optional, Any
26
+ import inspect
27
+ import logging
28
+
29
+ from jarviscore.profiles.customagent import CustomAgent
30
+ from jarviscore.context import JarvisContext, create_context
31
+
32
+ logger = logging.getLogger(__name__)
33
+
34
+ # Common method names to auto-detect
35
+ EXECUTE_METHODS = [
36
+ "run", # Most common
37
+ "invoke", # LangChain
38
+ "execute", # Generic
39
+ "call", # Callable pattern
40
+ "__call__", # Callable objects
41
+ "process", # Processing agents
42
+ "handle", # Handler pattern
43
+ ]
44
+
45
+
46
+ def detect_execute_method(instance: Any) -> Optional[str]:
47
+ """
48
+ Auto-detect the execute method on an instance.
49
+
50
+ Args:
51
+ instance: Object instance to inspect
52
+
53
+ Returns:
54
+ Method name or None if not found
55
+ """
56
+ for method_name in EXECUTE_METHODS:
57
+ if hasattr(instance, method_name):
58
+ attr = getattr(instance, method_name)
59
+ if callable(attr) and method_name != '__call__':
60
+ return method_name
61
+ elif method_name == '__call__':
62
+ # Check if __call__ is defined on the class itself
63
+ for klass in type(instance).__mro__:
64
+ if klass is object:
65
+ break
66
+ if '__call__' in klass.__dict__:
67
+ return method_name
68
+ return None
69
+
70
+
71
+ def wrap(
72
+ instance: Any,
73
+ role: str,
74
+ capabilities: List[str],
75
+ execute_method: Optional[str] = None
76
+ ) -> CustomAgent:
77
+ """
78
+ Wrap an existing instance as a JarvisCore agent.
79
+
80
+ Use this when you have an already-instantiated object (like a LangChain
81
+ agent or custom class instance) that you want to use with JarvisCore.
82
+
83
+ Args:
84
+ instance: The object instance to wrap
85
+ role: Agent role identifier (used for step routing)
86
+ capabilities: List of capabilities (used for step matching)
87
+ execute_method: Method name to call for execution (auto-detected if not provided)
88
+
89
+ Returns:
90
+ A CustomAgent instance wrapping the original object
91
+
92
+ Example:
93
+ # Wrap a LangChain agent
94
+ from langchain.agents import AgentExecutor
95
+
96
+ langchain_agent = AgentExecutor(agent=my_agent, tools=my_tools)
97
+
98
+ wrapped = wrap(
99
+ langchain_agent,
100
+ role="assistant",
101
+ capabilities=["chat", "tools"],
102
+ execute_method="invoke"
103
+ )
104
+
105
+ mesh.add(wrapped)
106
+
107
+ # Wrap a simple Python object
108
+ class MyProcessor:
109
+ def run(self, data):
110
+ return {"processed": data * 2}
111
+
112
+ processor = MyProcessor()
113
+ wrapped = wrap(processor, role="processor", capabilities=["processing"])
114
+
115
+ # Wrap with context access
116
+ class MyAggregator:
117
+ def run(self, task, ctx: JarvisContext):
118
+ prev = ctx.previous("step1")
119
+ return {"aggregated": prev}
120
+
121
+ aggregator = MyAggregator()
122
+ wrapped = wrap(aggregator, role="aggregator", capabilities=["aggregation"])
123
+ """
124
+ # Detect execute method if not provided
125
+ method_name = execute_method or detect_execute_method(instance)
126
+ if not method_name:
127
+ raise ValueError(
128
+ f"Could not detect execute method on {type(instance).__name__}. "
129
+ f"Please specify execute_method parameter or add one of: {EXECUTE_METHODS}"
130
+ )
131
+
132
+ # Verify method exists
133
+ if not hasattr(instance, method_name):
134
+ raise ValueError(
135
+ f"{type(instance).__name__} has no method '{method_name}'"
136
+ )
137
+
138
+ # Check if method expects context parameter
139
+ method = getattr(instance, method_name)
140
+ sig = inspect.signature(method)
141
+ params = list(sig.parameters.keys())
142
+
143
+ # Remove 'self' from params (shouldn't be there for bound methods, but just in case)
144
+ if params and params[0] == 'self':
145
+ params = params[1:]
146
+
147
+ expects_context = 'ctx' in params or 'context' in params
148
+
149
+ logger.debug(
150
+ f"Wrapping instance {type(instance).__name__}: method={method_name}, "
151
+ f"expects_context={expects_context}, params={params}"
152
+ )
153
+
154
+ # Create a CustomAgent subclass dynamically
155
+ class WrappedInstanceAgent(CustomAgent):
156
+ """Agent wrapping an existing instance via wrap()."""
157
+ pass
158
+
159
+ # Set class attributes
160
+ WrappedInstanceAgent.role = role
161
+ WrappedInstanceAgent.capabilities = capabilities
162
+
163
+ # Store metadata
164
+ WrappedInstanceAgent._wrapped_instance = instance
165
+ WrappedInstanceAgent._execute_method = method_name
166
+ WrappedInstanceAgent._expects_context = expects_context
167
+ WrappedInstanceAgent._original_params = params
168
+
169
+ # Create the agent instance
170
+ agent = WrappedInstanceAgent.__new__(WrappedInstanceAgent)
171
+
172
+ # Initialize CustomAgent parts
173
+ CustomAgent.__init__(agent)
174
+
175
+ # Store the wrapped instance
176
+ agent._instance = instance
177
+
178
+ # Override execute_task
179
+ async def execute_task(self, task: dict) -> dict:
180
+ """Execute by calling the wrapped instance's method."""
181
+ method = getattr(self._instance, method_name)
182
+
183
+ # Get context if method expects it
184
+ ctx = None
185
+ if expects_context:
186
+ # Use pre-injected context from WorkflowEngine if available
187
+ ctx = task.get('_jarvis_context')
188
+
189
+ # Fallback: build context manually (for standalone usage)
190
+ if ctx is None:
191
+ memory_dict = {}
192
+ dep_manager = None
193
+
194
+ if self._mesh:
195
+ engine = getattr(self._mesh, '_workflow_engine', None)
196
+ if engine:
197
+ memory_dict = engine.memory
198
+ dep_manager = getattr(engine, 'dependency_manager', None)
199
+
200
+ ctx = create_context(
201
+ workflow_id=task.get('context', {}).get('workflow_id', 'unknown'),
202
+ step_id=task.get('context', {}).get('step_id', task.get('id', 'unknown')),
203
+ task=task.get('task', ''),
204
+ params=task.get('params', {}),
205
+ memory_dict=memory_dict,
206
+ dependency_manager=dep_manager
207
+ )
208
+
209
+ # Prepare arguments
210
+ args = _prepare_args(task, ctx, params)
211
+
212
+ # Call the method
213
+ try:
214
+ result = method(*args)
215
+
216
+ # Handle async methods
217
+ if inspect.isawaitable(result):
218
+ result = await result
219
+
220
+ except Exception as e:
221
+ logger.error(f"Error in {type(instance).__name__}.{method_name}: {e}")
222
+ return {
223
+ 'status': 'failure',
224
+ 'error': str(e),
225
+ 'agent': self.agent_id
226
+ }
227
+
228
+ # Normalize result
229
+ return _normalize_result(self, result)
230
+
231
+ # Bind the method to the agent
232
+ import types
233
+ agent.execute_task = types.MethodType(execute_task, agent)
234
+
235
+ # Copy class name for better debugging
236
+ WrappedInstanceAgent.__name__ = f"Wrapped_{type(instance).__name__}"
237
+ WrappedInstanceAgent.__qualname__ = f"Wrapped_{type(instance).__name__}"
238
+
239
+ logger.debug(f"Created wrapped agent: role={role}, instance={type(instance).__name__}")
240
+
241
+ return agent
242
+
243
+
244
+ def _prepare_args(task: dict, ctx: Optional[JarvisContext], param_names: List[str]) -> tuple:
245
+ """Prepare arguments for the wrapped method."""
246
+ args = []
247
+
248
+ task_params = task.get('params', {})
249
+ params_is_dict = isinstance(task_params, dict)
250
+
251
+ for param in param_names:
252
+ if param in ('ctx', 'context'):
253
+ args.append(ctx)
254
+ elif param == 'task':
255
+ args.append(task.get('task', task))
256
+ elif param == 'data':
257
+ if params_is_dict:
258
+ args.append(task_params.get('data', task_params))
259
+ else:
260
+ args.append(task_params)
261
+ elif param == 'params':
262
+ args.append(task_params)
263
+ elif param in ('input', 'query', 'text', 'message'):
264
+ if params_is_dict:
265
+ args.append(task.get('task', task_params.get(param, '')))
266
+ else:
267
+ args.append(task.get('task', task_params))
268
+ else:
269
+ if params_is_dict and param in task_params:
270
+ args.append(task_params[param])
271
+ elif param in task:
272
+ args.append(task[param])
273
+ else:
274
+ args.append(task_params)
275
+ break
276
+
277
+ if not args:
278
+ if 'params' in task:
279
+ args.append(task_params)
280
+ else:
281
+ args.append(task)
282
+
283
+ return tuple(args)
284
+
285
+
286
+ def _normalize_result(agent: CustomAgent, result: Any) -> dict:
287
+ """Normalize result to expected format."""
288
+ if isinstance(result, dict):
289
+ if 'status' not in result:
290
+ return {
291
+ 'status': 'success',
292
+ 'output': result,
293
+ 'agent': agent.agent_id
294
+ }
295
+ if 'agent' not in result:
296
+ result['agent'] = agent.agent_id
297
+ return result
298
+ else:
299
+ return {
300
+ 'status': 'success',
301
+ 'output': result,
302
+ 'agent': agent.agent_id
303
+ }
@@ -119,7 +119,7 @@ def print_next_steps(env_created: bool, examples_created: bool):
119
119
 
120
120
  if examples_created:
121
121
  steps.append(f"{'4' if env_created else '3'}. Try an example:\n"
122
- " python examples/calculator_agent_example.py")
122
+ " python examples/calculator_agent_example.py for AutoAgent Profile or python examples/customagent_p2p_example.py ")
123
123
 
124
124
  for step in steps:
125
125
  print(f"\n{step}")
@@ -311,8 +311,9 @@ class SmokeTest:
311
311
 
312
312
  print("\n✓ All smoke tests passed!")
313
313
  print("\nJarvisCore is working correctly. Next steps:")
314
- print(" 1. Try examples: python examples/calculator_agent_example.py")
315
- print(" 2. Read user guide: docs/USER_GUIDE.md")
314
+ print(" 1. Try examples - AutoAgent Profile: python examples/calculator_agent_example.py")
315
+ print(" 2. Try examples - CustomAgent Profile: python examples/customagent_p2p_example.py")
316
+ print(" 3. Read user guide: docs/USER_GUIDE.md")
316
317
  print(" 3. Build your first agent: docs/GETTING_STARTED.md")
317
318
  print()
318
319
  return True
jarviscore/core/agent.py CHANGED
@@ -2,12 +2,18 @@
2
2
  Agent base class - defines WHAT an agent does (role, capabilities).
3
3
 
4
4
  This is the foundation of the JarvisCore framework. All agents inherit from this class.
5
+
6
+ For p2p mode, agents can implement a run() method for their own execution loop
7
+ and use self.peers for direct peer-to-peer communication.
5
8
  """
6
9
  from abc import ABC, abstractmethod
7
- from typing import List, Dict, Any, Optional
10
+ from typing import List, Dict, Any, Optional, TYPE_CHECKING
8
11
  from uuid import uuid4
9
12
  import logging
10
13
 
14
+ if TYPE_CHECKING:
15
+ from jarviscore.p2p import PeerClient
16
+
11
17
  logger = logging.getLogger(__name__)
12
18
 
13
19
 
@@ -60,6 +66,10 @@ class Agent(ABC):
60
66
  self._mesh = None # Set by Mesh when agent is added
61
67
  self._logger = logging.getLogger(f"jarviscore.agent.{self.agent_id}")
62
68
 
69
+ # P2P mode support
70
+ self.peers: Optional['PeerClient'] = None # Injected by Mesh in p2p mode
71
+ self.shutdown_requested: bool = False # Set True to stop run() loop
72
+
63
73
  self._logger.debug(f"Agent initialized: {self.agent_id}")
64
74
 
65
75
  @abstractmethod
@@ -125,6 +135,39 @@ class Agent(ABC):
125
135
  """
126
136
  self._logger.info(f"Tearing down agent: {self.agent_id}")
127
137
 
138
+ async def run(self):
139
+ """
140
+ Optional execution loop for p2p mode agents.
141
+
142
+ Override this for agents that run their own execution loops
143
+ instead of waiting for tasks from the workflow engine.
144
+
145
+ The loop should check self.shutdown_requested to know when to stop.
146
+
147
+ Example:
148
+ async def run(self):
149
+ while not self.shutdown_requested:
150
+ # Do agent work
151
+ result = await self.do_work()
152
+
153
+ # Notify peer
154
+ await self.peers.notify("analyst", {"done": True, "data": result})
155
+
156
+ # Wait before next cycle
157
+ await asyncio.sleep(5)
158
+ """
159
+ # Default: do nothing (for task-driven agents)
160
+ pass
161
+
162
+ def request_shutdown(self):
163
+ """
164
+ Request the agent to stop its run() loop.
165
+
166
+ Called by Mesh during shutdown.
167
+ """
168
+ self.shutdown_requested = True
169
+ self._logger.info(f"Shutdown requested for agent: {self.agent_id}")
170
+
128
171
  def can_handle(self, task: Dict[str, Any]) -> bool:
129
172
  """
130
173
  Check if agent can handle a task based on capabilities.