jarviscore-framework 0.2.1__py3-none-any.whl → 0.3.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 (37) hide show
  1. examples/cloud_deployment_example.py +162 -0
  2. examples/customagent_cognitive_discovery_example.py +343 -0
  3. examples/fastapi_integration_example.py +570 -0
  4. jarviscore/__init__.py +19 -5
  5. jarviscore/cli/smoketest.py +8 -4
  6. jarviscore/core/agent.py +227 -0
  7. jarviscore/core/mesh.py +9 -0
  8. jarviscore/data/examples/cloud_deployment_example.py +162 -0
  9. jarviscore/data/examples/custom_profile_decorator.py +134 -0
  10. jarviscore/data/examples/custom_profile_wrap.py +168 -0
  11. jarviscore/data/examples/customagent_cognitive_discovery_example.py +343 -0
  12. jarviscore/data/examples/fastapi_integration_example.py +570 -0
  13. jarviscore/docs/API_REFERENCE.md +283 -3
  14. jarviscore/docs/CHANGELOG.md +139 -0
  15. jarviscore/docs/CONFIGURATION.md +1 -1
  16. jarviscore/docs/CUSTOMAGENT_GUIDE.md +997 -85
  17. jarviscore/docs/GETTING_STARTED.md +228 -267
  18. jarviscore/docs/TROUBLESHOOTING.md +1 -1
  19. jarviscore/docs/USER_GUIDE.md +153 -8
  20. jarviscore/integrations/__init__.py +16 -0
  21. jarviscore/integrations/fastapi.py +247 -0
  22. jarviscore/p2p/broadcaster.py +10 -3
  23. jarviscore/p2p/coordinator.py +310 -14
  24. jarviscore/p2p/keepalive.py +45 -23
  25. jarviscore/p2p/peer_client.py +311 -12
  26. jarviscore/p2p/swim_manager.py +9 -4
  27. jarviscore/profiles/__init__.py +7 -1
  28. jarviscore/profiles/customagent.py +295 -74
  29. {jarviscore_framework-0.2.1.dist-info → jarviscore_framework-0.3.1.dist-info}/METADATA +66 -18
  30. {jarviscore_framework-0.2.1.dist-info → jarviscore_framework-0.3.1.dist-info}/RECORD +37 -22
  31. {jarviscore_framework-0.2.1.dist-info → jarviscore_framework-0.3.1.dist-info}/WHEEL +1 -1
  32. tests/test_13_dx_improvements.py +554 -0
  33. tests/test_14_cloud_deployment.py +403 -0
  34. tests/test_15_llm_cognitive_discovery.py +684 -0
  35. tests/test_16_unified_dx_flow.py +947 -0
  36. {jarviscore_framework-0.2.1.dist-info → jarviscore_framework-0.3.1.dist-info}/licenses/LICENSE +0 -0
  37. {jarviscore_framework-0.2.1.dist-info → jarviscore_framework-0.3.1.dist-info}/top_level.txt +0 -0
