nucleus-mcp 1.0.7 → 1.2.0

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 (135) hide show
  1. package/package.json +2 -4
  2. package/.github/workflows/ci.yml +0 -72
  3. package/.smithery/shttp/module.js +0 -3
  4. package/.smithery/shttp/module.js.map +0 -7
  5. package/.smithery/stdio/index.cjs +0 -74
  6. package/.smithery/stdio/index.cjs.map +0 -7
  7. package/CHANGELOG.md +0 -65
  8. package/CODE_OF_CONDUCT.md +0 -34
  9. package/COMPARISON.md +0 -20
  10. package/CONTRIBUTING.md +0 -137
  11. package/DEVELOPMENT.md +0 -109
  12. package/LICENSE +0 -21
  13. package/README.md +0 -319
  14. package/ROADMAP.md +0 -24
  15. package/SECURITY.md +0 -88
  16. package/docs/COMMUNITY_FAQ.md +0 -48
  17. package/docs/COMPETITIVE_ANALYSIS.md +0 -90
  18. package/docs/DEMO_SCRIPT.md +0 -86
  19. package/docs/ENTERPRISE.md +0 -169
  20. package/docs/FAQ.md +0 -228
  21. package/docs/QUICK_START.md +0 -155
  22. package/docs/README.md +0 -67
  23. package/docs/RECURSIVE_SNAP_DEMO.gif +0 -0
  24. package/docs/social-preview-hq.png +0 -0
  25. package/docs/social-preview.png +0 -0
  26. package/examples/basic_usage.py +0 -84
  27. package/package.json.real +0 -24
  28. package/pyproject.toml +0 -50
  29. package/registry-server.json +0 -20
  30. package/scripts/demo_governance.py +0 -109
  31. package/scripts/sse_bridge.py +0 -87
  32. package/scripts/verify_windows_paths.py +0 -48
  33. package/server.json +0 -20
  34. package/src/mcp_server_nucleus/__init__.py +0 -8583
  35. package/src/mcp_server_nucleus/__main__.py +0 -9
  36. package/src/mcp_server_nucleus/cli.py +0 -1225
  37. package/src/mcp_server_nucleus/commitment_core.py +0 -146
  38. package/src/mcp_server_nucleus/commitment_ledger.py +0 -841
  39. package/src/mcp_server_nucleus/core/__init__.py +0 -6
  40. package/src/mcp_server_nucleus/core/orchestrator.py +0 -9
  41. package/src/mcp_server_nucleus/core/tool_registration_impl.py +0 -99
  42. package/src/mcp_server_nucleus/daemon/safety/lock.py +0 -121
  43. package/src/mcp_server_nucleus/deployment/render_integration.py +0 -4
  44. package/src/mcp_server_nucleus/hypervisor/__init__.py +0 -0
  45. package/src/mcp_server_nucleus/hypervisor/injector.py +0 -83
  46. package/src/mcp_server_nucleus/hypervisor/locker.py +0 -130
  47. package/src/mcp_server_nucleus/hypervisor/watchdog.py +0 -71
  48. package/src/mcp_server_nucleus/runtime/__init__.py +0 -58
  49. package/src/mcp_server_nucleus/runtime/agent.py +0 -517
  50. package/src/mcp_server_nucleus/runtime/agent_pool.py +0 -797
  51. package/src/mcp_server_nucleus/runtime/agents/base.py +0 -34
  52. package/src/mcp_server_nucleus/runtime/agents/oracle/manifest.json +0 -28
  53. package/src/mcp_server_nucleus/runtime/auth.py +0 -98
  54. package/src/mcp_server_nucleus/runtime/autopilot.py +0 -1057
  55. package/src/mcp_server_nucleus/runtime/broker.py +0 -126
  56. package/src/mcp_server_nucleus/runtime/budget.py +0 -153
  57. package/src/mcp_server_nucleus/runtime/capabilities/base.py +0 -20
  58. package/src/mcp_server_nucleus/runtime/capabilities/brain_ops.py +0 -253
  59. package/src/mcp_server_nucleus/runtime/capabilities/code_ops.py +0 -192
  60. package/src/mcp_server_nucleus/runtime/capabilities/depth_tracker.py +0 -81
  61. package/src/mcp_server_nucleus/runtime/capabilities/feature_map.py +0 -274
  62. package/src/mcp_server_nucleus/runtime/capabilities/marketing_engine.py +0 -205
  63. package/src/mcp_server_nucleus/runtime/capabilities/memory_ops.py +0 -79
  64. package/src/mcp_server_nucleus/runtime/capabilities/proof_system.py +0 -127
  65. package/src/mcp_server_nucleus/runtime/capabilities/render_ops.py +0 -80
  66. package/src/mcp_server_nucleus/runtime/capabilities/render_poller_cap.py +0 -266
  67. package/src/mcp_server_nucleus/runtime/capabilities/self_healing.py +0 -110
  68. package/src/mcp_server_nucleus/runtime/capabilities/strategy.py +0 -138
  69. package/src/mcp_server_nucleus/runtime/capabilities/synthesizer.py +0 -134
  70. package/src/mcp_server_nucleus/runtime/capabilities/web_ops.py +0 -94
  71. package/src/mcp_server_nucleus/runtime/common.py +0 -134
  72. package/src/mcp_server_nucleus/runtime/context_manager.py +0 -342
  73. package/src/mcp_server_nucleus/runtime/context_rules.json +0 -86
  74. package/src/mcp_server_nucleus/runtime/crdt_task_store.py +0 -349
  75. package/src/mcp_server_nucleus/runtime/critic.py +0 -150
  76. package/src/mcp_server_nucleus/runtime/daemon.py +0 -142
  77. package/src/mcp_server_nucleus/runtime/dashboard.py +0 -1087
  78. package/src/mcp_server_nucleus/runtime/depth_ops.py +0 -339
  79. package/src/mcp_server_nucleus/runtime/event_ops.py +0 -103
  80. package/src/mcp_server_nucleus/runtime/event_stream.py +0 -213
  81. package/src/mcp_server_nucleus/runtime/export.py +0 -64
  82. package/src/mcp_server_nucleus/runtime/factory.py +0 -594
  83. package/src/mcp_server_nucleus/runtime/federation.py +0 -1040
  84. package/src/mcp_server_nucleus/runtime/fetcher.py +0 -92
  85. package/src/mcp_server_nucleus/runtime/file_monitor.py +0 -208
  86. package/src/mcp_server_nucleus/runtime/firestore_bridge.py +0 -91
  87. package/src/mcp_server_nucleus/runtime/gcloud_ops.py +0 -186
  88. package/src/mcp_server_nucleus/runtime/hooks.py +0 -246
  89. package/src/mcp_server_nucleus/runtime/identity/gatekeeper.py +0 -106
  90. package/src/mcp_server_nucleus/runtime/identity/keygen.py +0 -147
  91. package/src/mcp_server_nucleus/runtime/identity/manifest.py +0 -62
  92. package/src/mcp_server_nucleus/runtime/identity/trust.py +0 -27
  93. package/src/mcp_server_nucleus/runtime/inspector.py +0 -74
  94. package/src/mcp_server_nucleus/runtime/installer.py +0 -81
  95. package/src/mcp_server_nucleus/runtime/ipc_auth.py +0 -377
  96. package/src/mcp_server_nucleus/runtime/lifecycle.py +0 -129
  97. package/src/mcp_server_nucleus/runtime/llm_client.py +0 -408
  98. package/src/mcp_server_nucleus/runtime/llm_tiers.json +0 -88
  99. package/src/mcp_server_nucleus/runtime/locking.py +0 -228
  100. package/src/mcp_server_nucleus/runtime/loops/fixer.py +0 -110
  101. package/src/mcp_server_nucleus/runtime/memory.py +0 -158
  102. package/src/mcp_server_nucleus/runtime/mounter.py +0 -210
  103. package/src/mcp_server_nucleus/runtime/mounter_ops.py +0 -250
  104. package/src/mcp_server_nucleus/runtime/nuke_protocol.py +0 -147
  105. package/src/mcp_server_nucleus/runtime/orchestrator.py +0 -370
  106. package/src/mcp_server_nucleus/runtime/orchestrator_unified.py +0 -263
  107. package/src/mcp_server_nucleus/runtime/orchestrator_v3.py +0 -617
  108. package/src/mcp_server_nucleus/runtime/plugin_loader.py +0 -84
  109. package/src/mcp_server_nucleus/runtime/policy.py +0 -117
  110. package/src/mcp_server_nucleus/runtime/profiling.py +0 -219
  111. package/src/mcp_server_nucleus/runtime/prometheus.py +0 -286
  112. package/src/mcp_server_nucleus/runtime/proposals.py +0 -101
  113. package/src/mcp_server_nucleus/runtime/publisher.py +0 -109
  114. package/src/mcp_server_nucleus/runtime/pulse.py +0 -516
  115. package/src/mcp_server_nucleus/runtime/registry.py +0 -82
  116. package/src/mcp_server_nucleus/runtime/render_ops.py +0 -83
  117. package/src/mcp_server_nucleus/runtime/schema_gen.py +0 -140
  118. package/src/mcp_server_nucleus/runtime/session_ops.py +0 -224
  119. package/src/mcp_server_nucleus/runtime/stdio_server.py +0 -633
  120. package/src/mcp_server_nucleus/runtime/storage.py +0 -83
  121. package/src/mcp_server_nucleus/runtime/strategy.py +0 -91
  122. package/src/mcp_server_nucleus/runtime/swarm.py +0 -170
  123. package/src/mcp_server_nucleus/runtime/sync_ops.py +0 -878
  124. package/src/mcp_server_nucleus/runtime/task_ingestion.py +0 -1020
  125. package/src/mcp_server_nucleus/runtime/task_ops.py +0 -449
  126. package/src/mcp_server_nucleus/runtime/task_scheduler.py +0 -488
  127. package/src/mcp_server_nucleus/runtime/team.py +0 -59
  128. package/src/mcp_server_nucleus/runtime/triggers.py +0 -237
  129. package/src/mcp_server_nucleus/runtime/vector_store.py +0 -125
  130. package/src/mcp_server_nucleus/runtime/watcher.py +0 -112
  131. package/src/mcp_server_nucleus/tool_tiers.py +0 -243
  132. package/tests/__init__.py +0 -1
  133. package/tests/conftest.py +0 -42
  134. package/tests/test_core.py +0 -235
  135. package/tests/test_search.py +0 -69
