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,488 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
TaskScheduler - Intelligent Task Assignment Engine
|
|
3
|
-
Implements Priority Queue + Agent State Machine + Dependency Resolution
|
|
4
|
-
|
|
5
|
-
Path A: Simple, deterministic, scales 1→1000 agents
|
|
6
|
-
- Priority queue per tier (T1, T2, T3, T4)
|
|
7
|
-
- Agent capacity tracking (busy vs available)
|
|
8
|
-
- Dependency resolution (blocked tasks)
|
|
9
|
-
- FIFO fairness within tier
|
|
10
|
-
- Deadline-aware scheduling
|
|
11
|
-
- Zero conflicts guarantee
|
|
12
|
-
|
|
13
|
-
Author: NOP V3 - January 2026
|
|
14
|
-
"""
|
|
15
|
-
|
|
16
|
-
import time
|
|
17
|
-
import threading
|
|
18
|
-
from typing import Dict, List, Optional, Set
|
|
19
|
-
from collections import defaultdict
|
|
20
|
-
from copy import deepcopy
|
|
21
|
-
from enum import Enum
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
class TaskStatus(str, Enum):
|
|
25
|
-
"""Task status in scheduler."""
|
|
26
|
-
PENDING = "PENDING"
|
|
27
|
-
SCHEDULED = "SCHEDULED"
|
|
28
|
-
BLOCKED = "BLOCKED"
|
|
29
|
-
ASSIGNED = "ASSIGNED"
|
|
30
|
-
COMPLETED = "COMPLETED"
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
class TaskPriority(str, Enum):
|
|
34
|
-
"""Task priority levels."""
|
|
35
|
-
HIGH = "HIGH"
|
|
36
|
-
MEDIUM = "MEDIUM"
|
|
37
|
-
LOW = "LOW"
|
|
38
|
-
|
|
39
|
-
def sort_value(self) -> int:
|
|
40
|
-
"""Return numeric value for sorting (lower = higher priority)."""
|
|
41
|
-
return {"HIGH": 0, "MEDIUM": 1, "LOW": 2}[self.value]
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
class TaskTier(str, Enum):
|
|
45
|
-
"""Task difficulty tiers."""
|
|
46
|
-
T1_PLANNING = "T1_PLANNING"
|
|
47
|
-
T2_CODE = "T2_CODE"
|
|
48
|
-
T3_REVIEW = "T3_REVIEW"
|
|
49
|
-
T4_DEPLOY = "T4_DEPLOY"
|
|
50
|
-
|
|
51
|
-
def sort_value(self) -> int:
|
|
52
|
-
"""Return numeric value for tier ordering."""
|
|
53
|
-
return {"T1_PLANNING": 0, "T2_CODE": 1, "T3_REVIEW": 2, "T4_DEPLOY": 3}[self.value]
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
class AgentState:
|
|
57
|
-
"""Represents state of a single agent."""
|
|
58
|
-
|
|
59
|
-
def __init__(self, agent_id: str, tier: str, capacity: int = 5):
|
|
60
|
-
self.id = agent_id
|
|
61
|
-
self.tier = tier
|
|
62
|
-
self.capacity = capacity
|
|
63
|
-
self.current_tasks: Set[str] = set()
|
|
64
|
-
self.available = True
|
|
65
|
-
self.last_heartbeat = int(time.time() * 1000)
|
|
66
|
-
self.tasks_completed = 0
|
|
67
|
-
|
|
68
|
-
def is_available(self) -> bool:
|
|
69
|
-
"""Check if agent has capacity for new task."""
|
|
70
|
-
return len(self.current_tasks) < self.capacity
|
|
71
|
-
|
|
72
|
-
def assign_task(self, task_id: str) -> bool:
|
|
73
|
-
"""Assign task to agent if capacity available."""
|
|
74
|
-
if not self.is_available():
|
|
75
|
-
return False
|
|
76
|
-
self.current_tasks.add(task_id)
|
|
77
|
-
return True
|
|
78
|
-
|
|
79
|
-
def complete_task(self, task_id: str) -> bool:
|
|
80
|
-
"""Mark task as completed, free capacity."""
|
|
81
|
-
if task_id in self.current_tasks:
|
|
82
|
-
self.current_tasks.remove(task_id)
|
|
83
|
-
self.tasks_completed += 1
|
|
84
|
-
return True
|
|
85
|
-
return False
|
|
86
|
-
|
|
87
|
-
def to_dict(self) -> Dict:
|
|
88
|
-
"""Convert to dictionary."""
|
|
89
|
-
return {
|
|
90
|
-
"id": self.id,
|
|
91
|
-
"tier": self.tier,
|
|
92
|
-
"capacity": self.capacity,
|
|
93
|
-
"current_tasks": list(self.current_tasks),
|
|
94
|
-
"available": self.is_available(),
|
|
95
|
-
"last_heartbeat": self.last_heartbeat,
|
|
96
|
-
"tasks_completed": self.tasks_completed,
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
class ScheduleDecision:
|
|
101
|
-
"""Represents a scheduling decision for a task."""
|
|
102
|
-
|
|
103
|
-
def __init__(
|
|
104
|
-
self,
|
|
105
|
-
task_id: str,
|
|
106
|
-
agent_id: Optional[str] = None,
|
|
107
|
-
reason: str = "queued",
|
|
108
|
-
):
|
|
109
|
-
self.task_id = task_id
|
|
110
|
-
self.agent_id = agent_id
|
|
111
|
-
self.scheduled_at = int(time.time() * 1000)
|
|
112
|
-
self.reason = reason # assigned, blocked, queued
|
|
113
|
-
|
|
114
|
-
def to_dict(self) -> Dict:
|
|
115
|
-
"""Convert to dictionary."""
|
|
116
|
-
return {
|
|
117
|
-
"task_id": self.task_id,
|
|
118
|
-
"agent_id": self.agent_id,
|
|
119
|
-
"scheduled_at": self.scheduled_at,
|
|
120
|
-
"reason": self.reason,
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
class TaskScheduler:
|
|
125
|
-
"""
|
|
126
|
-
Intelligent task assignment engine.
|
|
127
|
-
|
|
128
|
-
Key guarantees:
|
|
129
|
-
- Zero scheduling conflicts: Each task assigned to at most one agent
|
|
130
|
-
- Fairness: Tasks distributed evenly across agents in tier
|
|
131
|
-
- Dependency resolution: Blocked tasks queued, not scheduled
|
|
132
|
-
- Deadline-aware: Urgent tasks scheduled first
|
|
133
|
-
- FIFO within tier: Stable ordering
|
|
134
|
-
- Capacity respected: No over-booking agents
|
|
135
|
-
- Tier matching: Tasks only assigned to agents of matching tier
|
|
136
|
-
- Performance: <500ms for 100K tasks
|
|
137
|
-
|
|
138
|
-
Single-process implementation (thread-safe).
|
|
139
|
-
Multi-agent coordination ready for future distributed scheduler.
|
|
140
|
-
"""
|
|
141
|
-
|
|
142
|
-
def __init__(self, max_agents: int = 1000):
|
|
143
|
-
"""
|
|
144
|
-
Initialize task scheduler.
|
|
145
|
-
|
|
146
|
-
Args:
|
|
147
|
-
max_agents: Maximum number of agents (for allocation)
|
|
148
|
-
"""
|
|
149
|
-
self.max_agents = max_agents
|
|
150
|
-
self.agent_registry: Dict[str, AgentState] = {} # agent_id -> AgentState
|
|
151
|
-
self.task_states: Dict[str, Dict] = {} # task_id -> TaskState
|
|
152
|
-
self.dependency_graph: Dict[str, Set[str]] = defaultdict(set) # task_id -> blocked_by task_ids
|
|
153
|
-
self.task_queues: Dict[str, List] = {
|
|
154
|
-
"T1_PLANNING": [],
|
|
155
|
-
"T2_CODE": [],
|
|
156
|
-
"T3_REVIEW": [],
|
|
157
|
-
"T4_DEPLOY": [],
|
|
158
|
-
} # tier -> priority queue
|
|
159
|
-
self.lock = threading.RLock()
|
|
160
|
-
self.stats = {
|
|
161
|
-
"scheduled": 0,
|
|
162
|
-
"blocked": 0,
|
|
163
|
-
"queued": 0,
|
|
164
|
-
"completed": 0,
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
def register_agent(self, agent_id: str, tier: str, capacity: int = 5) -> Dict:
|
|
168
|
-
"""
|
|
169
|
-
Register new agent into scheduler.
|
|
170
|
-
|
|
171
|
-
Args:
|
|
172
|
-
agent_id: Unique agent identifier
|
|
173
|
-
tier: Task tier (T1_PLANNING, T2_CODE, T3_REVIEW, T4_DEPLOY)
|
|
174
|
-
capacity: Max concurrent tasks this agent can handle
|
|
175
|
-
|
|
176
|
-
Returns:
|
|
177
|
-
AgentState as dictionary
|
|
178
|
-
"""
|
|
179
|
-
with self.lock:
|
|
180
|
-
if agent_id in self.agent_registry:
|
|
181
|
-
raise ValueError(f"Agent {agent_id} already registered")
|
|
182
|
-
|
|
183
|
-
agent = AgentState(agent_id, tier, capacity)
|
|
184
|
-
self.agent_registry[agent_id] = agent
|
|
185
|
-
return agent.to_dict()
|
|
186
|
-
|
|
187
|
-
def unregister_agent(self, agent_id: str) -> bool:
|
|
188
|
-
"""
|
|
189
|
-
Unregister agent from scheduler.
|
|
190
|
-
|
|
191
|
-
Args:
|
|
192
|
-
agent_id: Agent to unregister
|
|
193
|
-
|
|
194
|
-
Returns:
|
|
195
|
-
True if unregistered, False if not found
|
|
196
|
-
"""
|
|
197
|
-
with self.lock:
|
|
198
|
-
if agent_id not in self.agent_registry:
|
|
199
|
-
return False
|
|
200
|
-
|
|
201
|
-
agent = self.agent_registry[agent_id]
|
|
202
|
-
|
|
203
|
-
# Free all assigned tasks
|
|
204
|
-
for task_id in list(agent.current_tasks):
|
|
205
|
-
if task_id in self.task_states:
|
|
206
|
-
self.task_states[task_id]["assigned_to"] = None
|
|
207
|
-
self.task_states[task_id]["status"] = TaskStatus.PENDING.value
|
|
208
|
-
|
|
209
|
-
del self.agent_registry[agent_id]
|
|
210
|
-
return True
|
|
211
|
-
|
|
212
|
-
def mark_task_done(self, task_id: str, agent_id: str) -> bool:
|
|
213
|
-
"""
|
|
214
|
-
Mark task as completed, free agent capacity.
|
|
215
|
-
|
|
216
|
-
Args:
|
|
217
|
-
task_id: Task that completed
|
|
218
|
-
agent_id: Agent that completed it
|
|
219
|
-
|
|
220
|
-
Returns:
|
|
221
|
-
True if marked done, False if not found
|
|
222
|
-
"""
|
|
223
|
-
with self.lock:
|
|
224
|
-
if agent_id not in self.agent_registry:
|
|
225
|
-
return False
|
|
226
|
-
|
|
227
|
-
agent = self.agent_registry[agent_id]
|
|
228
|
-
if not agent.complete_task(task_id):
|
|
229
|
-
return False
|
|
230
|
-
|
|
231
|
-
if task_id in self.task_states:
|
|
232
|
-
self.task_states[task_id]["status"] = TaskStatus.COMPLETED.value
|
|
233
|
-
self.stats["completed"] += 1
|
|
234
|
-
|
|
235
|
-
return True
|
|
236
|
-
|
|
237
|
-
def resolve_dependencies(self, task_id: str) -> bool:
|
|
238
|
-
"""
|
|
239
|
-
Check if all dependencies of a task are satisfied.
|
|
240
|
-
|
|
241
|
-
Args:
|
|
242
|
-
task_id: Task to check
|
|
243
|
-
|
|
244
|
-
Returns:
|
|
245
|
-
True if all blocked_by tasks are completed, False otherwise
|
|
246
|
-
"""
|
|
247
|
-
with self.lock:
|
|
248
|
-
if task_id not in self.task_states:
|
|
249
|
-
return False
|
|
250
|
-
|
|
251
|
-
blocked_by = self.task_states[task_id].get("blocked_by", [])
|
|
252
|
-
|
|
253
|
-
for blocker_id in blocked_by:
|
|
254
|
-
if blocker_id not in self.task_states:
|
|
255
|
-
continue # Blocker not in scheduler
|
|
256
|
-
|
|
257
|
-
blocker_status = self.task_states[blocker_id].get("status")
|
|
258
|
-
if blocker_status != TaskStatus.COMPLETED.value:
|
|
259
|
-
return False # Blocker not completed
|
|
260
|
-
|
|
261
|
-
return True # All blockers completed
|
|
262
|
-
|
|
263
|
-
def schedule_batch(
|
|
264
|
-
self,
|
|
265
|
-
tasks: List[Dict],
|
|
266
|
-
force_schedule: bool = False,
|
|
267
|
-
) -> List[Dict]:
|
|
268
|
-
"""
|
|
269
|
-
Schedule a batch of tasks across agents.
|
|
270
|
-
|
|
271
|
-
Args:
|
|
272
|
-
tasks: List of task dicts with id, tier, priority, blocked_by, deadline
|
|
273
|
-
force_schedule: If True, use best-effort (may over-book)
|
|
274
|
-
|
|
275
|
-
Returns:
|
|
276
|
-
List of ScheduleDecision dicts (assigned, blocked, queued)
|
|
277
|
-
|
|
278
|
-
Algorithm:
|
|
279
|
-
1. Add tasks to internal state
|
|
280
|
-
2. Resolve dependencies (mark blocked)
|
|
281
|
-
3. Sort by: priority, deadline, FIFO (created_at)
|
|
282
|
-
4. For each non-blocked task:
|
|
283
|
-
- Find agent with matching tier + available capacity
|
|
284
|
-
- Assign task to agent
|
|
285
|
-
- Mark as scheduled
|
|
286
|
-
5. Return decisions
|
|
287
|
-
"""
|
|
288
|
-
with self.lock:
|
|
289
|
-
decisions = []
|
|
290
|
-
time.time()
|
|
291
|
-
|
|
292
|
-
# Phase 1: Add tasks to internal state
|
|
293
|
-
for task in tasks:
|
|
294
|
-
task_id = task.get("id")
|
|
295
|
-
if task_id in self.task_states:
|
|
296
|
-
continue # Already tracked
|
|
297
|
-
|
|
298
|
-
self.task_states[task_id] = {
|
|
299
|
-
"id": task_id,
|
|
300
|
-
"status": TaskStatus.PENDING.value,
|
|
301
|
-
"tier": task.get("tier", "T1_PLANNING"),
|
|
302
|
-
"priority": task.get("priority", "MEDIUM"),
|
|
303
|
-
"deadline": task.get("deadline"),
|
|
304
|
-
"created_at": task.get("created_at", int(time.time() * 1000)),
|
|
305
|
-
"assigned_to": None,
|
|
306
|
-
"blocked_by": task.get("blocked_by", []),
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
# Phase 2: Resolve dependencies, categorize tasks
|
|
310
|
-
blocked_tasks = set()
|
|
311
|
-
pending_tasks = []
|
|
312
|
-
|
|
313
|
-
for task in tasks:
|
|
314
|
-
task_id = task.get("id")
|
|
315
|
-
task_state = self.task_states[task_id]
|
|
316
|
-
|
|
317
|
-
# Check if task is blocked
|
|
318
|
-
is_blocked = not self.resolve_dependencies(task_id)
|
|
319
|
-
|
|
320
|
-
if is_blocked:
|
|
321
|
-
blocked_tasks.add(task_id)
|
|
322
|
-
task_state["status"] = TaskStatus.BLOCKED.value
|
|
323
|
-
decisions.append(
|
|
324
|
-
ScheduleDecision(task_id, reason="blocked").to_dict()
|
|
325
|
-
)
|
|
326
|
-
self.stats["blocked"] += 1
|
|
327
|
-
else:
|
|
328
|
-
pending_tasks.append(task_state)
|
|
329
|
-
|
|
330
|
-
# Phase 3: Sort pending tasks by priority/deadline/FIFO
|
|
331
|
-
def sort_key(task_state):
|
|
332
|
-
priority = TaskPriority[task_state["priority"]].sort_value()
|
|
333
|
-
deadline = task_state.get("deadline") or float("inf")
|
|
334
|
-
created_at = task_state.get("created_at", float("inf"))
|
|
335
|
-
# Sort by: priority (low first), deadline (early first), FIFO (early created first)
|
|
336
|
-
return (priority, deadline, created_at)
|
|
337
|
-
|
|
338
|
-
pending_tasks.sort(key=sort_key)
|
|
339
|
-
|
|
340
|
-
# Phase 4: Assign tasks to agents
|
|
341
|
-
for task_state in pending_tasks:
|
|
342
|
-
task_id = task_state["id"]
|
|
343
|
-
tier = task_state["tier"]
|
|
344
|
-
|
|
345
|
-
# Find available agent with matching tier
|
|
346
|
-
assigned_agent = None
|
|
347
|
-
for agent in self.agent_registry.values():
|
|
348
|
-
if agent.tier == tier and agent.is_available():
|
|
349
|
-
assigned_agent = agent
|
|
350
|
-
break
|
|
351
|
-
|
|
352
|
-
if assigned_agent:
|
|
353
|
-
# Assign task to agent
|
|
354
|
-
if assigned_agent.assign_task(task_id):
|
|
355
|
-
task_state["status"] = TaskStatus.SCHEDULED.value
|
|
356
|
-
task_state["assigned_to"] = assigned_agent.id
|
|
357
|
-
decisions.append(
|
|
358
|
-
ScheduleDecision(
|
|
359
|
-
task_id, agent_id=assigned_agent.id, reason="assigned"
|
|
360
|
-
).to_dict()
|
|
361
|
-
)
|
|
362
|
-
self.stats["scheduled"] += 1
|
|
363
|
-
else:
|
|
364
|
-
# Capacity exhausted
|
|
365
|
-
task_state["status"] = TaskStatus.PENDING.value
|
|
366
|
-
decisions.append(
|
|
367
|
-
ScheduleDecision(task_id, reason="queued").to_dict()
|
|
368
|
-
)
|
|
369
|
-
self.stats["queued"] += 1
|
|
370
|
-
else:
|
|
371
|
-
# No available agent with matching tier
|
|
372
|
-
task_state["status"] = TaskStatus.PENDING.value
|
|
373
|
-
decisions.append(
|
|
374
|
-
ScheduleDecision(task_id, reason="queued").to_dict()
|
|
375
|
-
)
|
|
376
|
-
self.stats["queued"] += 1
|
|
377
|
-
|
|
378
|
-
return decisions
|
|
379
|
-
|
|
380
|
-
def get_agent_state(self, agent_id: str) -> Optional[Dict]:
|
|
381
|
-
"""
|
|
382
|
-
Get current state of an agent.
|
|
383
|
-
|
|
384
|
-
Args:
|
|
385
|
-
agent_id: Agent to query
|
|
386
|
-
|
|
387
|
-
Returns:
|
|
388
|
-
AgentState dict or None if not found
|
|
389
|
-
"""
|
|
390
|
-
with self.lock:
|
|
391
|
-
agent = self.agent_registry.get(agent_id)
|
|
392
|
-
return agent.to_dict() if agent else None
|
|
393
|
-
|
|
394
|
-
def get_all_agents(self) -> List[Dict]:
|
|
395
|
-
"""
|
|
396
|
-
Get all registered agents.
|
|
397
|
-
|
|
398
|
-
Returns:
|
|
399
|
-
List of AgentState dicts
|
|
400
|
-
"""
|
|
401
|
-
with self.lock:
|
|
402
|
-
return [agent.to_dict() for agent in self.agent_registry.values()]
|
|
403
|
-
|
|
404
|
-
def get_pending_tasks(self, agent_id: str) -> List[Dict]:
|
|
405
|
-
"""
|
|
406
|
-
Get tasks assigned to an agent (pending execution).
|
|
407
|
-
|
|
408
|
-
Args:
|
|
409
|
-
agent_id: Agent to query
|
|
410
|
-
|
|
411
|
-
Returns:
|
|
412
|
-
List of task dicts
|
|
413
|
-
"""
|
|
414
|
-
with self.lock:
|
|
415
|
-
if agent_id not in self.agent_registry:
|
|
416
|
-
return []
|
|
417
|
-
|
|
418
|
-
agent = self.agent_registry[agent_id]
|
|
419
|
-
pending = []
|
|
420
|
-
|
|
421
|
-
for task_id in agent.current_tasks:
|
|
422
|
-
if task_id in self.task_states:
|
|
423
|
-
pending.append(deepcopy(self.task_states[task_id]))
|
|
424
|
-
|
|
425
|
-
return pending
|
|
426
|
-
|
|
427
|
-
def get_scheduling_stats(self) -> Dict:
|
|
428
|
-
"""
|
|
429
|
-
Get scheduler statistics.
|
|
430
|
-
|
|
431
|
-
Returns:
|
|
432
|
-
Dict with scheduled, blocked, queued, completed counts
|
|
433
|
-
"""
|
|
434
|
-
with self.lock:
|
|
435
|
-
return {
|
|
436
|
-
"scheduled": self.stats["scheduled"],
|
|
437
|
-
"blocked": self.stats["blocked"],
|
|
438
|
-
"queued": self.stats["queued"],
|
|
439
|
-
"completed": self.stats["completed"],
|
|
440
|
-
"total_agents": len(self.agent_registry),
|
|
441
|
-
"total_tasks": len(self.task_states),
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
def get_task_state(self, task_id: str) -> Optional[Dict]:
|
|
445
|
-
"""
|
|
446
|
-
Get state of a specific task.
|
|
447
|
-
|
|
448
|
-
Args:
|
|
449
|
-
task_id: Task to query
|
|
450
|
-
|
|
451
|
-
Returns:
|
|
452
|
-
TaskState dict or None if not found
|
|
453
|
-
"""
|
|
454
|
-
with self.lock:
|
|
455
|
-
return deepcopy(self.task_states.get(task_id))
|
|
456
|
-
|
|
457
|
-
def get_fairness_metrics(self) -> Dict:
|
|
458
|
-
"""
|
|
459
|
-
Get fairness metrics for each tier.
|
|
460
|
-
|
|
461
|
-
Returns:
|
|
462
|
-
Dict with per-tier fairness stats (task distribution across agents)
|
|
463
|
-
"""
|
|
464
|
-
with self.lock:
|
|
465
|
-
fairness = {}
|
|
466
|
-
|
|
467
|
-
for tier in ["T1_PLANNING", "T2_CODE", "T3_REVIEW", "T4_DEPLOY"]:
|
|
468
|
-
tier_agents = [
|
|
469
|
-
a for a in self.agent_registry.values() if a.tier == tier
|
|
470
|
-
]
|
|
471
|
-
|
|
472
|
-
if not tier_agents:
|
|
473
|
-
continue
|
|
474
|
-
|
|
475
|
-
task_counts = [len(a.current_tasks) for a in tier_agents]
|
|
476
|
-
avg_tasks = sum(task_counts) / len(tier_agents) if tier_agents else 0
|
|
477
|
-
variance = sum((x - avg_tasks) ** 2 for x in task_counts) / len(
|
|
478
|
-
task_counts
|
|
479
|
-
) if tier_agents else 0
|
|
480
|
-
|
|
481
|
-
fairness[tier] = {
|
|
482
|
-
"agents": len(tier_agents),
|
|
483
|
-
"task_counts": task_counts,
|
|
484
|
-
"avg_tasks_per_agent": avg_tasks,
|
|
485
|
-
"variance": variance,
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
return fairness
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
TeamManager: The Hive Mind.
|
|
3
|
-
Manages Shared Configuration and Trust Roots for the Team.
|
|
4
|
-
|
|
5
|
-
Strategic Role:
|
|
6
|
-
- SYNC: Ensures all agents on the team use the same Registry.
|
|
7
|
-
- TRUST: Distributes the Team Lead's public keys.
|
|
8
|
-
- POLICY: Enforces team-wide rules (e.g. "Signed Only").
|
|
9
|
-
"""
|
|
10
|
-
|
|
11
|
-
import json
|
|
12
|
-
import logging
|
|
13
|
-
from typing import List, Optional
|
|
14
|
-
from pathlib import Path
|
|
15
|
-
from pydantic import BaseModel, Field
|
|
16
|
-
|
|
17
|
-
logger = logging.getLogger("TEAM")
|
|
18
|
-
|
|
19
|
-
class TeamPolicy(BaseModel):
|
|
20
|
-
require_signed: bool = True
|
|
21
|
-
min_trust_level: str = "verified"
|
|
22
|
-
|
|
23
|
-
class TeamConfig(BaseModel):
|
|
24
|
-
team_id: str
|
|
25
|
-
name: str
|
|
26
|
-
registry_url: Optional[str] = None
|
|
27
|
-
trusted_keys: List[str] = []
|
|
28
|
-
policy: TeamPolicy = Field(default_factory=TeamPolicy)
|
|
29
|
-
|
|
30
|
-
class TeamManager:
|
|
31
|
-
def __init__(self, brain_path: Path):
|
|
32
|
-
self.brain_path = brain_path
|
|
33
|
-
self.config_path = brain_path / "config" / "team.json"
|
|
34
|
-
|
|
35
|
-
def get_config(self) -> Optional[TeamConfig]:
|
|
36
|
-
"""Load and return the current team configuration."""
|
|
37
|
-
if not self.config_path.exists():
|
|
38
|
-
return None
|
|
39
|
-
|
|
40
|
-
try:
|
|
41
|
-
data = json.loads(self.config_path.read_text())
|
|
42
|
-
return TeamConfig(**data)
|
|
43
|
-
except Exception as e:
|
|
44
|
-
logger.error(f"Failed to load team config: {e}")
|
|
45
|
-
return None
|
|
46
|
-
|
|
47
|
-
def get_trusted_roots(self) -> List[str]:
|
|
48
|
-
"""Return the list of trusted key fingerprints defined by the team."""
|
|
49
|
-
config = self.get_config()
|
|
50
|
-
if config:
|
|
51
|
-
return config.trusted_keys
|
|
52
|
-
return []
|
|
53
|
-
|
|
54
|
-
def get_registry_url(self) -> Optional[str]:
|
|
55
|
-
"""Return the team's private registry URL if configured."""
|
|
56
|
-
config = self.get_config()
|
|
57
|
-
if config:
|
|
58
|
-
return config.registry_url
|
|
59
|
-
return None
|