@@ -16,9 +16,12 @@ Practical guide to building agent systems with JarvisCore.
16
16
  8. [Remote Sandbox](#remote-sandbox)
17
17
  9. [Result Storage](#result-storage)
18
18
  10. [Code Registry](#code-registry)
19
- 11. [Best Practices](#best-practices)
20
- 12. [Common Patterns](#common-patterns)
21
- 13. [Troubleshooting](#troubleshooting)
19
+ 11. [FastAPI Integration (v0.3.0)](#fastapi-integration-v030)
20
+ 12. [Cloud Deployment (v0.3.0)](#cloud-deployment-v030)
21
+ 13. [Cognitive Discovery (v0.3.0)](#cognitive-discovery-v030)
22
+ 14. [Best Practices](#best-practices)
23
+ 15. [Common Patterns](#common-patterns)
24
+ 16. [Troubleshooting](#troubleshooting)
22
25
 
23
26
  ---
24
27
 
@@ -138,12 +141,12 @@ mesh = Mesh(mode="distributed", config={'bind_port': 7950})
138
141
 
139
142
  ### Agents
140
143
 
141
- **Agents** are workers that execute tasks. JarvisCore offers two profiles:
144
+ **Agents** are workers that execute tasks. JarvisCore offers three profiles:
142
145
 
143
146
  | Profile | Best For | How It Works |
144
147
  |---------|----------|--------------|
145
148
  | **AutoAgent** | Rapid prototyping | LLM generates + executes code from prompts |
146
- | **CustomAgent** | Existing code | You provide `execute_task()` or `run()` |
149
+ | **CustomAgent** | Your own code | Implement `on_peer_request()` for P2P or `execute_task()` for workflows |
147
150
 
148
151
  See [AutoAgent Guide](AUTOAGENT_GUIDE.md) and [CustomAgent Guide](CUSTOMAGENT_GUIDE.md) for details.
149
152
 
@@ -223,7 +226,7 @@ asyncio.run(data_analyst_demo())
223
226
 
224
227
  ## Custom Profile Tutorial
225
228
 
226
- The **Custom Profile** (decorator/wrap approach) is deprecated. Use **CustomAgent** instead.
229
+ Use **CustomAgent** profile.
227
230
 
228
231
  See [CustomAgent Guide](CUSTOMAGENT_GUIDE.md) for:
229
232
  - Converting standalone agents to JarvisCore
@@ -534,6 +537,148 @@ print(f"Registered: {func['registered_at']}")
534
537
 
535
538
  ---
536
539
 
540
+ ## FastAPI Integration (v0.3.0)
541
+
542
+ Deploy agents as FastAPI services with minimal boilerplate:
543
+
544
+ ### JarvisLifespan
545
+
546
+ ```python
547
+ from fastapi import FastAPI, Request
548
+ from jarviscore.profiles import CustomAgent
549
+ from jarviscore.integrations.fastapi import JarvisLifespan
550
+
551
+ class ProcessorAgent(CustomAgent):
552
+ role = "processor"
553
+ capabilities = ["processing"]
554
+
555
+ async def on_peer_request(self, msg):
556
+ return {"result": msg.data.get("task", "").upper()}
557
+
558
+ # 3 lines to integrate
559
+ agent = ProcessorAgent()
560
+ app = FastAPI(lifespan=JarvisLifespan(agent, mode="p2p", bind_port=7950))
561
+
562
+ @app.get("/peers")
563
+ async def list_peers(request: Request):
564
+ agent = request.app.state.jarvis_agents["processor"]
565
+ return {"peers": agent.peers.list_peers()}
566
+ ```
567
+
568
+ **What JarvisLifespan handles:**
569
+ - Mesh startup/shutdown
570
+ - Background task management for agent run() loops
571
+ - Graceful shutdown with timeouts
572
+ - State injection into FastAPI app
573
+
574
+ ---
575
+
576
+ ## Cloud Deployment (v0.3.0)
577
+
578
+ Deploy agents to containers without a central orchestrator:
579
+
580
+ ### Self-Registration Pattern
581
+
582
+ ```python
583
+ # In your container entrypoint
584
+ import asyncio
585
+ from jarviscore.profiles import CustomAgent
586
+
587
+ class MyAgent(CustomAgent):
588
+ role = "worker"
589
+ capabilities = ["processing"]
590
+
591
+ async def on_peer_request(self, msg):
592
+ return {"processed": msg.data}
593
+
594
+ async def main():
595
+ agent = MyAgent()
596
+
597
+ # Join existing mesh (uses JARVISCORE_SEED_NODES env var)
598
+ await agent.join_mesh()
599
+
600
+ print(f"Joined as {agent.role}, discovered: {agent.peers.list_peers()}")
601
+
602
+ # Run until shutdown, auto-leaves mesh on exit
603
+ await agent.run_standalone()
604
+
605
+ asyncio.run(main())
606
+ ```
607
+
608
+ ### Docker/Kubernetes
609
+
610
+ ```dockerfile
611
+ FROM python:3.11-slim
612
+ WORKDIR /app
613
+ COPY . .
614
+ RUN pip install jarviscore-framework
615
+
616
+ # Point to existing mesh
617
+ ENV JARVISCORE_SEED_NODES=mesh-service:7946
618
+
619
+ CMD ["python", "-m", "myapp.agent"]
620
+ ```
621
+
622
+ **Environment Variables:**
623
+ - `JARVISCORE_SEED_NODES` - Comma-separated list of seed nodes
624
+ - `JARVISCORE_MESH_ENDPOINT` - Single endpoint to join
625
+
626
+ ---
627
+
628
+ ## Cognitive Discovery (v0.3.0)
629
+
630
+ Let LLMs dynamically discover mesh peers instead of hardcoding agent names:
631
+
632
+ ### The Problem
633
+
634
+ ```python
635
+ # Before: Hardcoded peer names in prompts
636
+ system_prompt = """
637
+ You can delegate to:
638
+ - analyst for data analysis
639
+ - scout for research
640
+ """
641
+ # Breaks when mesh composition changes!
642
+ ```
643
+
644
+ ### The Solution
645
+
646
+ ```python
647
+ # After: Dynamic discovery
648
+ system_prompt = self.peers.build_system_prompt(
649
+ "You are a coordinator agent."
650
+ )
651
+ # Automatically includes all available peers!
652
+ ```
653
+
654
+ ### get_cognitive_context()
655
+
656
+ ```python
657
+ # Get prompt-ready peer descriptions
658
+ context = self.peers.get_cognitive_context(format="markdown")
659
+ ```
660
+
661
+ **Output:**
662
+ ```markdown
663
+ ## AVAILABLE MESH PEERS
664
+
665
+ You are part of a multi-agent mesh. The following peers are available:
666
+
667
+ - **analyst** (`agent-analyst-abc123`)
668
+ - Capabilities: analysis, charting, reporting
669
+ - Description: Analyzes data and generates insights
670
+
671
+ - **scout** (`agent-scout-def456`)
672
+ - Capabilities: research, reconnaissance
673
+ - Description: Gathers information
674
+
675
+ Use the `ask_peer` tool to delegate tasks to these specialists.
676
+ ```
677
+
678
+ **Formats:** `markdown`, `json`, `text`
679
+
680
+ ---
681
+
537
682
  ## Best Practices
538
683
 
539
684
  ### 1. Always Use Context Managers
@@ -759,6 +904,6 @@ mesh = Mesh(config=config)
759
904
 
760
905
  ## Version
761
906
 
762
- User Guide for JarvisCore v0.2.1
907
+ User Guide for JarvisCore v0.3.1
763
908
 
764
- Last Updated: 2026-01-23
909
+ Last Updated: 2026-02-02
@@ -0,0 +1,16 @@
1
+ """
2
+ Framework integrations for JarvisCore.
3
+
4
+ Provides first-class support for popular web frameworks,
5
+ reducing boilerplate for production deployments.
6
+
7
+ Available integrations:
8
+ - FastAPI: JarvisLifespan, create_jarvis_app
9
+ """
10
+
11
+ try:
12
+ from .fastapi import JarvisLifespan, create_jarvis_app
13
+ __all__ = ['JarvisLifespan', 'create_jarvis_app']
14
+ except ImportError:
15
+ # FastAPI not installed - integrations not available
16
+ __all__ = []
@@ -0,0 +1,247 @@
1
+ """
2
+ FastAPI integration for JarvisCore.
3
+
4
+ Reduces boilerplate from ~100 lines to 3 lines for integrating
5
+ JarvisCore agents with FastAPI applications.
6
+
7
+ Example:
8
+ from fastapi import FastAPI
9
+ from jarviscore.integrations.fastapi import JarvisLifespan
10
+
11
+ agent = MyAgent()
12
+ app = FastAPI(lifespan=JarvisLifespan(agent, mode="p2p", bind_port=7950))
13
+
14
+ @app.get("/peers")
15
+ async def get_peers(request: Request):
16
+ agent = request.app.state.jarvis_agents.get("my_role")
17
+ return {"peers": agent.peers.list_peers()}
18
+ """
19
+ from contextlib import asynccontextmanager
20
+ from typing import Union, List, TYPE_CHECKING
21
+ import asyncio
22
+ import logging
23
+
24
+ if TYPE_CHECKING:
25
+ from jarviscore.core.agent import Agent
26
+
27
+ logger = logging.getLogger(__name__)
28
+
29
+
30
+ class JarvisLifespan:
31
+ """
32
+ FastAPI lifespan manager for JarvisCore agents.
33
+
34
+ Handles the complete lifecycle of JarvisCore mesh integration:
35
+ - Mesh initialization on startup
36
+ - Background task management for agent run() loops
37
+ - Graceful shutdown with proper cleanup
38
+ - State injection into FastAPI app for handler access
39
+
40
+ Args:
41
+ agents: Single agent instance or list of agents to run
42
+ mode: Mesh mode - "p2p", "distributed", or "autonomous"
43
+ **mesh_config: Additional Mesh configuration options:
44
+ - bind_host: P2P bind address (default: "127.0.0.1")
45
+ - bind_port: P2P bind port (default: 7946)
46
+ - seed_nodes: Comma-separated seed nodes for joining cluster
47
+ - node_name: Node identifier for P2P network
48
+
49
+ Example - Single Agent:
50
+ from fastapi import FastAPI, Request
51
+ from jarviscore.integrations.fastapi import JarvisLifespan
52
+ from jarviscore.profiles import CustomAgent
53
+
54
+ class MyAgent(CustomAgent):
55
+ role = "processor"
56
+ capabilities = ["processing"]
57
+
58
+ async def on_peer_request(self, msg):
59
+ return {"processed": msg.data}
60
+
61
+ agent = MyAgent()
62
+ app = FastAPI(lifespan=JarvisLifespan(agent, mode="p2p", bind_port=7950))
63
+
64
+ @app.get("/health")
65
+ async def health(request: Request):
66
+ mesh = request.app.state.jarvis_mesh
67
+ return {"status": "ok", "mesh_started": mesh._started}
68
+
69
+ Example - Multiple Agents:
70
+ agents = [ProcessorAgent(), AnalyzerAgent()]
71
+ app = FastAPI(lifespan=JarvisLifespan(agents, mode="p2p"))
72
+
73
+ Example - Joining Existing Cluster:
74
+ app = FastAPI(lifespan=JarvisLifespan(
75
+ agent,
76
+ mode="p2p",
77
+ seed_nodes="192.168.1.10:7946,192.168.1.11:7946"
78
+ ))
79
+ """
80
+
81
+ def __init__(
82
+ self,
83
+ agents: Union['Agent', List['Agent']],
84
+ mode: str = "p2p",
85
+ **mesh_config
86
+ ):
87
+ """
88
+ Initialize JarvisLifespan.
89
+
90
+ Args:
91
+ agents: Single agent or list of agents to run
92
+ mode: Mesh mode ("p2p", "distributed", "autonomous")
93
+ **mesh_config: Additional Mesh configuration
94
+ """
95
+ self.agents = agents if isinstance(agents, list) else [agents]
96
+ self.mode = mode
97
+ self.mesh_config = mesh_config
98
+ self.mesh = None
99
+ self._background_tasks: List[asyncio.Task] = []
100
+ self._nodes: List['Agent'] = []
101
+
102
+ @asynccontextmanager
103
+ async def __call__(self, app):
104
+ """
105
+ ASGI lifespan context manager.
106
+
107
+ Called by FastAPI/Starlette on app startup/shutdown.
108
+ Manages the complete mesh lifecycle.
109
+ """
110
+ from jarviscore import Mesh
111
+
112
+ # ─────────────────────────────────────────────────────────────
113
+ # STARTUP
114
+ # ─────────────────────────────────────────────────────────────
115
+ logger.info(f"JarvisLifespan: Starting mesh in {self.mode} mode...")
116
+
117
+ # 1. Create mesh with provided configuration
118
+ self.mesh = Mesh(mode=self.mode, config=self.mesh_config)
119
+
120
+ # 2. Register all agents with the mesh
121
+ self._nodes = []
122
+ for agent in self.agents:
123
+ node = self.mesh.add(agent)
124
+ self._nodes.append(node)
125
+ logger.debug(f"JarvisLifespan: Registered agent {node.role}")
126
+
127
+ # 3. Start mesh (initializes P2P coordinator, injects PeerClients)
128
+ await self.mesh.start()
129
+ logger.info(f"JarvisLifespan: Mesh started with {len(self._nodes)} agent(s)")
130
+
131
+ # 4. Launch agent run() loops as background tasks
132
+ # This is crucial - without backgrounding, the HTTP server would hang
133
+ for node in self._nodes:
134
+ if hasattr(node, 'run') and asyncio.iscoroutinefunction(node.run):
135
+ task = asyncio.create_task(
136
+ self._run_agent_with_error_handling(node),
137
+ name=f"jarvis-agent-{node.agent_id}"
138
+ )
139
+ self._background_tasks.append(task)
140
+ logger.info(f"JarvisLifespan: Started background loop for {node.role}")
141
+
142
+ # 5. Inject state into FastAPI app for handler access
143
+ app.state.jarvis_mesh = self.mesh
144
+ app.state.jarvis_agents = {node.role: node for node in self._nodes}
145
+
146
+ logger.info("JarvisLifespan: Startup complete")
147
+
148
+ # ─────────────────────────────────────────────────────────────
149
+ # APP RUNS HERE
150
+ # ─────────────────────────────────────────────────────────────
151
+ try:
152
+ yield
153
+ finally:
154
+ # ─────────────────────────────────────────────────────────────
155
+ # SHUTDOWN
156
+ # ─────────────────────────────────────────────────────────────
157
+ logger.info("JarvisLifespan: Shutting down...")
158
+
159
+ # 1. Request shutdown for all agents (signals run() loops to exit)
160
+ for node in self._nodes:
161
+ node.request_shutdown()
162
+
163
+ # 2. Cancel background tasks gracefully with timeout
164
+ for task in self._background_tasks:
165
+ if not task.done():
166
+ task.cancel()
167
+ try:
168
+ await asyncio.wait_for(task, timeout=5.0)
169
+ except (asyncio.CancelledError, asyncio.TimeoutError):
170
+ pass
171
+
172
+ # 3. Stop mesh (cleanup P2P coordinator, call agent teardown)
173
+ if self.mesh:
174
+ await self.mesh.stop()
175
+
176
+ logger.info("JarvisLifespan: Shutdown complete")
177
+
178
+ async def _run_agent_with_error_handling(self, agent: 'Agent'):
179
+ """
180
+ Run agent loop with error handling and logging.
181
+
182
+ Wraps the agent's run() method to catch and log errors
183
+ without crashing the entire application.
184
+ """
185
+ try:
186
+ await agent.run()
187
+ except asyncio.CancelledError:
188
+ logger.debug(f"Agent {agent.agent_id} loop cancelled")
189
+ raise
190
+ except Exception as e:
191
+ logger.error(f"Agent {agent.agent_id} loop error: {e}", exc_info=True)
192
+ # Re-raise to allow task to be marked as failed
193
+ raise
194
+
195
+
196
+ def create_jarvis_app(
197
+ agent: 'Agent',
198
+ mode: str = "p2p",
199
+ title: str = "JarvisCore Agent",
200
+ description: str = "API powered by JarvisCore",
201
+ version: str = "1.0.0",
202
+ **mesh_config
203
+ ) -> 'FastAPI':
204
+ """
205
+ Create a FastAPI app with JarvisCore integration pre-configured.
206
+
207
+ Convenience function for simple single-agent deployments.
208
+ For more control, use JarvisLifespan directly.
209
+
210
+ Args:
211
+ agent: Agent instance to run
212
+ mode: Mesh mode ("p2p", "distributed", "autonomous")
213
+ title: FastAPI app title
214
+ description: FastAPI app description
215
+ version: API version
216
+ **mesh_config: Mesh configuration options
217
+
218
+ Returns:
219
+ Configured FastAPI app with JarvisCore integration
220
+
221
+ Example:
222
+ from jarviscore.integrations.fastapi import create_jarvis_app
223
+ from jarviscore.profiles import CustomAgent
224
+
225
+ class MyAgent(CustomAgent):
226
+ role = "processor"
227
+ capabilities = ["processing"]
228
+
229
+ async def on_peer_request(self, msg):
230
+ return {"result": "processed"}
231
+
232
+ app = create_jarvis_app(MyAgent(), mode="p2p", bind_port=7950)
233
+
234
+ @app.get("/health")
235
+ async def health():
236
+ return {"status": "ok"}
237
+
238
+ # Run with: uvicorn myapp:app --host 0.0.0.0 --port 8000
239
+ """
240
+ from fastapi import FastAPI
241
+
242
+ return FastAPI(
243
+ title=title,
244
+ description=description,
245
+ version=version,
246
+ lifespan=JarvisLifespan(agent, mode=mode, **mesh_config)
247
+ )
@@ -143,12 +143,19 @@ class StepOutputBroadcaster:
143
143
  """
144
144
  try:
145
145
  message_id = message_data.get('id', str(uuid.uuid4()))
146
- step_result_data = message_data.get('step_result', {})
147
-
146
+
147
+ # The sender uses 'step_output_data' as the key (json string)
148
+ step_output_json = message_data.get('step_output_data', '')
149
+ if step_output_json:
150
+ step_result_data = json.loads(step_output_json)
151
+ else:
152
+ # Fallback for legacy format
153
+ step_result_data = message_data.get('step_result', {})
154
+
148
155
  # Send acknowledgment if callback provided
149
156
  if send_ack_callback:
150
157
  await send_ack_callback(sender_swim_id, message_id, "STEP_OUTPUT_BROADCAST")
151
-
158
+
152
159
  # Create StepExecutionResult from received data
153
160
  result = StepExecutionResult(**step_result_data)
154
161