@@ -1,370 +0,0 @@
1
-
2
- """
3
- Preemptive Swarms Orchestrator.
4
- The "Muscles" of the Nucleus Daemon.
5
-
6
- Strategic Role:
7
- - Manages Agent Swarms (Genesis, Execution).
8
- - Enforces "Bounded Autonomy" (Budget/Time limits).
9
- - Bridges the "Context Factory" (Tools) with the "Daemon" (Loop).
10
- - Future: Triggers "Private Graph Training" (Self-Healing).
11
-
12
- Phase 60+ Enterprise Upgrade:
13
- - REAL agent execution (not mock)
14
- - Artifact persistence per mission
15
- - Flywheel logging integration
16
- """
17
-
18
- import asyncio
19
- import json
20
- import logging
21
- import time
22
- import re
23
- from pathlib import Path
24
- from typing import Dict, Any, Optional, List
25
-
26
- from .locking import get_lock
27
- from .policy import DirectivesLoader, MissionParameters
28
- from .factory import ContextFactory
29
- import os
30
-
31
- # ============================================================
32
- # FLYWHEEL LOGGING SETUP (Bug 6 Fix)
33
- # ============================================================
34
- def _setup_flywheel_logger():
35
- """Configure flywheel.log file handler for all Nucleus components."""
36
- try:
37
- brain_path = Path(os.environ.get("NUCLEUS_BRAIN_PATH", ".brain"))
38
- log_path = brain_path / "flywheel.log"
39
-
40
- # Create handler
41
- handler = logging.FileHandler(log_path, mode='a')
42
- handler.setFormatter(logging.Formatter(
43
- '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
44
- ))
45
-
46
- # Add to nucleus logger (and all child loggers)
47
- nucleus_logger = logging.getLogger("mcp_server_nucleus")
48
-
49
- # Avoid duplicate handlers
50
- if not any(isinstance(h, logging.FileHandler) for h in nucleus_logger.handlers):
51
- nucleus_logger.addHandler(handler)
52
- nucleus_logger.setLevel(logging.INFO)
53
-
54
- except Exception as e:
55
- print(f"[Flywheel] Failed to setup logger: {e}")
56
-
57
- # Initialize on module load
58
- _setup_flywheel_logger()
59
-
60
- logger = logging.getLogger(__name__)
61
-
62
-
63
- class PrivateGraphTrainer:
64
- """
65
- Interface for Local Fine-Tuning (Path D: Neural-GPT).
66
- Currently a stub, but architecturally placed for Phase 60.
67
- """
68
- def __init__(self, brain_path: Path):
69
- self.brain_path = brain_path
70
-
71
- async def train_on_session(self, session_id: str, content: str):
72
- """
73
- Future: Fine-tune local SLM on this session.
74
- Current: Log intent.
75
- """
76
- logger.info(f"🎓 [TRAINER] Would fine-tune on session {session_id}")
77
- # Append to training dataset
78
- dataset_path = self.brain_path / "training" / "dataset.jsonl"
79
- dataset_path.parent.mkdir(parents=True, exist_ok=True)
80
- pass
81
-
82
-
83
- class SwarmsOrchestrator:
84
- """
85
- Enterprise Swarm Orchestrator with REAL agent execution.
86
- Bug 4, 6, 7 Fix: No longer a mock - spawns actual EphemeralAgents.
87
- """
88
-
89
- def __init__(self, brain_path: Path):
90
- self.brain_path = brain_path
91
- self.state_file = brain_path / "swarms" / "state.json"
92
-
93
- # Dependencies
94
- self.policy_engine = DirectivesLoader(brain_path)
95
- self.context_factory = ContextFactory(brain_path)
96
- self.trainer = PrivateGraphTrainer(brain_path)
97
-
98
- self._active_missions = {}
99
- self._load_state()
100
-
101
- def _load_state(self):
102
- """Load swarm state with BrainLock"""
103
- if not self.state_file.exists():
104
- return
105
-
106
- try:
107
- self._active_missions = json.loads(self.state_file.read_text())
108
- except Exception as e:
109
- logger.error(f"Failed to load swarm state: {e}")
110
-
111
- def _save_state(self):
112
- """Save swarm state with BrainLock"""
113
- self.state_file.parent.mkdir(parents=True, exist_ok=True)
114
- try:
115
- with get_lock("swarms", self.brain_path).section():
116
- self.state_file.write_text(json.dumps(self._active_missions, indent=2))
117
- except Exception as e:
118
- logger.error(f"Failed to save swarm state: {e}")
119
-
120
- def _parse_agents_from_goal(self, mission_goal: str) -> List[str]:
121
- """
122
- Extract agent names from mission goal text.
123
- Looks for patterns like: "delegate to 'researcher'" or "Researcher agent"
124
- """
125
- # Pattern 1: Explicit delegation syntax
126
- pattern1 = r"delegate to ['\"]?(\w+)['\"]?"
127
- matches = re.findall(pattern1, mission_goal.lower())
128
-
129
- # Pattern 2: Agent names from known personas
130
- known_personas = ["researcher", "critic", "developer", "librarian", "synthesizer", "devops", "architect", "strategist"]
131
- for persona in known_personas:
132
- if persona in mission_goal.lower():
133
- if persona not in matches:
134
- matches.append(persona)
135
-
136
- # Default if none found
137
- if not matches:
138
- matches = ["developer"]
139
-
140
- return matches
141
-
142
- async def start_mission(
143
- self,
144
- mission_goal: str,
145
- swarm_type: str = "genesis",
146
- agents: List[str] = None
147
- ) -> Dict[str, Any]:
148
- """
149
- Start a new mission with REAL agent execution.
150
-
151
- Args:
152
- mission_goal: High-level goal description
153
- swarm_type: "genesis" (planning) or "execution"
154
- agents: List of agent personas to spawn (optional)
155
- """
156
- mission_id = f"mission-{int(time.time())}"
157
- logger.info(f"🚀 Starting Mission {mission_id}: {mission_goal}")
158
-
159
- # Parse agents from goal if not provided
160
- if not agents:
161
- agents = self._parse_agents_from_goal(mission_goal)
162
-
163
- logger.info(f"📋 Mission agents: {agents}")
164
-
165
- # 1. Load Policy constraints
166
- params = self.policy_engine.get_mission_parameters(swarm_type)
167
-
168
- # 2. Initialize Mission State
169
- mission_state = {
170
- "id": mission_id,
171
- "goal": mission_goal,
172
- "type": swarm_type,
173
- "agents": agents,
174
- "status": "running",
175
- "step_count": 0,
176
- "cost_usd": 0.0,
177
- "created_at": time.time(),
178
- "max_steps": params.max_steps,
179
- "max_budget": params.max_budget_usd,
180
- "artifacts": []
181
- }
182
-
183
- self._active_missions[mission_id] = mission_state
184
- self._save_state()
185
-
186
- # 3. Spawn Background Execution via Thread (persists beyond MCP call)
187
- import threading
188
-
189
- def run_mission_in_thread():
190
- """Run the async mission loop in a new thread with its own event loop."""
191
- loop = asyncio.new_event_loop()
192
- asyncio.set_event_loop(loop)
193
- try:
194
- loop.run_until_complete(
195
- self._run_mission_loop(mission_id, mission_state, params, agents)
196
- )
197
- finally:
198
- loop.close()
199
-
200
- thread = threading.Thread(target=run_mission_in_thread, daemon=True)
201
- thread.start()
202
- logger.info(f"🚀 Mission {mission_id} thread started")
203
-
204
- return {"mission_id": mission_id, "status": "started"}
205
-
206
-
207
- async def _run_mission_loop(
208
- self,
209
- mission_id: str,
210
- state: Dict,
211
- params: MissionParameters,
212
- agents: List[str]
213
- ):
214
- """
215
- The REAL Execution Loop (Bug 4 & 7 Fix).
216
- Actually spawns EphemeralAgents and collects their output.
217
- """
218
- # Lazy imports to avoid circular dependencies
219
- from .agent import EphemeralAgent
220
- from .llm_client import DualEngineLLM
221
-
222
- try:
223
- logger.info(f"🔄 Mission {mission_id} loop started with agents: {agents}")
224
-
225
- mission_artifacts = []
226
-
227
- for i, agent_persona in enumerate(agents):
228
- # Check Bounds (Safety First)
229
- if state["step_count"] >= params.max_steps:
230
- logger.warning(f"🛑 Mission {mission_id} hit Max Steps ({params.max_steps}). halting.")
231
- state["status"] = "halted_steps"
232
- break
233
-
234
- if state["cost_usd"] >= params.max_budget_usd:
235
- logger.warning(f"💸 Mission {mission_id} hit Budget Limit (${params.max_budget_usd}). halting.")
236
- state["status"] = "halted_budget"
237
- break
238
-
239
- logger.info(f"🤖 Spawning agent {i+1}/{len(agents)}: {agent_persona}")
240
-
241
- try:
242
- # 1. Create context for this agent
243
- context = self.context_factory.create_context_for_persona(
244
- persona_name=agent_persona,
245
- intent=f"[SWARM {mission_id}] Step {i+1}/{len(agents)}: {state['goal']}",
246
- session_id=mission_id
247
- )
248
-
249
- # QUALITY FIX: Inject absolute project path into context
250
- project_root = str(self.brain_path.parent)
251
- brain_path_str = str(self.brain_path)
252
-
253
- awareness_injection = f"""
254
- # WORLD AWARENESS
255
- Current Project Root: {project_root}
256
- Nucleus Brain Path: {brain_path_str}
257
- Active Swarm Mission: {mission_id}
258
- Mission Artifacts: {brain_path_str}/swarms/{mission_id}/
259
-
260
- CRITICAL: When using files or tools, always search within the Project Root first.
261
- """
262
- context["system_prompt"] = awareness_injection + context["system_prompt"]
263
-
264
- # 2. Get LLM with correct tier routing
265
- # DualEngineLLM takes job_type in constructor and IS the model (has generate_content)
266
- job_type = context.get('job_type', 'ORCHESTRATION')
267
- model = DualEngineLLM(job_type=job_type)
268
-
269
- logger.info(f"📡 Agent {agent_persona} using job_type={job_type}, tier={model.tier}")
270
-
271
- # 3. Spawn and run agent (DualEngineLLM has generate_content method)
272
- agent = EphemeralAgent(context, model)
273
- result = await agent.run()
274
-
275
- # 4. Record results
276
- artifact = {
277
- "agent": agent_persona,
278
- "step": i + 1,
279
- "job_type": job_type,
280
- "result": result[:3000] # Truncate for storage
281
- }
282
- mission_artifacts.append(artifact)
283
- state["artifacts"].append(artifact)
284
-
285
- logger.info(f"✅ Agent {agent_persona} completed step {i+1}")
286
-
287
- except Exception as agent_error:
288
- logger.error(f"💥 Agent {agent_persona} failed: {agent_error}")
289
- mission_artifacts.append({
290
- "agent": agent_persona,
291
- "step": i + 1,
292
- "error": str(agent_error)
293
- })
294
-
295
- state["step_count"] += 1
296
- state["cost_usd"] += 0.05 # Estimated cost per agent
297
-
298
- # Save checkpoint after each agent
299
- self._active_missions[mission_id] = state
300
- self._save_state()
301
-
302
- # 5. Persist artifacts to disk (Bug 4 fix)
303
- self._save_mission_artifacts(mission_id, mission_artifacts, state['goal'])
304
-
305
- # 6. Mark completed
306
- if state["status"] == "running":
307
- state["status"] = "completed"
308
- logger.info(f"✅ Mission {mission_id} Completed with {len(mission_artifacts)} agent outputs.")
309
-
310
- # 7. Trigger Training (The "Sovereign Loop")
311
- await self.trainer.train_on_session(mission_id, json.dumps(mission_artifacts))
312
-
313
- # Final save
314
- self._active_missions[mission_id] = state
315
- self._save_state()
316
-
317
- except Exception as e:
318
- logger.error(f"💥 Mission {mission_id} Failed: {e}")
319
- state["status"] = "failed"
320
- state["error"] = str(e)
321
- self._save_state()
322
-
323
- def _save_mission_artifacts(self, mission_id: str, artifacts: List[Dict], goal: str):
324
- """
325
- Save mission artifacts to .brain/swarms/{mission_id}/summary.md
326
- Bug 4 Fix: Ensures artifacts persist to disk.
327
- """
328
- try:
329
- mission_dir = self.brain_path / "swarms" / mission_id
330
- mission_dir.mkdir(parents=True, exist_ok=True)
331
-
332
- # Save summary markdown
333
- summary_file = mission_dir / "summary.md"
334
- summary_content = f"""# Mission: {mission_id}
335
-
336
- **Goal:** {goal}
337
- **Timestamp:** {time.strftime('%Y-%m-%d %H:%M:%S')}
338
- **Agents Executed:** {len(artifacts)}
339
-
340
- ---
341
-
342
- """
343
- for artifact in artifacts:
344
- agent = artifact.get('agent', 'unknown')
345
- step = artifact.get('step', 0)
346
- result = artifact.get('result', artifact.get('error', 'No output'))
347
- job_type = artifact.get('job_type', 'unknown')
348
-
349
- summary_content += f"""## Step {step}: {agent.title()} ({job_type})
350
-
351
- ```
352
- {result}
353
- ```
354
-
355
- ---
356
-
357
- """
358
-
359
- summary_file.write_text(summary_content)
360
- logger.info(f"📄 Saved mission artifacts to {mission_dir}")
361
-
362
- # Also save raw JSON
363
- raw_file = mission_dir / "artifacts.json"
364
- raw_file.write_text(json.dumps(artifacts, indent=2))
365
-
366
- except Exception as e:
367
- logger.error(f"Failed to save mission artifacts: {e}")
368
-
369
- async def get_mission_status(self, mission_id: str) -> Optional[Dict]:
370
- return self._active_missions.get(mission_id)
@@ -1,263 +0,0 @@
1
- """
2
- Unified Nucleus Orchestrator (v0.5.0)
3
- =====================================
4
- Converges SwarmsOrchestrator (V2) with NucleusOrchestratorV3 (V3).
5
-
6
- Single entry point for:
7
- - Mission/Swarm Management (Ephemeral Agent execution).
8
- - Task/Queue Management (CRDT Store + Scheduler).
9
- - System-wide persistence and state synchronization.
10
-
11
- Strategic Role:
12
- - The "Brain" and "Muscles" of the Nucleus Daemon.
13
- - MDR_010 Compliant: High availability and reliability.
14
- """
15
-
16
- import json
17
- import time
18
- import os
19
- import logging
20
- import asyncio
21
- import threading
22
- from pathlib import Path
23
- from typing import Dict, List, Optional
24
- from datetime import datetime, timezone
25
-
26
- # V3 Core Components
27
- from .crdt_task_store import CRDTTaskStore
28
- from .task_scheduler import TaskScheduler
29
- # V2 Logic Components
30
- from .locking import get_lock
31
- from .policy import DirectivesLoader, MissionParameters
32
- from .factory import ContextFactory
33
-
34
- logger = logging.getLogger("nucleus.orchestrator")
35
-
36
- class UnifiedOrchestrator:
37
- """
38
- The Single Source of Truth for Nucleus Orchestration.
39
- Integrates mission execution with task management.
40
- """
41
-
42
- def __init__(self, brain_path: Optional[Path] = None):
43
- self.brain_path = brain_path or Path(os.getenv("NUCLEAR_BRAIN_PATH", "/Users/lokeshgarg/ai-mvp-backend/.brain"))
44
- self.replica_id = f"nucleus_{int(time.time())}"
45
-
46
- # 1. Initialize Task Core (V3)
47
- self.task_store = CRDTTaskStore(replica_id=self.replica_id)
48
- self.scheduler = TaskScheduler(max_agents=1000)
49
-
50
- # 2. Initialize Mission Logic (V2)
51
- self.policy_engine = DirectivesLoader(self.brain_path)
52
- self.context_factory = ContextFactory(self.brain_path)
53
-
54
- # 3. State Management
55
- self._active_missions = {}
56
- self.swarms_state_file = self.brain_path / "swarms" / "state.json"
57
-
58
- # Load existing datasets
59
- self._load_all_state()
60
-
61
- def _load_all_state(self):
62
- """Unified state loader."""
63
- # Load Tasks (CRDT logic handles merge)
64
- tasks_path = self.brain_path / "ledger" / "tasks.json"
65
- if tasks_path.exists():
66
- try:
67
- with open(tasks_path) as f:
68
- data = json.load(f)
69
-
70
- # V3.1+ format is {"tasks": [...]}
71
- # Older format might be just a list [...]
72
- tasks_list = data["tasks"] if isinstance(data, dict) and "tasks" in data else data
73
-
74
- if not isinstance(tasks_list, list):
75
- tasks_list = []
76
-
77
- for task in tasks_list:
78
- self.task_store.add_task(task)
79
- except Exception as e:
80
- logger.warning(f"Failed to load tasks during unification: {e}")
81
-
82
- # Load Swarms
83
- if self.swarms_state_file.exists():
84
- try:
85
- self._active_missions = json.loads(self.swarms_state_file.read_text())
86
- except Exception as e:
87
- logger.error(f"Failed to load swarm state: {e}")
88
-
89
- def _save_all_state(self):
90
- """Unified state saver."""
91
- # Save Tasks
92
- tasks_path = self.brain_path / "ledger" / "tasks.json"
93
- tasks_path.parent.mkdir(parents=True, exist_ok=True)
94
- try:
95
- crdt_tasks = self.task_store.get_all_tasks()
96
- output = {
97
- "tasks": crdt_tasks,
98
- "metadata": {
99
- "version": "0.5.0",
100
- "last_synced": datetime.now(timezone.utc).isoformat().replace("+00:00", "Z") if 'timezone' in globals() else time.strftime("%Y-%m-%dT%H:%M:%SZ")
101
- }
102
- }
103
- with open(tasks_path, "w") as f:
104
- json.dump(output, f, indent=2)
105
- except Exception as e:
106
- logger.error(f"Failed to save tasks: {e}")
107
-
108
- # Save Swarms
109
- self.swarms_state_file.parent.mkdir(parents=True, exist_ok=True)
110
- try:
111
- with get_lock("swarms", self.brain_path).section():
112
- self.swarms_state_file.write_text(json.dumps(self._active_missions, indent=2))
113
- except Exception as e:
114
- logger.error(f"Failed to save swarm state: {e}")
115
-
116
- # =========================================================================
117
- # TASK OPERATIONS (Brains)
118
- # =========================================================================
119
-
120
- def add_task(self, description: str, priority: int = 3,
121
- tier: str = "T2_CODE", blocked_by: List[str] = None,
122
- source: str = "user", required_skills: List[str] = None) -> Dict:
123
- task_id = f"task_{int(time.time() * 1000)}"
124
- task = {
125
- "id": task_id,
126
- "description": description,
127
- "status": "PENDING",
128
- "tier": tier,
129
- "priority": priority,
130
- "blocked_by": blocked_by or [],
131
- "required_skills": required_skills or [],
132
- "source": source,
133
- "created_at": time.strftime("%Y-%m-%dT%H:%M:%SZ"),
134
- "checkpoint": None,
135
- "context_summary": None
136
- }
137
- self.task_store.add_task(task)
138
- self._save_all_state()
139
- return {"success": True, "task": task}
140
-
141
- def get_task(self, task_id: str) -> Optional[Dict]:
142
- return self.task_store.get_task(task_id)
143
-
144
- def update_task(self, task_id: str, updates: Dict) -> Dict:
145
- task = self.task_store.get_task(task_id)
146
- if not task:
147
- return {"success": False, "error": f"Task {task_id} not found"}
148
- for key, value in updates.items():
149
- if key != "id":
150
- task[key] = value
151
- self.task_store.update_task(task_id, task)
152
- self._save_all_state()
153
- return {"success": True, "task": task}
154
-
155
- def get_next_task(self, skills: List[str] = None) -> Optional[Dict]:
156
- tasks = self.task_store.get_all_tasks()
157
- available = [t for t in tasks if t.get("status") in ["PENDING", "READY"]]
158
- if not available:
159
- return None
160
- available.sort(key=lambda x: x.get("priority", 3))
161
- return available[0]
162
-
163
- # =========================================================================
164
- # MISSION OPERATIONS (Muscles)
165
- # =========================================================================
166
-
167
- async def start_mission(self, goal: str, swarm_type: str = "genesis",
168
- agents: List[str] = None) -> Dict:
169
- mission_id = f"mission-{int(time.time())}"
170
- logger.info(f"🚀 [UNIFIED] Starting Mission {mission_id}")
171
-
172
- if not agents:
173
- agents = self._parse_agents_from_goal(goal)
174
-
175
- params = self.policy_engine.get_mission_parameters(swarm_type)
176
-
177
- mission_state = {
178
- "id": mission_id,
179
- "goal": goal,
180
- "type": swarm_type,
181
- "agents": agents,
182
- "status": "running",
183
- "step_count": 0,
184
- "created_at": time.time(),
185
- "artifacts": []
186
- }
187
-
188
- self._active_missions[mission_id] = mission_state
189
- self._save_all_state()
190
-
191
- # Background execution
192
- threading.Thread(target=lambda: self._run_mission_background(mission_id, mission_state, params), daemon=True).start()
193
-
194
- return {"mission_id": mission_id, "status": "started"}
195
-
196
- def _parse_agents_from_goal(self, goal: str) -> List[str]:
197
- known = ["researcher", "critic", "developer", "architect"]
198
- matches = [p for p in known if p in goal.lower()]
199
- return matches if matches else ["developer"]
200
-
201
- def _run_mission_background(self, mission_id: str, state: Dict, params: MissionParameters):
202
- loop = asyncio.new_event_loop()
203
- asyncio.set_event_loop(loop)
204
- try:
205
- loop.run_until_complete(self._run_mission_loop(mission_id, state, params))
206
- except Exception as e:
207
- logger.error(f"Mission {mission_id} thread-loop crashed: {e}")
208
- finally:
209
- loop.close()
210
-
211
- async def _run_mission_loop(self, mission_id: str, state: Dict, params: MissionParameters):
212
- from .agent import EphemeralAgent
213
- from .llm_client import DualEngineLLM
214
-
215
- logger.info(f"🔄 [UNIFIED] Loop started for {mission_id}")
216
- mission_artifacts = []
217
-
218
- for i, persona in enumerate(state["agents"]):
219
- if state["step_count"] >= params.max_steps:
220
- break
221
-
222
- logger.info(f"🤖 Step {i+1}: Spawning {persona}")
223
- context = self.context_factory.create_context_for_persona(
224
- persona_name=persona,
225
- intent=f"[MISSION {mission_id}] {state['goal']}",
226
- session_id=mission_id
227
- )
228
-
229
- model = DualEngineLLM(job_type=context.get('job_type', 'ORCHESTRATION'))
230
- agent = EphemeralAgent(context, model)
231
- result = await agent.run()
232
-
233
- artifact = {"agent": persona, "step": i+1, "result": result[:5000]}
234
- mission_artifacts.append(artifact)
235
- state["artifacts"].append(artifact)
236
- state["step_count"] += 1
237
-
238
- self._save_all_state()
239
-
240
- self._save_mission_artifacts_to_disk(mission_id, mission_artifacts, state['goal'])
241
- state["status"] = "completed"
242
- self._save_all_state()
243
-
244
- def _save_mission_artifacts_to_disk(self, mission_id: str, artifacts: List[Dict], goal: str):
245
- mission_dir = self.brain_path / "swarms" / mission_id
246
- mission_dir.mkdir(parents=True, exist_ok=True)
247
- summary = f"# Mission {mission_id}\n\nGoal: {goal}\n\n"
248
- for a in artifacts:
249
- summary += f"## {a['agent']} (Step {a['step']})\n{a['result']}\n\n---\n\n"
250
- (mission_dir / "summary.md").write_text(summary)
251
- (mission_dir / "artifacts.json").write_text(json.dumps(artifacts, indent=2))
252
-
253
- def get_mission_status(self, mission_id: str) -> Optional[Dict]:
254
- return self._active_missions.get(mission_id)
255
-
256
- # Singleton management
257
- _orchestrator = None
258
-
259
- def get_orchestrator() -> UnifiedOrchestrator:
260
- global _orchestrator
261
- if _orchestrator is None:
262
- _orchestrator = UnifiedOrchestrator()
263
- return _orchestrator