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
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
|
-
-
|
|
8
|
-
|
|
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
|
-
|
|
13
|
-
from
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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(
|
|
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 (
|
|
25
|
-
from jarviscore import Mesh
|
|
32
|
+
Quick Start (CustomAgent - distributed mode):
|
|
33
|
+
from jarviscore import Mesh
|
|
34
|
+
from jarviscore.profiles import CustomAgent
|
|
26
35
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
return {"processed": data * 2}
|
|
36
|
+
class MyAgent(CustomAgent):
|
|
37
|
+
role = "processor"
|
|
38
|
+
capabilities = ["processing"]
|
|
31
39
|
|
|
32
|
-
|
|
33
|
-
|
|
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="
|
|
40
|
-
mesh.add(
|
|
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.
|
|
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
|
|
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
|
]
|
jarviscore/adapter/__init__.py
CHANGED
|
@@ -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.
|
|
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
|
]
|
jarviscore/adapter/decorator.py
CHANGED
|
@@ -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
|
-
#
|
|
191
|
+
# Get context if method expects it
|
|
192
192
|
ctx = None
|
|
193
193
|
if expects_context:
|
|
194
|
-
#
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
if
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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
|
+
}
|
jarviscore/cli/scaffold.py
CHANGED
|
@@ -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}")
|
jarviscore/cli/smoketest.py
CHANGED
|
@@ -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.
|
|
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.
|