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.
Files changed (86) 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 +730 -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 +730 -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 +1362 -0
  21. jarviscore/docs/GETTING_STARTED.md +107 -14
  22. jarviscore/docs/TROUBLESHOOTING.md +145 -7
  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.2.1.dist-info/METADATA +144 -0
  31. jarviscore_framework-0.2.1.dist-info/RECORD +132 -0
  32. {jarviscore_framework-0.1.1.dist-info → jarviscore_framework-0.2.1.dist-info}/WHEEL +1 -1
  33. {jarviscore_framework-0.1.1.dist-info → jarviscore_framework-0.2.1.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/METADATA +0 -137
  85. jarviscore_framework-0.1.1.dist-info/RECORD +0 -69
  86. {jarviscore_framework-0.1.1.dist-info → jarviscore_framework-0.2.1.dist-info}/licenses/LICENSE +0 -0
jarviscore/__init__.py CHANGED
@@ -2,52 +2,51 @@
2
2
  JarvisCore - P2P Distributed Agent Framework
3
3
 
4
4
  A production-grade framework for building autonomous agent systems with:
5
- - Event-sourced state management (crash recovery, HITL support)
6
5
  - P2P coordination via SWIM protocol
7
- - Three execution profiles:
8
- * AutoAgent: LLM code generation (3 lines of user code)
9
- * CustomAgent: Framework-agnostic (LangChain, MCP, raw Python)
10
- * @jarvis_agent: Decorator to wrap existing agents (1 line)
6
+ - Workflow orchestration with dependencies
7
+ - Two agent profiles: AutoAgent (LLM-powered) and CustomAgent (your code)
11
8
 
12
- Quick Start (AutoAgent):
13
- from jarviscore import Mesh, AutoAgent
9
+ Profiles:
10
+ AutoAgent - LLM generates and executes code from prompts (autonomous mode)
11
+ CustomAgent - You provide execute_task() or run() (p2p/distributed modes)
14
12
 
15
- class ScraperAgent(AutoAgent):
16
- role = "scraper"
17
- capabilities = ["web_scraping"]
18
- system_prompt = "You are an expert web scraper..."
13
+ Modes:
14
+ autonomous - Workflow engine only (AutoAgent)
15
+ p2p - P2P coordinator only (CustomAgent with run() loops)
16
+ distributed - Both workflow + P2P (CustomAgent with execute_task())
17
+
18
+ Quick Start (AutoAgent - autonomous mode):
19
+ from jarviscore import Mesh
20
+ from jarviscore.profiles import AutoAgent
21
+
22
+ class CalcAgent(AutoAgent):
23
+ role = "calculator"
24
+ capabilities = ["math"]
25
+ system_prompt = "You are a math expert. Store result in 'result'."
19
26
 
20
27
  mesh = Mesh(mode="autonomous")
21
- mesh.add(ScraperAgent)
28
+ mesh.add(CalcAgent)
22
29
  await mesh.start()
30
+ results = await mesh.workflow("calc", [{"agent": "calculator", "task": "Calculate 10!"}])
23
31
 
24
- Quick Start (Custom Profile with decorator):
25
- from jarviscore import Mesh, jarvis_agent, JarvisContext
32
+ Quick Start (CustomAgent - distributed mode):
33
+ from jarviscore import Mesh
34
+ from jarviscore.profiles import CustomAgent
26
35
 
27
- @jarvis_agent(role="processor", capabilities=["processing"])
28
- class DataProcessor:
29
- def run(self, data):
30
- return {"processed": data * 2}
36
+ class MyAgent(CustomAgent):
37
+ role = "processor"
38
+ capabilities = ["processing"]
31
39
 
32
- # With context access
33
- @jarvis_agent(role="aggregator", capabilities=["aggregation"])
34
- class Aggregator:
35
- def run(self, task, ctx: JarvisContext):
36
- prev = ctx.previous("step1")
37
- return {"result": prev}
40
+ async def execute_task(self, task):
41
+ return {"status": "success", "output": task.get("task").upper()}
38
42
 
39
- mesh = Mesh(mode="autonomous")
40
- mesh.add(DataProcessor)
41
- mesh.add(Aggregator)
43
+ mesh = Mesh(mode="distributed", config={'bind_port': 7950})
44
+ mesh.add(MyAgent)
42
45
  await mesh.start()
43
-
44
- results = await mesh.workflow("pipeline", [
45
- {"agent": "processor", "task": "Process", "params": {"data": [1,2,3]}},
46
- {"agent": "aggregator", "task": "Aggregate", "depends_on": [0]}
47
- ])
46
+ results = await mesh.workflow("demo", [{"agent": "processor", "task": "hello"}])
48
47
  """
49
48
 
50
- __version__ = "0.1.1"
49
+ __version__ = "0.2.1"
51
50
  __author__ = "JarvisCore Contributors"
52
51
  __license__ = "MIT"
53
52
 
@@ -60,16 +59,23 @@ from jarviscore.core.mesh import Mesh, MeshMode
60
59
  from jarviscore.profiles.autoagent import AutoAgent
61
60
  from jarviscore.profiles.customagent import CustomAgent
62
61
 
63
- # Custom Profile: Decorator and Context
64
- from jarviscore.adapter import jarvis_agent
62
+ # Custom Profile: Decorator, Wrapper, and Context
63
+ from jarviscore.adapter import jarvis_agent, wrap
65
64
  from jarviscore.context import JarvisContext, MemoryAccessor, DependencyAccessor
66
65
 
66
+ # P2P Direct Communication
67
+ from jarviscore.p2p import PeerClient, PeerTool, PeerInfo, IncomingMessage
68
+
69
+ # Alias for p2p mode agents
70
+ JarvisAgent = Agent # Use this for agents with run() loops
71
+
67
72
  __all__ = [
68
73
  # Version
69
74
  "__version__",
70
75
 
71
76
  # Core
72
77
  "Agent",
78
+ "JarvisAgent", # Alias for p2p mode
73
79
  "Profile",
74
80
  "Mesh",
75
81
  "MeshMode",
@@ -78,9 +84,16 @@ __all__ = [
78
84
  "AutoAgent",
79
85
  "CustomAgent",
80
86
 
81
- # Custom Profile (decorator approach)
87
+ # Custom Profile (decorator and wrapper)
82
88
  "jarvis_agent",
89
+ "wrap",
83
90
  "JarvisContext",
84
91
  "MemoryAccessor",
85
92
  "DependencyAccessor",
93
+
94
+ # P2P Direct Communication
95
+ "PeerClient",
96
+ "PeerTool",
97
+ "PeerInfo",
98
+ "IncomingMessage",
86
99
  ]
@@ -3,8 +3,9 @@ Adapter module for JarvisCore Custom Profile.
3
3
 
4
4
  Provides utilities to wrap existing agents for use with JarvisCore:
5
5
  - @jarvis_agent: Decorator to convert any class into a JarvisCore agent
6
+ - wrap(): Function to wrap an existing instance as a JarvisCore agent
6
7
 
7
- Example:
8
+ Example (decorator):
8
9
  from jarviscore import jarvis_agent, Mesh, JarvisContext
9
10
 
10
11
  @jarvis_agent(role="processor", capabilities=["processing"])
@@ -12,23 +13,28 @@ Example:
12
13
  def run(self, data):
13
14
  return {"processed": data * 2}
14
15
 
15
- # With context access
16
- @jarvis_agent(role="aggregator", capabilities=["aggregation"])
17
- class Aggregator:
18
- def run(self, task, ctx: JarvisContext):
19
- prev = ctx.previous("step1")
20
- return {"aggregated": prev}
21
-
22
16
  mesh = Mesh(mode="autonomous")
23
17
  mesh.add(DataProcessor)
24
- mesh.add(Aggregator)
18
+ await mesh.start()
19
+
20
+ Example (wrap function):
21
+ from jarviscore import wrap, Mesh
22
+
23
+ # Wrap an existing instance
24
+ my_agent = MyLangChainAgent(llm=my_llm)
25
+ wrapped = wrap(my_agent, role="assistant", capabilities=["chat"])
26
+
27
+ mesh = Mesh(mode="autonomous")
28
+ mesh.add(wrapped)
25
29
  await mesh.start()
26
30
  """
27
31
 
28
32
  from .decorator import jarvis_agent, detect_execute_method, EXECUTE_METHODS
33
+ from .wrapper import wrap
29
34
 
30
35
  __all__ = [
31
36
  'jarvis_agent',
37
+ 'wrap',
32
38
  'detect_execute_method',
33
39
  'EXECUTE_METHODS',
34
40
  ]
@@ -188,27 +188,31 @@ def jarvis_agent(
188
188
  # Get the execute method from instance
189
189
  method = getattr(self._instance, method_name)
190
190
 
191
- # Build context if method expects it
191
+ # Get context if method expects it
192
192
  ctx = None
193
193
  if expects_context:
194
- # Get memory from mesh/workflow engine
195
- memory_dict = {}
196
- dep_manager = None
197
-
198
- if self._mesh:
199
- engine = getattr(self._mesh, '_workflow_engine', None)
200
- if engine:
201
- memory_dict = engine.memory
202
- dep_manager = getattr(engine, 'dependency_manager', None)
203
-
204
- ctx = create_context(
205
- workflow_id=task.get('context', {}).get('workflow_id', 'unknown'),
206
- step_id=task.get('context', {}).get('step_id', task.get('id', 'unknown')),
207
- task=task.get('task', ''),
208
- params=task.get('params', {}),
209
- memory_dict=memory_dict,
210
- dependency_manager=dep_manager
211
- )
194
+ # Use pre-injected context from WorkflowEngine if available
195
+ ctx = task.get('_jarvis_context')
196
+
197
+ # Fallback: build context manually (for standalone usage)
198
+ if ctx is None:
199
+ memory_dict = {}
200
+ dep_manager = None
201
+
202
+ if self._mesh:
203
+ engine = getattr(self._mesh, '_workflow_engine', None)
204
+ if engine:
205
+ memory_dict = engine.memory
206
+ dep_manager = getattr(engine, 'dependency_manager', None)
207
+
208
+ ctx = create_context(
209
+ workflow_id=task.get('context', {}).get('workflow_id', 'unknown'),
210
+ step_id=task.get('context', {}).get('step_id', task.get('id', 'unknown')),
211
+ task=task.get('task', ''),
212
+ params=task.get('params', {}),
213
+ memory_dict=memory_dict,
214
+ dependency_manager=dep_manager
215
+ )
212
216
 
213
217
  # Prepare arguments based on method signature
214
218
  args = self._prepare_args(task, ctx, params)
@@ -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.