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
@@ -0,0 +1,35 @@
1
+ async def main():
2
+ try:
3
+ # Create invoice header with company name and date
4
+ company_name = "Acme Corp"
5
+ date = "Jan 2024"
6
+
7
+ # Create formatted header
8
+ width = 50
9
+ border = "=" * width
10
+
11
+ header_lines = [
12
+ border,
13
+ "",
14
+ company_name.center(width),
15
+ "",
16
+ "INVOICE".center(width),
17
+ "",
18
+ f"Date: {date}".center(width),
19
+ "",
20
+ border
21
+ ]
22
+
23
+ report = "\n".join(header_lines)
24
+
25
+ result = {
26
+ 'report': report
27
+ }
28
+
29
+ return result
30
+
31
+ except Exception as e:
32
+ result = {
33
+ 'report': f"Error generating invoice header: {str(e)}"
34
+ }
35
+ return result
@@ -0,0 +1,35 @@
1
+ async def main():
2
+ try:
3
+ # Create invoice header with company name and date
4
+ company_name = "Acme Corp"
5
+ date = "Jan 2024"
6
+
7
+ # Create formatted header
8
+ width = 50
9
+ border = "=" * width
10
+
11
+ header_lines = [
12
+ border,
13
+ "",
14
+ company_name.center(width),
15
+ "",
16
+ "INVOICE".center(width),
17
+ "",
18
+ f"Date: {date}".center(width),
19
+ "",
20
+ border
21
+ ]
22
+
23
+ report = "\n".join(header_lines)
24
+
25
+ result = {
26
+ 'report': report
27
+ }
28
+
29
+ return result
30
+
31
+ except Exception as e:
32
+ result = {
33
+ 'report': f"Error generating invoice header: {str(e)}"
34
+ }
35
+ return result
@@ -0,0 +1,44 @@
1
+ async def main():
2
+ try:
3
+ # Get data from previous step if available, otherwise use provided data
4
+ previous_data = {'name': 'John', 'age': 30, 'city': 'NYC'}
5
+
6
+ # Check if we have previous step results
7
+ try:
8
+ if 'step0' in previous_step_results:
9
+ previous_data = previous_step_results['step0']
10
+ except NameError:
11
+ pass
12
+
13
+ name = previous_data.get('name', 'John')
14
+ age = previous_data.get('age', 30)
15
+ city = previous_data.get('city', 'NYC')
16
+
17
+ # Create formatted text report
18
+ report_lines = [
19
+ "=" * 40,
20
+ "PERSON INFORMATION REPORT",
21
+ "=" * 40,
22
+ "",
23
+ f"{'Name:':<15} {name}",
24
+ f"{'Age:':<15} {age}",
25
+ f"{'City:':<15} {city}",
26
+ "",
27
+ "-" * 40,
28
+ f"Report generated successfully",
29
+ "=" * 40
30
+ ]
31
+
32
+ report = "\n".join(report_lines)
33
+
34
+ result = {
35
+ 'report': report
36
+ }
37
+
38
+ return result
39
+
40
+ except Exception as e:
41
+ result = {
42
+ 'report': f"Error generating report: {str(e)}"
43
+ }
44
+ return result
@@ -0,0 +1,44 @@
1
+ async def main():
2
+ try:
3
+ # Get data from previous step if available, otherwise use provided defaults
4
+ previous_data = {'name': 'John', 'age': 30, 'city': 'NYC'}
5
+
6
+ # Check if we have previous step results
7
+ try:
8
+ if 'step0' in globals().get('previous_step_results', {}):
9
+ previous_data = previous_step_results['step0']
10
+ except:
11
+ pass
12
+
13
+ # Extract person data
14
+ name = previous_data.get('name', 'John')
15
+ age = previous_data.get('age', 30)
16
+ city = previous_data.get('city', 'NYC')
17
+
18
+ # Create formatted text report
19
+ report_lines = [
20
+ "=" * 40,
21
+ "PERSON INFORMATION REPORT",
22
+ "=" * 40,
23
+ "",
24
+ f"Name: {name}",
25
+ f"Age: {age}",
26
+ f"City: {city}",
27
+ "",
28
+ "-" * 40,
29
+ f"Generated for: {name}",
30
+ "=" * 40
31
+ ]
32
+
33
+ report = "\n".join(report_lines)
34
+
35
+ result = {
36
+ 'report': report
37
+ }
38
+
39
+ return result
40
+
41
+ except Exception as e:
42
+ return {
43
+ 'report': f"Error generating report: {str(e)}"
44
+ }
@@ -0,0 +1,40 @@
1
+ def main():
2
+ # Input product list
3
+ product_string = "Widget=$10, Gadget=$25, Tool=$15"
4
+
5
+ try:
6
+ # Split the string by comma and strip whitespace
7
+ products = [item.strip() for item in product_string.split(',')]
8
+
9
+ # Create bulleted list
10
+ bulleted_items = []
11
+ for product in products:
12
+ # Split by '=' to separate name and price
13
+ parts = product.split('=')
14
+ if len(parts) == 2:
15
+ name = parts[0].strip()
16
+ price = parts[1].strip()
17
+ bulleted_items.append(f"• {name}: {price}")
18
+ else:
19
+ bulleted_items.append(f"• {product}")
20
+
21
+ # Join into final bulleted text
22
+ bulleted_text = '\n'.join(bulleted_items)
23
+
24
+ result = {
25
+ 'original': product_string,
26
+ 'bulleted_list': bulleted_text,
27
+ 'item_count': len(bulleted_items),
28
+ 'items': bulleted_items
29
+ }
30
+
31
+ return result
32
+
33
+ except Exception as e:
34
+ return {
35
+ 'error': str(e),
36
+ 'original': product_string,
37
+ 'bulleted_list': None
38
+ }
39
+
40
+ result = main()
@@ -0,0 +1,37 @@
1
+ async def main():
2
+ try:
3
+ # Input product list
4
+ product_string = "Widget=$10, Gadget=$25, Tool=$15"
5
+
6
+ # Parse the product list
7
+ products = product_string.split(", ")
8
+
9
+ # Format as bulleted list
10
+ bulleted_lines = []
11
+ for product in products:
12
+ # Split by = to get name and price
13
+ parts = product.split("=")
14
+ if len(parts) == 2:
15
+ name = parts[0].strip()
16
+ price = parts[1].strip()
17
+ bulleted_lines.append(f"• {name}: {price}")
18
+
19
+ # Join into final bulleted text
20
+ bulleted_text = "\n".join(bulleted_lines)
21
+
22
+ result = {
23
+ "original": product_string,
24
+ "bulleted_list": bulleted_text,
25
+ "item_count": len(bulleted_lines),
26
+ "items": [{"name": p.split("=")[0].strip(), "price": p.split("=")[1].strip()} for p in products]
27
+ }
28
+
29
+ return result
30
+
31
+ except Exception as e:
32
+ result = {
33
+ "error": str(e),
34
+ "bulleted_list": "",
35
+ "item_count": 0
36
+ }
37
+ return result
@@ -0,0 +1,13 @@
1
+ async def main():
2
+ words = ["apple", "banana", "cherry", "date", "elderberry"]
3
+
4
+ word_lengths = {word: len(word) for word in words}
5
+ total_characters = sum(word_lengths.values())
6
+
7
+ result = {
8
+ "words": words,
9
+ "individual_lengths": word_lengths,
10
+ "total_characters": total_characters
11
+ }
12
+
13
+ return result
@@ -0,0 +1,42 @@
1
+ def main():
2
+ # Input product list
3
+ product_string = "Widget=$10, Gadget=$25, Tool=$15"
4
+
5
+ try:
6
+ # Split the string by comma and strip whitespace
7
+ products = [item.strip() for item in product_string.split(",")]
8
+
9
+ # Create bulleted list
10
+ bulleted_items = []
11
+ for product in products:
12
+ # Split by = to get name and price
13
+ parts = product.split("=")
14
+ if len(parts) == 2:
15
+ name = parts[0].strip()
16
+ price = parts[1].strip()
17
+ bulleted_items.append(f"• {name}: {price}")
18
+ else:
19
+ bulleted_items.append(f"• {product}")
20
+
21
+ # Join into final bulleted text
22
+ bulleted_text = "\n".join(bulleted_items)
23
+
24
+ result = {
25
+ "original": product_string,
26
+ "bulleted_list": bulleted_text,
27
+ "item_count": len(bulleted_items),
28
+ "items": bulleted_items
29
+ }
30
+
31
+ return result
32
+
33
+ except Exception as e:
34
+ return {
35
+ "error": str(e),
36
+ "original": product_string,
37
+ "bulleted_list": "",
38
+ "item_count": 0,
39
+ "items": []
40
+ }
41
+
42
+ result = main()
@@ -0,0 +1,13 @@
1
+ async def main():
2
+ words = ["apple", "banana", "cherry", "date", "elderberry"]
3
+
4
+ word_lengths = {word: len(word) for word in words}
5
+ total_characters = sum(word_lengths.values())
6
+
7
+ result = {
8
+ "words": words,
9
+ "individual_lengths": word_lengths,
10
+ "total_characters": total_characters
11
+ }
12
+
13
+ return result
@@ -0,0 +1,13 @@
1
+ async def main():
2
+ words = ["apple", "banana", "cherry", "date", "elderberry"]
3
+
4
+ word_lengths = {word: len(word) for word in words}
5
+ total_characters = sum(word_lengths.values())
6
+
7
+ result = {
8
+ "words": words,
9
+ "individual_lengths": word_lengths,
10
+ "total_characters": total_characters
11
+ }
12
+
13
+ return result
@@ -0,0 +1,124 @@
1
+ """
2
+ Test 1: Standalone Analyst
3
+
4
+ The Analyst agent working alone - no framework.
5
+ Can analyze data, but cannot receive requests from other agents.
6
+ """
7
+
8
+
9
+ class Analyst:
10
+ """
11
+ Standalone Analyst agent.
12
+
13
+ Capabilities:
14
+ - Analyze data
15
+ - Generate reports
16
+ - Provide recommendations
17
+ """
18
+
19
+ def __init__(self):
20
+ self.name = "analyst"
21
+ self.analyses_count = 0
22
+
23
+ def analyze(self, data: str) -> dict:
24
+ """Analyze data and return insights."""
25
+ self.analyses_count += 1
26
+ return {
27
+ "id": self.analyses_count,
28
+ "input": data,
29
+ "summary": f"Analysis of '{data}' shows positive trends",
30
+ "confidence": 0.85,
31
+ "recommendation": "Proceed with caution"
32
+ }
33
+
34
+ def generate_report(self, analysis: dict) -> str:
35
+ """Generate a text report from analysis."""
36
+ return (
37
+ f"Report #{analysis['id']}\n"
38
+ f"Summary: {analysis['summary']}\n"
39
+ f"Confidence: {analysis['confidence']}\n"
40
+ f"Recommendation: {analysis['recommendation']}"
41
+ )
42
+
43
+
44
+ # ═══════════════════════════════════════════════════════════════════════════════
45
+ # TESTS
46
+ # ═══════════════════════════════════════════════════════════════════════════════
47
+
48
+ def test_analyst_init():
49
+ """Analyst initializes correctly."""
50
+ analyst = Analyst()
51
+ assert analyst.name == "analyst"
52
+ assert analyst.analyses_count == 0
53
+ print("✓ Analyst initialized")
54
+
55
+
56
+ def test_analyst_analyze():
57
+ """Analyst can analyze data."""
58
+ analyst = Analyst()
59
+ result = analyst.analyze("Q4 sales data")
60
+
61
+ assert result["id"] == 1
62
+ assert result["confidence"] == 0.85
63
+ assert "Q4 sales data" in result["summary"]
64
+ print(f"✓ Analysis: {result['summary']}")
65
+
66
+
67
+ def test_analyst_multiple_analyses():
68
+ """Analyst tracks analysis count."""
69
+ analyst = Analyst()
70
+
71
+ analyst.analyze("data 1")
72
+ analyst.analyze("data 2")
73
+ result = analyst.analyze("data 3")
74
+
75
+ assert analyst.analyses_count == 3
76
+ assert result["id"] == 3
77
+ print(f"✓ Multiple analyses: count = {analyst.analyses_count}")
78
+
79
+
80
+ def test_analyst_generate_report():
81
+ """Analyst can generate reports."""
82
+ analyst = Analyst()
83
+ analysis = analyst.analyze("market trends")
84
+ report = analyst.generate_report(analysis)
85
+
86
+ assert "Report #1" in report
87
+ assert "market trends" in report
88
+ print(f"✓ Report generated:\n{report}")
89
+
90
+
91
+ def test_analyst_cannot_receive_requests():
92
+ """Analyst has NO way to receive external requests."""
93
+ analyst = Analyst()
94
+
95
+ # No peers
96
+ assert not hasattr(analyst, 'peers')
97
+
98
+ # No receive method
99
+ assert not hasattr(analyst, 'receive')
100
+
101
+ # No way to listen for incoming messages
102
+ assert not hasattr(analyst, 'run')
103
+
104
+ print("✓ Analyst CANNOT receive external requests (limitation)")
105
+
106
+
107
+ # ═══════════════════════════════════════════════════════════════════════════════
108
+ # RUN
109
+ # ═══════════════════════════════════════════════════════════════════════════════
110
+
111
+ if __name__ == "__main__":
112
+ print("\n" + "="*60)
113
+ print("TEST 1: STANDALONE ANALYST")
114
+ print("="*60 + "\n")
115
+
116
+ test_analyst_init()
117
+ test_analyst_analyze()
118
+ test_analyst_multiple_analyses()
119
+ test_analyst_generate_report()
120
+ test_analyst_cannot_receive_requests()
121
+
122
+ print("\n" + "-"*60)
123
+ print("Analyst works, but cannot receive requests from others.")
124
+ print("-"*60 + "\n")
@@ -0,0 +1,164 @@
1
+ """
2
+ Test 2: Standalone Assistant
3
+
4
+ The Assistant agent working alone - no framework.
5
+ Has local tools (search, calculate) but cannot talk to other agents.
6
+ """
7
+
8
+
9
+ class Assistant:
10
+ """
11
+ Standalone Assistant agent.
12
+
13
+ Capabilities:
14
+ - Search the web
15
+ - Calculate expressions
16
+ - Chat with user
17
+
18
+ Tools available to LLM:
19
+ - search
20
+ - calculate
21
+ """
22
+
23
+ def __init__(self):
24
+ self.name = "assistant"
25
+
26
+ def search(self, query: str) -> str:
27
+ """Search the web for information."""
28
+ # Simulated search
29
+ return f"Search results for '{query}': Found 10 relevant articles."
30
+
31
+ def calculate(self, expression: str) -> str:
32
+ """Calculate a math expression."""
33
+ try:
34
+ result = eval(expression)
35
+ return f"Result: {result}"
36
+ except Exception as e:
37
+ return f"Error: {e}"
38
+
39
+ def get_tools(self) -> list:
40
+ """Return tool definitions for LLM."""
41
+ return [
42
+ {
43
+ "name": "search",
44
+ "description": "Search the web for information",
45
+ "parameters": {"query": "string"}
46
+ },
47
+ {
48
+ "name": "calculate",
49
+ "description": "Calculate a math expression",
50
+ "parameters": {"expression": "string"}
51
+ }
52
+ ]
53
+
54
+ def execute_tool(self, tool_name: str, args: dict) -> str:
55
+ """Execute a tool by name."""
56
+ if tool_name == "search":
57
+ return self.search(args.get("query", ""))
58
+ elif tool_name == "calculate":
59
+ return self.calculate(args.get("expression", ""))
60
+ else:
61
+ return f"Unknown tool: {tool_name}"
62
+
63
+ def chat(self, message: str) -> str:
64
+ """Simple chat response."""
65
+ return f"I received: {message}"
66
+
67
+
68
+ # ═══════════════════════════════════════════════════════════════════════════════
69
+ # TESTS
70
+ # ═══════════════════════════════════════════════════════════════════════════════
71
+
72
+ def test_assistant_init():
73
+ """Assistant initializes correctly."""
74
+ assistant = Assistant()
75
+ assert assistant.name == "assistant"
76
+ print("✓ Assistant initialized")
77
+
78
+
79
+ def test_assistant_search():
80
+ """Assistant can search."""
81
+ assistant = Assistant()
82
+ result = assistant.search("market trends 2024")
83
+
84
+ assert "Search results" in result
85
+ assert "market trends" in result
86
+ print(f"✓ Search: {result}")
87
+
88
+
89
+ def test_assistant_calculate():
90
+ """Assistant can calculate."""
91
+ assistant = Assistant()
92
+
93
+ result1 = assistant.calculate("2 + 2")
94
+ assert "4" in result1
95
+ print(f"✓ Calculate 2+2: {result1}")
96
+
97
+ result2 = assistant.calculate("100 * 0.15")
98
+ assert "15" in result2
99
+ print(f"✓ Calculate 100*0.15: {result2}")
100
+
101
+
102
+ def test_assistant_get_tools():
103
+ """Assistant returns tool definitions."""
104
+ assistant = Assistant()
105
+ tools = assistant.get_tools()
106
+ tool_names = [t["name"] for t in tools]
107
+
108
+ assert "search" in tool_names
109
+ assert "calculate" in tool_names
110
+ assert len(tools) == 2
111
+ print(f"✓ Tools: {tool_names}")
112
+
113
+
114
+ def test_assistant_execute_tool():
115
+ """Assistant can execute tools by name."""
116
+ assistant = Assistant()
117
+
118
+ result = assistant.execute_tool("search", {"query": "python tutorials"})
119
+ assert "python tutorials" in result
120
+ print(f"✓ Execute search: {result}")
121
+
122
+ result = assistant.execute_tool("calculate", {"expression": "10 / 2"})
123
+ assert "5" in result
124
+ print(f"✓ Execute calculate: {result}")
125
+
126
+
127
+ def test_assistant_cannot_talk_to_peers():
128
+ """Assistant has NO way to talk to other agents."""
129
+ assistant = Assistant()
130
+ tools = assistant.get_tools()
131
+ tool_names = [t["name"] for t in tools]
132
+
133
+ # No peer communication tools
134
+ assert "ask_peer" not in tool_names
135
+ assert "broadcast_update" not in tool_names
136
+ assert "list_peers" not in tool_names
137
+
138
+ # No peers attribute
139
+ assert not hasattr(assistant, 'peers')
140
+
141
+ print("✓ Assistant CANNOT talk to peers (limitation)")
142
+ print(f" Available tools: {tool_names}")
143
+ print(f" Missing: ask_peer, broadcast_update, list_peers")
144
+
145
+
146
+ # ═══════════════════════════════════════════════════════════════════════════════
147
+ # RUN
148
+ # ═══════════════════════════════════════════════════════════════════════════════
149
+
150
+ if __name__ == "__main__":
151
+ print("\n" + "="*60)
152
+ print("TEST 2: STANDALONE ASSISTANT")
153
+ print("="*60 + "\n")
154
+
155
+ test_assistant_init()
156
+ test_assistant_search()
157
+ test_assistant_calculate()
158
+ test_assistant_get_tools()
159
+ test_assistant_execute_tool()
160
+ test_assistant_cannot_talk_to_peers()
161
+
162
+ print("\n" + "-"*60)
163
+ print("Assistant works, but cannot talk to other agents.")
164
+ print("-"*60 + "\n")