0xray 3.4.8 → 3.4.9

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 (35) hide show
  1. package/AGENTS-consumer.md +1 -1
  2. package/AGENTS.md +1 -1
  3. package/README.md +15 -1
  4. package/SKILLS.md +1 -1
  5. package/dist/AGENTS.md +1 -1
  6. package/dist/CHANGELOG.md +16 -0
  7. package/dist/README.md +15 -1
  8. package/dist/SKILLS.md +1 -1
  9. package/dist/governance/llm-governance-provider.d.ts +2 -0
  10. package/dist/governance/llm-governance-provider.js +144 -158
  11. package/dist/mcps/knowledge-skills/code-review.server.js +1 -1
  12. package/dist/mcps/knowledge-skills/security-audit.server.js +1 -1
  13. package/dist/mcps/orchestrator/config/memory-routing-bridge.js +2 -1
  14. package/dist/mcps/orchestrator/execution/execution-planner.d.ts +2 -0
  15. package/dist/mcps/orchestrator/execution/execution-planner.js +22 -11
  16. package/dist/mcps/orchestrator/handlers/complexity-handler.d.ts +13 -1
  17. package/dist/mcps/orchestrator/handlers/complexity-handler.js +40 -4
  18. package/dist/mcps/orchestrator/handlers/task-handler.js +13 -6
  19. package/dist/mcps/orchestrator/server.js +9 -1
  20. package/dist/mcps/orchestrator/types.d.ts +4 -1
  21. package/dist/mcps/researcher.server.js +1 -1
  22. package/dist/nucleus/autonomy-kernel.d.ts +5 -1
  23. package/dist/nucleus/autonomy-kernel.js +20 -16
  24. package/dist/skills/orchestrator/SKILL.md +3 -2
  25. package/package.json +1 -1
  26. package/src/mcps/knowledge-skills/code-review.server.ts +1 -1
  27. package/src/mcps/knowledge-skills/security-audit.server.ts +1 -1
  28. package/src/mcps/orchestrator/config/memory-routing-bridge.ts +3 -1
  29. package/src/mcps/orchestrator/execution/execution-planner.ts +26 -11
  30. package/src/mcps/orchestrator/handlers/complexity-handler.ts +62 -5
  31. package/src/mcps/orchestrator/handlers/task-handler.ts +22 -6
  32. package/src/mcps/orchestrator/server.ts +9 -1
  33. package/src/mcps/orchestrator/types.ts +4 -1
  34. package/src/mcps/researcher.server.ts +1 -1
  35. package/src/skills/orchestrator/SKILL.md +3 -2
@@ -7,6 +7,34 @@ import { frameworkLogger } from '../../../core/framework-logger.js';
7
7
  import { buildLeadDevPlan, isLeadDevModeActive, persistLeadDevPlan, } from '../../../nucleus/autonomy-kernel.js';
8
8
  import { getExecutionPlanner } from '../execution/execution-planner.js';
9
9
  import { addObservations, extractComplexityObservations } from '../aside-context.js';
10
+ function normalizeAnalyzeTasks(raw) {
11
+ return raw.map((task, index) => {
12
+ let deps;
13
+ if (Array.isArray(task.dependencies)) {
14
+ deps = task.dependencies;
15
+ }
16
+ else if (typeof task.dependencies === 'number' && Number.isFinite(task.dependencies)) {
17
+ deps = task.dependencies;
18
+ }
19
+ else if (typeof task.dependencyCount === 'number' &&
20
+ Number.isFinite(task.dependencyCount)) {
21
+ deps = task.dependencyCount;
22
+ }
23
+ const normalized = {
24
+ id: task.id ?? `task-${index + 1}`,
25
+ description: task.description ?? '',
26
+ type: task.type ?? 'implement',
27
+ };
28
+ if (task.priority)
29
+ normalized.priority = task.priority;
30
+ if (deps !== undefined)
31
+ normalized.dependencies = deps;
32
+ if (task.estimatedComplexity !== undefined) {
33
+ normalized.estimatedComplexity = task.estimatedComplexity;
34
+ }
35
+ return normalized;
36
+ });
37
+ }
10
38
  /**
11
39
  * Complexity Handler
12
40
  * Processes analyze-complexity requests
@@ -17,7 +45,7 @@ export class ComplexityHandler {
17
45
  * Handle analyze-complexity request
18
46
  */
