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.
- package/package.json +2 -4
- package/.github/workflows/ci.yml +0 -72
- package/.smithery/shttp/module.js +0 -3
- package/.smithery/shttp/module.js.map +0 -7
- package/.smithery/stdio/index.cjs +0 -74
- package/.smithery/stdio/index.cjs.map +0 -7
- package/CHANGELOG.md +0 -65
- package/CODE_OF_CONDUCT.md +0 -34
- package/COMPARISON.md +0 -20
- package/CONTRIBUTING.md +0 -137
- package/DEVELOPMENT.md +0 -109
- package/LICENSE +0 -21
- package/README.md +0 -319
- package/ROADMAP.md +0 -24
- package/SECURITY.md +0 -88
- package/docs/COMMUNITY_FAQ.md +0 -48
- package/docs/COMPETITIVE_ANALYSIS.md +0 -90
- package/docs/DEMO_SCRIPT.md +0 -86
- package/docs/ENTERPRISE.md +0 -169
- package/docs/FAQ.md +0 -228
- package/docs/QUICK_START.md +0 -155
- package/docs/README.md +0 -67
- package/docs/RECURSIVE_SNAP_DEMO.gif +0 -0
- package/docs/social-preview-hq.png +0 -0
- package/docs/social-preview.png +0 -0
- package/examples/basic_usage.py +0 -84
- package/package.json.real +0 -24
- package/pyproject.toml +0 -50
- package/registry-server.json +0 -20
- package/scripts/demo_governance.py +0 -109
- package/scripts/sse_bridge.py +0 -87
- package/scripts/verify_windows_paths.py +0 -48
- package/server.json +0 -20
- package/src/mcp_server_nucleus/__init__.py +0 -8583
- package/src/mcp_server_nucleus/__main__.py +0 -9
- package/src/mcp_server_nucleus/cli.py +0 -1225
- package/src/mcp_server_nucleus/commitment_core.py +0 -146
- package/src/mcp_server_nucleus/commitment_ledger.py +0 -841
- package/src/mcp_server_nucleus/core/__init__.py +0 -6
- package/src/mcp_server_nucleus/core/orchestrator.py +0 -9
- package/src/mcp_server_nucleus/core/tool_registration_impl.py +0 -99
- package/src/mcp_server_nucleus/daemon/safety/lock.py +0 -121
- package/src/mcp_server_nucleus/deployment/render_integration.py +0 -4
- package/src/mcp_server_nucleus/hypervisor/__init__.py +0 -0
- package/src/mcp_server_nucleus/hypervisor/injector.py +0 -83
- package/src/mcp_server_nucleus/hypervisor/locker.py +0 -130
- package/src/mcp_server_nucleus/hypervisor/watchdog.py +0 -71
- package/src/mcp_server_nucleus/runtime/__init__.py +0 -58
- package/src/mcp_server_nucleus/runtime/agent.py +0 -517
- package/src/mcp_server_nucleus/runtime/agent_pool.py +0 -797
- package/src/mcp_server_nucleus/runtime/agents/base.py +0 -34
- package/src/mcp_server_nucleus/runtime/agents/oracle/manifest.json +0 -28
- package/src/mcp_server_nucleus/runtime/auth.py +0 -98
- package/src/mcp_server_nucleus/runtime/autopilot.py +0 -1057
- package/src/mcp_server_nucleus/runtime/broker.py +0 -126
- package/src/mcp_server_nucleus/runtime/budget.py +0 -153
- package/src/mcp_server_nucleus/runtime/capabilities/base.py +0 -20
- package/src/mcp_server_nucleus/runtime/capabilities/brain_ops.py +0 -253
- package/src/mcp_server_nucleus/runtime/capabilities/code_ops.py +0 -192
- package/src/mcp_server_nucleus/runtime/capabilities/depth_tracker.py +0 -81
- package/src/mcp_server_nucleus/runtime/capabilities/feature_map.py +0 -274
- package/src/mcp_server_nucleus/runtime/capabilities/marketing_engine.py +0 -205
- package/src/mcp_server_nucleus/runtime/capabilities/memory_ops.py +0 -79
- package/src/mcp_server_nucleus/runtime/capabilities/proof_system.py +0 -127
- package/src/mcp_server_nucleus/runtime/capabilities/render_ops.py +0 -80
- package/src/mcp_server_nucleus/runtime/capabilities/render_poller_cap.py +0 -266
- package/src/mcp_server_nucleus/runtime/capabilities/self_healing.py +0 -110
- package/src/mcp_server_nucleus/runtime/capabilities/strategy.py +0 -138
- package/src/mcp_server_nucleus/runtime/capabilities/synthesizer.py +0 -134
- package/src/mcp_server_nucleus/runtime/capabilities/web_ops.py +0 -94
- package/src/mcp_server_nucleus/runtime/common.py +0 -134
- package/src/mcp_server_nucleus/runtime/context_manager.py +0 -342
- package/src/mcp_server_nucleus/runtime/context_rules.json +0 -86
- package/src/mcp_server_nucleus/runtime/crdt_task_store.py +0 -349
- package/src/mcp_server_nucleus/runtime/critic.py +0 -150
- package/src/mcp_server_nucleus/runtime/daemon.py +0 -142
- package/src/mcp_server_nucleus/runtime/dashboard.py +0 -1087
- package/src/mcp_server_nucleus/runtime/depth_ops.py +0 -339
- package/src/mcp_server_nucleus/runtime/event_ops.py +0 -103
- package/src/mcp_server_nucleus/runtime/event_stream.py +0 -213
- package/src/mcp_server_nucleus/runtime/export.py +0 -64
- package/src/mcp_server_nucleus/runtime/factory.py +0 -594
- package/src/mcp_server_nucleus/runtime/federation.py +0 -1040
- package/src/mcp_server_nucleus/runtime/fetcher.py +0 -92
- package/src/mcp_server_nucleus/runtime/file_monitor.py +0 -208
- package/src/mcp_server_nucleus/runtime/firestore_bridge.py +0 -91
- package/src/mcp_server_nucleus/runtime/gcloud_ops.py +0 -186
- package/src/mcp_server_nucleus/runtime/hooks.py +0 -246
- package/src/mcp_server_nucleus/runtime/identity/gatekeeper.py +0 -106
- package/src/mcp_server_nucleus/runtime/identity/keygen.py +0 -147
- package/src/mcp_server_nucleus/runtime/identity/manifest.py +0 -62
- package/src/mcp_server_nucleus/runtime/identity/trust.py +0 -27
- package/src/mcp_server_nucleus/runtime/inspector.py +0 -74
- package/src/mcp_server_nucleus/runtime/installer.py +0 -81
- package/src/mcp_server_nucleus/runtime/ipc_auth.py +0 -377
- package/src/mcp_server_nucleus/runtime/lifecycle.py +0 -129
- package/src/mcp_server_nucleus/runtime/llm_client.py +0 -408
- package/src/mcp_server_nucleus/runtime/llm_tiers.json +0 -88
- package/src/mcp_server_nucleus/runtime/locking.py +0 -228
- package/src/mcp_server_nucleus/runtime/loops/fixer.py +0 -110
- package/src/mcp_server_nucleus/runtime/memory.py +0 -158
- package/src/mcp_server_nucleus/runtime/mounter.py +0 -210
- package/src/mcp_server_nucleus/runtime/mounter_ops.py +0 -250
- package/src/mcp_server_nucleus/runtime/nuke_protocol.py +0 -147
- package/src/mcp_server_nucleus/runtime/orchestrator.py +0 -370
- package/src/mcp_server_nucleus/runtime/orchestrator_unified.py +0 -263
- package/src/mcp_server_nucleus/runtime/orchestrator_v3.py +0 -617
- package/src/mcp_server_nucleus/runtime/plugin_loader.py +0 -84
- package/src/mcp_server_nucleus/runtime/policy.py +0 -117
- package/src/mcp_server_nucleus/runtime/profiling.py +0 -219
- package/src/mcp_server_nucleus/runtime/prometheus.py +0 -286
- package/src/mcp_server_nucleus/runtime/proposals.py +0 -101
- package/src/mcp_server_nucleus/runtime/publisher.py +0 -109
- package/src/mcp_server_nucleus/runtime/pulse.py +0 -516
- package/src/mcp_server_nucleus/runtime/registry.py +0 -82
- package/src/mcp_server_nucleus/runtime/render_ops.py +0 -83
- package/src/mcp_server_nucleus/runtime/schema_gen.py +0 -140
- package/src/mcp_server_nucleus/runtime/session_ops.py +0 -224
- package/src/mcp_server_nucleus/runtime/stdio_server.py +0 -633
- package/src/mcp_server_nucleus/runtime/storage.py +0 -83
- package/src/mcp_server_nucleus/runtime/strategy.py +0 -91
- package/src/mcp_server_nucleus/runtime/swarm.py +0 -170
- package/src/mcp_server_nucleus/runtime/sync_ops.py +0 -878
- package/src/mcp_server_nucleus/runtime/task_ingestion.py +0 -1020
- package/src/mcp_server_nucleus/runtime/task_ops.py +0 -449
- package/src/mcp_server_nucleus/runtime/task_scheduler.py +0 -488
- package/src/mcp_server_nucleus/runtime/team.py +0 -59
- package/src/mcp_server_nucleus/runtime/triggers.py +0 -237
- package/src/mcp_server_nucleus/runtime/vector_store.py +0 -125
- package/src/mcp_server_nucleus/runtime/watcher.py +0 -112
- package/src/mcp_server_nucleus/tool_tiers.py +0 -243
- package/tests/__init__.py +0 -1
- package/tests/conftest.py +0 -42
- package/tests/test_core.py +0 -235
- 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
|