Skip to content

Commit dd31fe7

Browse files
authored
refactor(ai): extract configured task composition (#13875) (#13876)
1 parent b42d5f7 commit dd31fe7

5 files changed

Lines changed: 328 additions & 263 deletions

File tree

ai/daemons/orchestrator/Orchestrator.mjs

Lines changed: 35 additions & 241 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,47 @@
11
// Class bootstrap belongs to `daemon.mjs`; this consumed class relies on global Neo.
2-
import fs from 'fs-extra';
3-
import {spawn} from 'child_process';
4-
import net from 'net';
5-
import path from 'path';
6-
import Base from '../../../src/core/Base.mjs';
7-
import ClassSystemUtil from '../../../src/util/ClassSystem.mjs';
8-
import AiConfig from '../../config.mjs';
9-
import neuralLinkConfig from '../../mcp/server/neural-link/config.mjs';
10-
import {
11-
buildLmsPreloadConfig,
12-
buildOllamaReadinessConfig
13-
} from '../../services/graph/providerReadinessHelper.mjs';
14-
import HealthService from '../../services/memory-core/HealthService.mjs';
15-
import SQLite from '../../graph/storage/SQLite.mjs';
2+
import fs from 'fs-extra';
3+
import {spawn} from 'child_process';
4+
import net from 'net';
5+
import path from 'path';
6+
import Base from '../../../src/core/Base.mjs';
7+
import ClassSystemUtil from '../../../src/util/ClassSystem.mjs';
8+
import AiConfig from '../../config.mjs';
9+
import HealthService from '../../services/memory-core/HealthService.mjs';
10+
import SQLite from '../../graph/storage/SQLite.mjs';
1611
import MaintenanceBackpressureService, {
1712
DEFAULT_HEAVY_MAINTENANCE_TASK_NAMES,
1813
DEFAULT_GOLDEN_PATH_DEPENDENCY_TASK_NAMES
1914
} from './services/MaintenanceBackpressureService.mjs';
20-
import PrimaryRepoSyncService from './services/PrimaryRepoSyncService.mjs';
21-
import TenantRepoSyncService from './services/TenantRepoSyncService.mjs';
22-
import {getDueTask as summaryGetDueTaskImport} from './scheduling/summary.mjs';
23-
import {getDueTask as backupGetDueTaskImport} from './scheduling/backup.mjs';
24-
import {getDueTask as graphLogCompactionGetDueTaskImport} from './scheduling/graphLogCompaction.mjs';
25-
import {getDueTask as primaryDevSyncGetDueTaskImport} from './scheduling/primaryDevSync.mjs';
26-
import {getDueTask as goldenPathGetDueTaskImport} from './scheduling/goldenPath.mjs';
27-
import {getDueTask as dreamGetDueTaskImport} from './scheduling/dream.mjs';
28-
import {getDueTask as embedDrainLivenessWatchdogGetDueTaskImport} from './scheduling/embedDrainLivenessWatchdog.mjs';
29-
import memoryCoreConfig from '../../mcp/server/memory-core/config.mjs';
30-
import MailboxService from '../../services/memory-core/MailboxService.mjs';
31-
import WakeSubscriptionService from '../../services/memory-core/WakeSubscriptionService.mjs';
32-
import RequestContextService from '../../mcp/server/shared/services/RequestContextService.mjs';
33-
import {normalizeAgentIdentityNodeId} from '../../scripts/lifecycle/resumeHarness.mjs';
34-
import TaskStateService from './services/TaskStateService.mjs';
35-
import ProcessSupervisorService from './services/ProcessSupervisorService.mjs';
36-
import DreamService from './services/DreamService.mjs';
37-
import SwarmHeartbeatService from './services/SwarmHeartbeatService.mjs';
38-
import GoldenPathSynthesizer from '../../services/graph/GoldenPathSynthesizer.mjs';
39-
import {getDueTask as tenantRepoSyncGetDueTaskImport} from './scheduling/tenantRepoSync.mjs';
40-
import {TASK_REGISTRY} from './scheduling/registry.mjs';
15+
import {buildConfiguredTaskDefinitions as buildConfiguredTaskDefinitionsImport} from './services/ConfiguredTaskDefinitionsService.mjs';
16+
import PrimaryRepoSyncService from './services/PrimaryRepoSyncService.mjs';
17+
import TenantRepoSyncService from './services/TenantRepoSyncService.mjs';
18+
import {getDueTask as summaryGetDueTaskImport} from './scheduling/summary.mjs';
19+
import {getDueTask as backupGetDueTaskImport} from './scheduling/backup.mjs';
20+
import {getDueTask as graphLogCompactionGetDueTaskImport} from './scheduling/graphLogCompaction.mjs';
21+
import {getDueTask as primaryDevSyncGetDueTaskImport} from './scheduling/primaryDevSync.mjs';
22+
import {getDueTask as goldenPathGetDueTaskImport} from './scheduling/goldenPath.mjs';
23+
import {getDueTask as dreamGetDueTaskImport} from './scheduling/dream.mjs';
24+
import {getDueTask as embedDrainLivenessWatchdogGetDueTaskImport} from './scheduling/embedDrainLivenessWatchdog.mjs';
25+
import memoryCoreConfig from '../../mcp/server/memory-core/config.mjs';
26+
import MailboxService from '../../services/memory-core/MailboxService.mjs';
27+
import WakeSubscriptionService from '../../services/memory-core/WakeSubscriptionService.mjs';
28+
import RequestContextService from '../../mcp/server/shared/services/RequestContextService.mjs';
29+
import {normalizeAgentIdentityNodeId} from '../../scripts/lifecycle/resumeHarness.mjs';
30+
import TaskStateService from './services/TaskStateService.mjs';
31+
import ProcessSupervisorService from './services/ProcessSupervisorService.mjs';
32+
import DreamService from './services/DreamService.mjs';
33+
import SwarmHeartbeatService from './services/SwarmHeartbeatService.mjs';
34+
import GoldenPathSynthesizer from '../../services/graph/GoldenPathSynthesizer.mjs';
35+
import {getDueTask as tenantRepoSyncGetDueTaskImport} from './scheduling/tenantRepoSync.mjs';
36+
import {TASK_REGISTRY} from './scheduling/registry.mjs';
4137
import {
4238
buildOrchestratorSchedulingOptions,
4339
runSchedulingPipeline
4440
} from './scheduling/pipeline.mjs';
4541
import {
4642
DEFAULT_DB_PATH,
4743
DEFAULT_DATA_DIR,
48-
DEFAULT_SCRIPT_DIR,
49-
buildOllamaServeEnv,
50-
getMaxOllamaContextLength,
51-
resolveOllamaHostPort,
52-
buildTaskDefinitions
44+
DEFAULT_SCRIPT_DIR
5345
} from './taskDefinitions.mjs';
5446

5547
/** @summary Opens/creates the orchestrator sqlite DB via the shared Memory Core schema bootstrap. */
@@ -135,6 +127,7 @@ export class Orchestrator extends Base {
135127
dreamGetDueTask = dreamGetDueTaskImport
136128
goldenPathGetDueTask = goldenPathGetDueTaskImport
137129
embedDrainLivenessWatchdogGetDueTask = embedDrainLivenessWatchdogGetDueTaskImport
130+
buildConfiguredTaskDefinitionsService = buildConfiguredTaskDefinitionsImport
138131

139132
isPolling = false
140133
pollHandle = null
@@ -350,217 +343,18 @@ export class Orchestrator extends Base {
350343
get neuralLinkBridgeLivenessTimeoutMs() { return AiConfig.orchestrator.neuralLinkBridge.livenessProbeTimeoutMs; }
351344

352345
/**
353-
* @summary Builds the supervised task table from the daemon entrypoint's config authorities.
346+
* @summary Delegates config-backed task-table construction to the orchestrator task builder.
354347
* @param {Object} options
355348
* @param {String} options.scriptDir Script directory.
356349
* @param {String} options.nodeBin Node executable.
357350
* @returns {Object}
358351
*/
359352
buildConfiguredTaskDefinitions({scriptDir, nodeBin}) {
360-
const tasks = buildTaskDefinitions({
353+
return this.buildConfiguredTaskDefinitionsService({
361354
scriptDir,
362355
nodeBin,
363-
chromaPort : AiConfig.engines.chroma.port,
364-
devServerPort : AiConfig.orchestrator.devServer.port,
365-
devServerLivenessTimeoutMs : AiConfig.orchestrator.devServer.livenessProbeTimeoutMs,
366-
neuralLinkBridgePort : neuralLinkConfig.port,
367356
neuralLinkBridgeLivenessTimeoutMs: this.neuralLinkBridgeLivenessTimeoutMs
368357
});
369-
370-
this.applyConfiguredGraphLogCompaction(tasks);
371-
this.applyConfiguredMlxTask(tasks, {scriptDir});
372-
this.applyConfiguredLmsTask(tasks);
373-
this.applyConfiguredOllamaTask(tasks);
374-
375-
return tasks;
376-
}
377-
378-
/**
379-
* @summary Applies the graph-log compaction flags owned by AiConfig.
380-
* @param {Object} tasks Task table.
381-
* @returns {void}
382-
*/
383-
applyConfiguredGraphLogCompaction(tasks) {
384-
if (AiConfig.orchestrator.graphLogCompaction.vacuum) {
385-
tasks['graphlog-compaction'].args.push('--vacuum');
386-
}
387-
}
388-
389-
/**
390-
* @summary Adds the orchestrator-owned MLX server task when enabled.
391-
* @param {Object} tasks Task table.
392-
* @param {Object} options
393-
* @param {String} options.scriptDir Script directory.
394-
* @returns {void}
395-
*/
396-
applyConfiguredMlxTask(tasks, {scriptDir}) {
397-
if (!AiConfig.orchestrator.mlx.enabled) {
398-
return;
399-
}
400-
401-
tasks.mlx = {
402-
label : 'mlx inference',
403-
command: path.resolve(scriptDir, '../mcp/server/memory-core/.venv/bin/python'),
404-
args : [
405-
'-m',
406-
'mlx_lm.server',
407-
'--model',
408-
AiConfig.orchestrator.mlx.model,
409-
'--port',
410-
String(AiConfig.orchestrator.mlx.port)
411-
],
412-
pidFileName : 'mlx.pid',
413-
expectedCommand: 'mlx_lm.server'
414-
};
415-
}
416-
417-
/**
418-
* @summary Adds the orchestrator-owned LM Studio server task when enabled.
419-
* @param {Object} tasks Task table.
420-
* @returns {void}
421-
*/
422-
applyConfiguredLmsTask(tasks) {
423-
if (!AiConfig.orchestrator.lms.enabled) {
424-
return;
425-
}
426-
427-
const preloadConfig = buildLmsPreloadConfig(AiConfig),
428-
requiredModels = Array.isArray(preloadConfig.models)
429-
? [...new Set(preloadConfig.models.filter(Boolean))]
430-
: [AiConfig.orchestrator.lms.model].filter(Boolean);
431-
432-
tasks.lms = {
433-
label : 'lms server (LM Studio CLI)',
434-
command : 'lms',
435-
args : ['server', 'start', '--port', String(AiConfig.orchestrator.lms.port)],
436-
pidFileName : 'lms.pid',
437-
expectedCommand: 'lms server',
438-
requiredModels,
439-
// `lms server start` is fire-and-exit: it wakes the LM Studio service and returns, so
440-
// the launched server never matches `expectedCommand` for the supervisor's
441-
// process-liveness check (`!state.running` stays permanently true). Liveness is the
442-
// HTTP endpoint instead — the supervisor gates the restart on this probe so a healthy
443-
// server is a silent no-op rather than a re-spawn loop.
444-
livenessProbe : async () => {
445-
const {fetchOpenAiCompatibleModelIds} = await import('../../services/graph/providerReadinessHelper.mjs');
446-
447-
try {
448-
await fetchOpenAiCompatibleModelIds({
449-
host : AiConfig.openAiCompatible.host,
450-
timeoutMs: AiConfig.orchestrator.providerReadiness.timeoutMs ?? 2000
451-
});
452-
return true;
453-
} catch {
454-
return false;
455-
}
456-
},
457-
postSpawn : async () => {
458-
const {ensureLmsModelsLoaded} = await import('../../services/graph/providerReadinessHelper.mjs');
459-
460-
if (requiredModels.length === 0) {
461-
return {
462-
ready : true,
463-
loadedModels : [],
464-
requiredModels,
465-
availableModels: [],
466-
attempts : 0,
467-
skipped : true,
468-
reason : 'no-openai-compatible-local-roles'
469-
};
470-
}
471-
472-
return ensureLmsModelsLoaded({
473-
host : AiConfig.openAiCompatible.host,
474-
models : requiredModels,
475-
contextLengths: preloadConfig.contextLengths,
476-
parallels : preloadConfig.parallels,
477-
allowPartial : true,
478-
attempts : AiConfig.orchestrator.providerReadiness.attempts,
479-
delayMs : AiConfig.orchestrator.providerReadiness.delayMs,
480-
timeoutMs : AiConfig.orchestrator.providerReadiness.timeoutMs
481-
});
482-
}
483-
};
484-
}
485-
486-
/**
487-
* @summary Adds the orchestrator-owned native Ollama server task when enabled.
488-
* @param {Object} tasks Task table.
489-
* @returns {void}
490-
*/
491-
applyConfiguredOllamaTask(tasks) {
492-
if (!AiConfig.orchestrator.ollama.enabled) {
493-
return;
494-
}
495-
496-
const readinessConfig = buildOllamaReadinessConfig(AiConfig),
497-
roles = Array.isArray(readinessConfig.roles)
498-
? readinessConfig.roles.filter(role => role.model)
499-
: [],
500-
requiredModels = [...new Set(roles.map(role => role.model))];
501-
502-
if (requiredModels.length === 0) {
503-
return;
504-
}
505-
506-
tasks.ollama = {
507-
label : 'ollama server',
508-
command : 'ollama',
509-
args : ['serve'],
510-
pidFileName : 'ollama.pid',
511-
expectedCommand : 'ollama serve',
512-
requiredModels,
513-
singletonPort : resolveOllamaHostPort(readinessConfig.host),
514-
duplicateListenerPolicy: 'defer',
515-
env : buildOllamaServeEnv({
516-
host : readinessConfig.host,
517-
keepAlive : readinessConfig.keepAlive,
518-
contextLength : getMaxOllamaContextLength(roles),
519-
requireParallelModels: readinessConfig.requireParallelModels
520-
}),
521-
livenessProbe : async () => {
522-
const {ensureOllamaModelsReady} = await import('../../services/graph/providerReadinessHelper.mjs');
523-
524-
try {
525-
const result = await ensureOllamaModelsReady({
526-
host : readinessConfig.host,
527-
roles,
528-
keepAlive : readinessConfig.keepAlive,
529-
requireParallelModels: readinessConfig.requireParallelModels,
530-
allowPartial : true,
531-
attempts : AiConfig.orchestrator.providerReadiness.attempts,
532-
delayMs : AiConfig.orchestrator.providerReadiness.delayMs,
533-
timeoutMs : AiConfig.orchestrator.providerReadiness.timeoutMs
534-
});
535-
536-
if (
537-
result.error &&
538-
result.availableModels?.length === 0 &&
539-
result.attemptedModels?.length === 0
540-
) {
541-
return false;
542-
}
543-
544-
return true;
545-
} catch {
546-
return false;
547-
}
548-
},
549-
postSpawn : async () => {
550-
const {ensureOllamaModelsReady} = await import('../../services/graph/providerReadinessHelper.mjs');
551-
552-
return ensureOllamaModelsReady({
553-
host : readinessConfig.host,
554-
roles,
555-
keepAlive : readinessConfig.keepAlive,
556-
requireParallelModels: readinessConfig.requireParallelModels,
557-
allowPartial : true,
558-
attempts : AiConfig.orchestrator.providerReadiness.attempts,
559-
delayMs : AiConfig.orchestrator.providerReadiness.delayMs,
560-
timeoutMs : AiConfig.orchestrator.providerReadiness.timeoutMs
561-
});
562-
}
563-
};
564358
}
565359

566360
/** @summary Starts the orchestrator timer loop after the wrapper selects this process. */

0 commit comments

Comments
 (0)