19
47
  async handleAnalyzeComplexity(args, asideId) {
20
- const { tasks = [] } = args;
48
+ const tasks = normalizeAnalyzeTasks(args.tasks ?? []);
21
49
  await frameworkLogger.log('orchestrator.server', 'analyze-complexity-start', 'info', { taskCount: tasks.length });
22
50
  try {
23
51
  const analysis = await this.planner.analyzeTaskComplexity(tasks);
@@ -27,12 +55,20 @@ export class ComplexityHandler {
27
55
  }
28
56
  const primaryDescription = tasks.map((t) => t.description).filter(Boolean).join(' ');
29
57
  const taskTypes = tasks.map((t) => t.type).filter(Boolean);
30
- const leadDevPlan = isLeadDevModeActive() && primaryDescription
31
- ? buildLeadDevPlan(primaryDescription, taskTypes.length ? taskTypes : ['implement'])
58
+ const planTaskInputs = tasks
59
+ .filter((t) => t.description?.trim())
60
+ .map((t) => ({ description: t.description, type: t.type }));
61
+ const leadDevPlan = isLeadDevModeActive() && (primaryDescription || planTaskInputs.length > 0)
62
+ ? buildLeadDevPlan(primaryDescription, taskTypes.length ? taskTypes : ['implement'], planTaskInputs)
32
63
  : null;
33
64
  if (leadDevPlan) {
34
65
  try {
35
- persistLeadDevPlan(leadDevPlan);
66
+ const planPath = persistLeadDevPlan(leadDevPlan);
67
+ await frameworkLogger.log('orchestrator.server', 'lead-dev-plan-persisted', 'info', {
68
+ planPath,
69
+ phaseCount: leadDevPlan.phases.length,
70
+ todoCount: leadDevPlan.phases.reduce((n, p) => n + p.todos.length, 0),
71
+ });
36
72
  }
37
73
  catch (err) {
38
74
  await frameworkLogger.log('orchestrator.server', 'lead-dev-plan-persist-failed', 'warning', { error: err instanceof Error ? err.message : String(err) });
@@ -49,7 +49,6 @@ export class TaskHandler {
49
49
  },
50
50
  });
51
51
  }
52
- // Execute orchestration (simulated for MCP server)
53
52
  const results = await this.executePlan(executionPlan, sessionId, timeout);
54
53
  // Feed per-task outcomes back to memory provider (not aggregate session stats)
55
54
  if (memoryProvider.ingestFeedback) {
@@ -74,6 +73,8 @@ export class TaskHandler {
74
73
  orchestrationResult.success = results.success;
75
74
  orchestrationResult.completedTasks = results.completedTasks;
76
75
  orchestrationResult.failedTasks = results.failedTasks;
76
+ orchestrationResult.deferredTasks = results.deferredTasks;
77
+ orchestrationResult.recommendations.push(...results.recommendations);
77
78
  orchestrationResult.agentUtilization = results.agentUtilization;
78
79
  orchestrationResult.bottlenecks = results.bottlenecks;
79
80
  orchestrationResult.recommendations = results.recommendations;
@@ -111,6 +112,7 @@ export class TaskHandler {
111
112
  const agentUtilization = {};
112
113
  let completedTasks = 0;
113
114
  let failedTasks = 0;
115
+ let deferredTasks = 0;
114
116
  const agentOutputs = {};
115
117
  const taskOutcomes = [];
116
118
  const skillServers = new Set([
@@ -163,20 +165,23 @@ export class TaskHandler {
163
165
  }
164
166
  }
165
167
  else {
166
- // For non-MCP agents we still count them as "assigned" (they will be handled by the caller or legacy path)
167
168
  for (const task of tasks) {
168
- completedTasks++;
169
+ deferredTasks++;
169
170
  taskOutcomes.push({
170
171
  taskId: task.id,
171
172
  agent,
172
- success: true,
173
+ success: false,
173
174
  durationMs: 0,
174
175
  });
176
+ await frameworkLogger.log('orchestrator.task-handler', 'delegate-deferred', 'info', { agent, taskId: task.id, sessionId });
175
177
  }
176
178
  }
177
179
  }
178
180
  const recommendations = [];
179
181
  const bottlenecks = [];
182
+ if (deferredTasks > 0) {
183
+ recommendations.push(`${deferredTasks} task(s) require host delegation (Task/spawn_subagent per lead-dev-plan) — orchestrate-task only invokes MCP consult skills directly`);
184
+ }
180
185
  if (plan.agentAssignments.size > 5) {
181
186
  recommendations.push('Consider reducing the number of agents for better coordination');
182
187
  }
@@ -186,9 +191,10 @@ export class TaskHandler {
186
191
  }
187
192
  }
188
193
  return {
189
- success: failedTasks === 0,
194
+ success: failedTasks === 0 && deferredTasks === 0,
190
195
  completedTasks,
191
196
  failedTasks,
197
+ deferredTasks,
192
198
  agentUtilization,
193
199
  bottlenecks,
194
200
  recommendations,
@@ -232,9 +238,10 @@ export class TaskHandler {
232
238
  **Session ID:** ${result.sessionId}
233
239
  **Success:** ${result.success ? '✅ COMPLETED' : '❌ FAILED'}
234
240
  **Duration:** ${result.duration}ms
235
- **Tasks:** ${result.completedTasks + result.failedTasks} total
241
+ **Tasks:** ${result.completedTasks + result.failedTasks + (result.deferredTasks ?? 0)} total
236
242
  - ✅ Completed: ${result.completedTasks}
237
243
  - ❌ Failed: ${result.failedTasks}
244
+ - ⏸ Deferred (host delegation required): ${result.deferredTasks ?? 0}
238
245
 
239
246
  **Agent Utilization:**
240
247
  ${Object.entries(result.agentUtilization)
@@ -117,7 +117,15 @@ export class OrchestratorServer {
117
117
  description: { type: 'string' },
118
118
  type: { type: 'string' },
119
119
  files: { type: 'array', items: { type: 'string' } },
120
- dependencies: { type: 'number' },
120
+ dependencyCount: {
121
+ type: 'number',
122
+ description: 'Upstream dependency count (complexity hint only)',
123
+ },
124
+ dependencies: {
125
+ type: 'array',
126
+ items: { type: 'string' },
127
+ description: 'Task IDs this task depends on',
128
+ },
121
129
  riskLevel: {
122
130
  type: 'string',
123
131
  enum: ['low', 'medium', 'high', 'critical'],
@@ -27,7 +27,8 @@ export interface OrchestrationTask {
27
27
  description: string;
28
28
  type: string;
29
29
  priority?: 'critical' | 'high' | 'medium' | 'low';
30
- dependencies?: string[];
30
+ /** Task IDs (orchestrate-task) or dependency count hint (analyze-complexity) */
31
+ dependencies?: string[] | number;
31
32
  estimatedComplexity?: number;
32
33
  metadata?: OrchestrationTaskMetadata;
33
34
  }
@@ -43,6 +44,8 @@ export interface OrchestrationResult {
43
44
  success: boolean;
44
45
  completedTasks: number;
45
46
  failedTasks: number;
47
+ /** Implementation agents not executed by orchestrate-task — lead must delegate via host Task */
48
+ deferredTasks?: number;
46
49
  duration: number;
47
50
  agentUtilization: Record<string, number>;
48
51
  agentOutputs?: Record<string, string>;
@@ -397,7 +397,7 @@ class XrayLibrarianServer extends XrayKnowledgeSkillBase {
397
397
  if (!vote) {
398
398
  const abstainReason = memoryContext?.confidence.highConfidenceTrapPresent
399
399
  ? "No LLM governance provider configured. Repertoire detected a high-confidence ontological trap — route to the recommended agent before proceeding."
400
- : "No LLM governance provider configured. Set XRAY_GOVERNANCE_LLM_ENABLED=true + XRAY_LLM_ENDPOINT, or install Hermes and run: hermes auth add xai-oauth --no-browser";
400
+ : "No LLM governance provider configured. Install Hermes on PATH (hermes -z) with xai-oauth, or set XRAY_GOVERNANCE_LLM_ENABLED=true + XRAY_LLM_ENDPOINT.";
401
401
  return {
402
402
  content: [
403
403
  {
@@ -18,6 +18,10 @@ export interface LeadDevPhase {
18
18
  definitionOfDone: string;
19
19
  todos: LeadDevTodo[];
20
20
  }
21
+ export interface LeadDevPlanTaskInput {
22
+ description: string;
23
+ type?: string;
24
+ }
21
25
  export interface LeadDevPlan {
22
26
  active: boolean;
23
27
  rules: readonly string[];
@@ -37,7 +41,7 @@ export interface LeadDevPlan {
37
41
  export declare function isLeadDevModeActive(): boolean;
38
42
  export declare function routeSubagent(taskType: string): SubagentRoute;
39
43
  export declare function shouldFlagFullTestSuite(command: string): boolean;
40
- export declare function buildLeadDevPlan(description: string, taskTypes?: string[]): LeadDevPlan | null;
44
+ export declare function buildLeadDevPlan(description: string, taskTypes?: string[], taskInputs?: LeadDevPlanTaskInput[]): LeadDevPlan | null;
41
45
  export declare function buildSessionBootContext(): Record<string, unknown>;
42
46
  /** Persist plan for PreToolUse spawn_subagent gate */
43
47
  export declare function persistLeadDevPlan(plan: LeadDevPlan, projectRoot?: string): string;
@@ -57,14 +57,30 @@ export function shouldFlagFullTestSuite(command) {
57
57
  /\btest:\w+/.test(cmd);
58
58
  return isTest && !hasFocusedTarget;
59
59
  }
60
- export function buildLeadDevPlan(description, taskTypes = ['implement']) {
60
+ function buildImplementationTodos(description, taskTypes, taskInputs, idPrefix) {
61
+ if (taskInputs.length > 0) {
62
+ return taskInputs.map((t, i) => ({
63
+ id: `${idPrefix}.${i + 1}`,
64
+ task: t.description.slice(0, 200),
65
+ subagent: routeSubagent(t.type ?? 'implement'),
66
+ status: 'pending',
67
+ }));
68
+ }
69
+ return taskTypes.map((type, i) => ({
70
+ id: `${idPrefix}.${i + 1}`,
71
+ task: description.slice(0, 200),
72
+ subagent: routeSubagent(type),
73
+ status: 'pending',
74
+ }));
75
+ }
76
+ export function buildLeadDevPlan(description, taskTypes = ['implement'], taskInputs = []) {
61
77
  if (!isLeadDevModeActive())
62
78
  return null;
63
79
  const cfg = orchestrationConfig();
64
80
  const threshold = cfg.phased_plan_threshold ?? 25;
65
81
  const score = scoreComplexity(description, { taskTypes });
66
82
  const complexity = score.score;
67
- const requiresPhasedPlan = complexity > threshold;
83
+ const requiresPhasedPlan = complexity > threshold || taskInputs.length > 1;
68
84
  const mandatoryConsults = cfg.auto_consult_major_work !== false && requiresPhasedPlan
69
85
  ? [...MANDATORY_MAJOR_CONSULTS]
70
86
  : [];
@@ -87,12 +103,7 @@ export function buildLeadDevPlan(description, taskTypes = ['implement']) {
87
103
  name: 'Implementation',
88
104
  goal: 'Execute with best subagent per todo',
89
105
  definitionOfDone: 'Affected per-suite tests green',
90
- todos: taskTypes.map((type, i) => ({
91
- id: `2.${i + 1}`,
92
- task: description.slice(0, 200),
93
- subagent: routeSubagent(type),
94
- status: 'pending',
95
- })),
106
+ todos: buildImplementationTodos(description, taskTypes, taskInputs, '2'),
96
107
  },
97
108
  {
98
109
  id: 'phase-3',
@@ -121,14 +132,7 @@ export function buildLeadDevPlan(description, taskTypes = ['implement']) {
121
132
  name: 'Direct execution',
122
133
  goal: description.slice(0, 120),
123
134
  definitionOfDone: 'Task complete with tests green',
124
- todos: [
125
- {
126
- id: '1.1',
127
- task: description.slice(0, 200),
128
- subagent: routeSubagent(taskTypes[0] ?? 'implement'),
129
- status: 'pending',
130
- },
131
- ],
135
+ todos: buildImplementationTodos(description, taskTypes, taskInputs, '1'),
132
136
  },
133
137
  ];
134
138
  return {
@@ -36,8 +36,9 @@ When `multi_agent_orchestration.lead_dev_mode` is true in `features.json` (defau
36
36
 
37
37
  | Step | MCP tool | Notes |
38
38
  |------|----------|-------|
39
- | **Intake + classify** | `analyze-complexity` | Pass `tasks` array; response includes **lead-dev plan JSON** when mode is on |
40
- | Delegate | `orchestrate-task` | Include agent + sessionId (codex 67) |
39
+ | **Intake + classify** | `analyze-complexity` | Pass `tasks` array; persists `.xray/state/lead-dev-plan.json` when mode is on |
40
+ | Delegate (consult) | `orchestrate-task` | Invokes MCP consult skills (code-review, researcher, security-audit, etc.) |
41
+ | Delegate (implement) | **host `Task` / `spawn_subagent`** | `orchestrate-task` **defers** backend-engineer, frontend-engineer, bug-triage — lead must spawn |
41
42
  | Monitor | `get-orchestration-status` | Lead updates todos |
42
43
  | Major work | researcher + architect-tools + code-review | Auto-listed in plan when complexity > threshold |
43
44
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "0xray",
3
- "version": "3.4.8",
3
+ "version": "3.4.9",
4
4
  "description": "Multi-agent orchestration and Codex governance for OpenCode, Hermes, Grok Build, and OpenClaw",
5
5
  "readme": "README.md",
6
6
  "license": "MIT",
@@ -448,7 +448,7 @@ class XrayCodeReviewServer extends XrayKnowledgeSkillBase {
448
448
  content: [
449
449
  {
450
450
  type: "text",
451
- text: "DECISION: abstain\nCONFIDENCE: 0.50\nREASONING: No LLM governance provider configured. Set XRAY_GOVERNANCE_LLM_ENABLED=true + XRAY_LLM_ENDPOINT, or install Hermes and run: hermes auth add xai-oauth --no-browser",
451
+ text: "DECISION: abstain\nCONFIDENCE: 0.50\nREASONING: No LLM governance provider configured. Install Hermes on PATH (hermes -z) with xai-oauth, or set XRAY_GOVERNANCE_LLM_ENABLED=true + XRAY_LLM_ENDPOINT.",
452
452
  },
453
453
  ],
454
454
  };
@@ -1008,7 +1008,7 @@ class XraySecurityAuditServer extends XrayKnowledgeSkillBase {
1008
1008
  content: [
1009
1009
  {
1010
1010
  type: "text",
1011
- text: "DECISION: abstain\nCONFIDENCE: 0.50\nREASONING: No LLM governance provider configured. Set XRAY_GOVERNANCE_LLM_ENABLED=true + XRAY_LLM_ENDPOINT, or install Hermes and run: hermes auth add xai-oauth --no-browser",
1011
+ text: "DECISION: abstain\nCONFIDENCE: 0.50\nREASONING: No LLM governance provider configured. Install Hermes on PATH (hermes -z) with xai-oauth, or set XRAY_GOVERNANCE_LLM_ENABLED=true + XRAY_LLM_ENDPOINT.",
1012
1012
  },
1013
1013
  ],
1014
1014
  };
@@ -39,7 +39,9 @@ export function toMemoryTask(task: OrchestrationTask): MemoryOrchestrationTask {
39
39
  type: task.type,
40
40
  };
41
41
  if (task.priority) result.priority = task.priority;
42
- if (task.dependencies) result.dependencies = task.dependencies;
42
+ if (Array.isArray(task.dependencies) && task.dependencies.length > 0) {
43
+ result.dependencies = task.dependencies;
44
+ }
43
45
  if (task.estimatedComplexity !== undefined) result.estimatedComplexity = task.estimatedComplexity;
44
46
  if (task.metadata) result.metadata = task.metadata as Record<string, unknown>;
45
47
  return result;
@@ -17,6 +17,24 @@ import {
17
17
  fromMemoryTask,
18
18
  } from '../config/memory-routing-bridge.js';
19
19
 
20
+ /** Dependency count from task-ID list or numeric hint from analyze-complexity */
21
+ export function dependencyCount(
22
+ deps: OrchestrationTask['dependencies'] | undefined,
23
+ ): number {
24
+ if (deps == null) return 0;
25
+ if (typeof deps === 'number' && Number.isFinite(deps)) {
26
+ return Math.max(0, Math.round(deps));
27
+ }
28
+ if (Array.isArray(deps)) return deps.length;
29
+ return 0;
30
+ }
31
+
32
+ function isDependencyIdList(
33
+ deps: OrchestrationTask['dependencies'] | undefined,
34
+ ): deps is string[] {
35
+ return Array.isArray(deps) && deps.length > 0;
36
+ }
37
+
20
38
  /**
21
39
  * Execution Planner
22
40
  * Plans and optimizes task execution strategies
@@ -51,8 +69,8 @@ export class ExecutionPlanner {
51
69
  errors.push(`Task ${i}: Missing required field 'type'`);
52
70
  }
53
71
 
54
- // Check dependencies exist
55
- if (task.dependencies && task.id) {
72
+ // Check task-ID dependencies exist (numeric hints skip graph validation)
73
+ if (isDependencyIdList(task.dependencies) && task.id) {
56
74
  const taskIds = new Set(tasks.map(t => t.id).filter((id): id is string => !!id));
57
75
  for (const depId of task.dependencies) {
58
76
  if (!taskIds.has(depId)) {
@@ -162,10 +180,10 @@ export class ExecutionPlanner {
162
180
  const dependentTasks: OrchestrationTask[] = [];
163
181
 
164
182
  for (const task of sortedTasks) {
165
- if (!task.dependencies || task.dependencies.length === 0) {
166
- independentTasks.push(task);
167
- } else {
183
+ if (isDependencyIdList(task.dependencies)) {
168
184
  dependentTasks.push(task);
185
+ } else {
186
+ independentTasks.push(task);
169
187
  }
170
188
  }
171
189
 
@@ -188,7 +206,7 @@ export class ExecutionPlanner {
188
206
  if (visited.has(task.id)) return;
189
207
  visited.add(task.id);
190
208
 
191
- if (task.dependencies) {
209
+ if (isDependencyIdList(task.dependencies)) {
192
210
  for (const depId of task.dependencies) {
193
211
  const depTask = taskMap.get(depId);
194
212
  if (depTask) visit(depTask);
@@ -344,10 +362,7 @@ export class ExecutionPlanner {
344
362
  complexity = (complexity + task.estimatedComplexity) / 2;
345
363
  }
346
364
 
347
- // Adjust based on dependencies
348
- if (task.dependencies) {
349
- complexity += task.dependencies.length * 5;
350
- }
365
+ complexity += dependencyCount(task.dependencies) * 5;
351
366
 
352
367
  if (task.metadata?.memoryComplexityBoost !== undefined) {
353
368
  complexity += task.metadata.memoryComplexityBoost;
@@ -376,7 +391,7 @@ export class ExecutionPlanner {
376
391
  */
377
392
  private calculateParallelPotential(tasks: OrchestrationTask[]): number {
378
393
  // Tasks without dependencies can run in parallel
379
- const independent = tasks.filter(t => !t.dependencies || t.dependencies.length === 0);
394
+ const independent = tasks.filter((t) => !isDependencyIdList(t.dependencies));
380
395
  return independent.length / tasks.length;
381
396
  }
382
397
  }
@@ -14,6 +14,46 @@ import { getExecutionPlanner } from '../execution/execution-planner.js';
14
14
  import { addObservations, extractComplexityObservations } from '../aside-context.js';
15
15
  import type { OrchestrationTask, ComplexityAnalysis } from '../types.js';
16
16
 
17
+ type AnalyzeComplexityInput = {
18
+ id?: string;
19
+ description?: string;
20
+ type?: string;
21
+ files?: string[];
22
+ dependencyCount?: number;
23
+ dependencies?: string[] | number;
24
+ riskLevel?: string;
25
+ estimatedComplexity?: number;
26
+ priority?: OrchestrationTask['priority'];
27
+ };
28
+
29
+ function normalizeAnalyzeTasks(raw: AnalyzeComplexityInput[]): OrchestrationTask[] {
30
+ return raw.map((task, index) => {
31
+ let deps: OrchestrationTask['dependencies'];
32
+ if (Array.isArray(task.dependencies)) {
33
+ deps = task.dependencies;
34
+ } else if (typeof task.dependencies === 'number' && Number.isFinite(task.dependencies)) {
35
+ deps = task.dependencies;
36
+ } else if (
37
+ typeof task.dependencyCount === 'number' &&
38
+ Number.isFinite(task.dependencyCount)
39
+ ) {
40
+ deps = task.dependencyCount;
41
+ }
42
+
43
+ const normalized: OrchestrationTask = {
44
+ id: task.id ?? `task-${index + 1}`,
45
+ description: task.description ?? '',
46
+ type: task.type ?? 'implement',
47
+ };
48
+ if (task.priority) normalized.priority = task.priority;
49
+ if (deps !== undefined) normalized.dependencies = deps;
50
+ if (task.estimatedComplexity !== undefined) {
51
+ normalized.estimatedComplexity = task.estimatedComplexity;
52
+ }
53
+ return normalized;
54
+ });
55
+ }
56
+
17
57
  /**
18
58
  * Complexity Handler
19
59
  * Processes analyze-complexity requests
@@ -25,10 +65,10 @@ export class ComplexityHandler {
25
65
  * Handle analyze-complexity request
26
66
  */
27
67
  async handleAnalyzeComplexity(
28
- args: { tasks: OrchestrationTask[] },
68
+ args: { tasks: AnalyzeComplexityInput[] },
29
69
  asideId?: string,
30
70
  ): Promise<{ content: Array<{ type: string; text: string }> }> {
31
- const { tasks = [] } = args;
71
+ const tasks = normalizeAnalyzeTasks(args.tasks ?? []);
32
72
 
33
73
  await frameworkLogger.log(
34
74
  'orchestrator.server',
@@ -47,14 +87,31 @@ export class ComplexityHandler {
47
87
 
48
88
  const primaryDescription = tasks.map((t) => t.description).filter(Boolean).join(' ');
49
89
  const taskTypes = tasks.map((t) => t.type).filter(Boolean) as string[];
90
+ const planTaskInputs = tasks
91
+ .filter((t) => t.description?.trim())
92
+ .map((t) => ({ description: t.description, type: t.type }));
50
93
  const leadDevPlan =
51
- isLeadDevModeActive() && primaryDescription
52
- ? buildLeadDevPlan(primaryDescription, taskTypes.length ? taskTypes : ['implement'])
94
+ isLeadDevModeActive() && (primaryDescription || planTaskInputs.length > 0)
95
+ ? buildLeadDevPlan(
96
+ primaryDescription,
97
+ taskTypes.length ? taskTypes : ['implement'],
98
+ planTaskInputs,
99
+ )
53
100
  : null;
54
101
 
55
102
  if (leadDevPlan) {
56
103
  try {
57
- persistLeadDevPlan(leadDevPlan);
104
+ const planPath = persistLeadDevPlan(leadDevPlan);
105
+ await frameworkLogger.log(
106
+ 'orchestrator.server',
107
+ 'lead-dev-plan-persisted',
108
+ 'info',
109
+ {
110
+ planPath,
111
+ phaseCount: leadDevPlan.phases.length,
112
+ todoCount: leadDevPlan.phases.reduce((n, p) => n + p.todos.length, 0),
113
+ },
114
+ );
58
115
  } catch (err) {
59
116
  await frameworkLogger.log(
60
117
  'orchestrator.server',
@@ -88,7 +88,6 @@ export class TaskHandler {
88
88
  });
89
89
  }
90
90
 
91
- // Execute orchestration (simulated for MCP server)
92
91
  const results = await this.executePlan(executionPlan, sessionId, timeout);
93
92
 
94
93
  // Feed per-task outcomes back to memory provider (not aggregate session stats)
@@ -118,6 +117,8 @@ export class TaskHandler {
118
117
  orchestrationResult.success = results.success;
119
118
  orchestrationResult.completedTasks = results.completedTasks;
120
119
  orchestrationResult.failedTasks = results.failedTasks;
120
+ orchestrationResult.deferredTasks = results.deferredTasks;
121
+ orchestrationResult.recommendations.push(...results.recommendations);
121
122
  orchestrationResult.agentUtilization = results.agentUtilization;
122
123
  orchestrationResult.bottlenecks = results.bottlenecks;
123
124
  orchestrationResult.recommendations = results.recommendations;
@@ -167,6 +168,7 @@ export class TaskHandler {
167
168
  success: boolean;
168
169
  completedTasks: number;
169
170
  failedTasks: number;
171
+ deferredTasks: number;
170
172
  agentUtilization: Record<string, number>;
171
173
  bottlenecks: string[];
172
174
  recommendations: string[];
@@ -181,6 +183,7 @@ export class TaskHandler {
181
183
  const agentUtilization: Record<string, number> = {};
182
184
  let completedTasks = 0;
183
185
  let failedTasks = 0;
186
+ let deferredTasks = 0;
184
187
  const agentOutputs: Record<string, string> = {};
185
188
  const taskOutcomes: Array<{
186
189
  taskId: string;
@@ -240,15 +243,20 @@ export class TaskHandler {
240
243
  }
241
244
  }
242
245
  } else {
243
- // For non-MCP agents we still count them as "assigned" (they will be handled by the caller or legacy path)
244
246
  for (const task of tasks) {
245
- completedTasks++;
247
+ deferredTasks++;
246
248
  taskOutcomes.push({
247
249
  taskId: task.id,
248
250
  agent,
249
- success: true,
251
+ success: false,
250
252
  durationMs: 0,
251
253
  });
254
+ await frameworkLogger.log(
255
+ 'orchestrator.task-handler',
256
+ 'delegate-deferred',
257
+ 'info',
258
+ { agent, taskId: task.id, sessionId },
259
+ );
252
260
  }
253
261
  }
254
262
  }
@@ -256,6 +264,12 @@ export class TaskHandler {
256
264
  const recommendations: string[] = [];
257
265
  const bottlenecks: string[] = [];
258
266
 
267
+ if (deferredTasks > 0) {
268
+ recommendations.push(
269
+ `${deferredTasks} task(s) require host delegation (Task/spawn_subagent per lead-dev-plan) — orchestrate-task only invokes MCP consult skills directly`,
270
+ );
271
+ }
272
+
259
273
  if (plan.agentAssignments.size > 5) {
260
274
  recommendations.push('Consider reducing the number of agents for better coordination');
261
275
  }
@@ -266,9 +280,10 @@ export class TaskHandler {
266
280
  }
267
281
 
268
282
  return {
269
- success: failedTasks === 0,
283
+ success: failedTasks === 0 && deferredTasks === 0,
270
284
  completedTasks,
271
285
  failedTasks,
286
+ deferredTasks,
272
287
  agentUtilization,
273
288
  bottlenecks,
274
289
  recommendations,
@@ -321,9 +336,10 @@ export class TaskHandler {
321
336
  **Session ID:** ${result.sessionId}
322
337
  **Success:** ${result.success ? '✅ COMPLETED' : '❌ FAILED'}
323
338
  **Duration:** ${result.duration}ms
324
- **Tasks:** ${result.completedTasks + result.failedTasks} total
339
+ **Tasks:** ${result.completedTasks + result.failedTasks + (result.deferredTasks ?? 0)} total
325
340
  - ✅ Completed: ${result.completedTasks}
326
341
  - ❌ Failed: ${result.failedTasks}
342
+ - ⏸ Deferred (host delegation required): ${result.deferredTasks ?? 0}
327
343
 
328
344
  **Agent Utilization:**
329
345
  ${Object.entries(result.agentUtilization)
@@ -152,7 +152,15 @@ export class OrchestratorServer {
152
152
  description: { type: 'string' },
153
153
  type: { type: 'string' },
154
154
  files: { type: 'array', items: { type: 'string' } },
155
- dependencies: { type: 'number' },
155
+ dependencyCount: {
156
+ type: 'number',
157
+ description: 'Upstream dependency count (complexity hint only)',
158
+ },
159
+ dependencies: {
160
+ type: 'array',
161
+ items: { type: 'string' },
162
+ description: 'Task IDs this task depends on',
163
+ },
156
164
  riskLevel: {
157
165
  type: 'string',
158
166
  enum: ['low', 'medium', 'high', 'critical'],
@@ -30,7 +30,8 @@ export interface OrchestrationTask {
30
30
  description: string;
31
31
  type: string;
32
32
  priority?: 'critical' | 'high' | 'medium' | 'low';
33
- dependencies?: string[];
33
+ /** Task IDs (orchestrate-task) or dependency count hint (analyze-complexity) */
34
+ dependencies?: string[] | number;
34
35
  estimatedComplexity?: number;
35
36
  metadata?: OrchestrationTaskMetadata;
36
37
  }
@@ -48,6 +49,8 @@ export interface OrchestrationResult {
48
49
  success: boolean;
49
50
  completedTasks: number;
50
51
  failedTasks: number;
52
+ /** Implementation agents not executed by orchestrate-task — lead must delegate via host Task */
53
+ deferredTasks?: number;
51
54
  duration: number;
52
55
  agentUtilization: Record<string, number>;
53
56
  agentOutputs?: Record<string, string>;
@@ -488,7 +488,7 @@ class XrayLibrarianServer extends XrayKnowledgeSkillBase {
488
488
  if (!vote) {
489
489
  const abstainReason = memoryContext?.confidence.highConfidenceTrapPresent
490
490
  ? "No LLM governance provider configured. Repertoire detected a high-confidence ontological trap — route to the recommended agent before proceeding."
491
- : "No LLM governance provider configured. Set XRAY_GOVERNANCE_LLM_ENABLED=true + XRAY_LLM_ENDPOINT, or install Hermes and run: hermes auth add xai-oauth --no-browser";
491
+ : "No LLM governance provider configured. Install Hermes on PATH (hermes -z) with xai-oauth, or set XRAY_GOVERNANCE_LLM_ENABLED=true + XRAY_LLM_ENDPOINT.";
492
492
 
493
493
  return {
494
494
  content: [
@@ -36,8 +36,9 @@ When `multi_agent_orchestration.lead_dev_mode` is true in `features.json` (defau
36
36
 
37
37
  | Step | MCP tool | Notes |
38
38
  |------|----------|-------|
39
- | **Intake + classify** | `analyze-complexity` | Pass `tasks` array; response includes **lead-dev plan JSON** when mode is on |
40
- | Delegate | `orchestrate-task` | Include agent + sessionId (codex 67) |
39
+ | **Intake + classify** | `analyze-complexity` | Pass `tasks` array; persists `.xray/state/lead-dev-plan.json` when mode is on |
40
+ | Delegate (consult) | `orchestrate-task` | Invokes MCP consult skills (code-review, researcher, security-audit, etc.) |
41
+ | Delegate (implement) | **host `Task` / `spawn_subagent`** | `orchestrate-task` **defers** backend-engineer, frontend-engineer, bug-triage — lead must spawn |
41
42
  | Monitor | `get-orchestration-status` | Lead updates todos |
42
43
  | Major work | researcher + architect-tools + code-review | Auto-listed in plan when complexity > threshold |
43
44