Parent Epic
#11831 — Sub 1 of 6 (primary lane; DEPENDS ON Sub 6 for Neo.util.Env).
Scope
Refactor ai/daemons/Orchestrator.mjs (~1000 lines → ~400-500 lines target) into masterclass-reference shape per Epic #11831 ACs 1-7. Sets the pattern for all subsequent daemon refactors (Sub 4 propagation).
Acceptance Criteria
Inherits Epic #11831 ACs 1-7 directly. Specifically:
- AC1 — Drop cargo-cult
_-suffix configs (~30 properties without hooks → drop _). Keep _ only for class-system-managed reactive collaborators with real hooks (cadenceEngine_, processSupervisorService_).
- AC2 — 2-value chain for every operator-tunable interval/timeout/threshold/boolean:
Env.parseNumber(process.env.X, 'X') ?? AiConfig.namespace.X. No options.X, no DEFAULT_X constant, no env-alias chain.
- AC4 — 4-way Service-DI classification:
- (A)
cadenceEngine_ reactive with beforeSetCadenceEngine + ClassSystemUtil.beforeSetInstance(value, CadenceEngine, {defaults}) + afterSetCadenceEngine lifecycle wiring
- (B)
processSupervisorService_ reactive with beforeSetProcessSupervisorService (parent-sourced config injection) + parent afterSet* propagation hooks for dataDir/taskDefinitions/taskStateService/healthService/spawnFn. Stable logger seam processSupervisorWriteLog (avoid per-call .bind() allocation). Explicit removal of processSupervisorService.set({...this...}) context-replay block in start().
- (C)
healthService direct import + instance field (after audit confirms no lifecycle need)
- (D)
summarySweepIntervalMs / pollIntervalMs / etc. — lazy getters, no class config slot
- AC5 —
configure(options) REMOVED as runtime-policy resolver.
- AC7 — Test coverage of parent-config-propagation via
afterSet* (NOT via start() context replay).
Implementation snippet (per Discussion #11828 body AFTER section)
import { CadenceEngine } from '../services/CadenceEngine.mjs';
import { ProcessSupervisorService } from '../services/ProcessSupervisorService.mjs';
import HealthService from '...';
import ClassSystemUtil from '../../src/util/ClassSystem.mjs';
import Env from '../../src/util/Env.mjs';
class Orchestrator extends Base {
static config = {
className: 'Neo.ai.daemons.Orchestrator',
cadenceEngine_: null,
processSupervisorService_: null
}
processSupervisorWriteLog = (level, msg) => this.writeLog(level, msg)
healthService = HealthService
beforeSetCadenceEngine(value, oldValue) {
oldValue?.destroy?.();
return ClassSystemUtil.beforeSetInstance(value, CadenceEngine, {});
}
beforeSetProcessSupervisorService(value) {
return ClassSystemUtil.beforeSetInstance(value, ProcessSupervisorService, {
dataDir : this.dataDir,
taskDefinitions : this.taskDefinitions,
taskStateService: this.taskStateService,
healthService : this.healthService,
writeLog : this.processSupervisorWriteLog,
spawnFn : this.spawnFn
});
}
afterSetTaskDefinitions(value) {
if (this.processSupervisorService) this.processSupervisorService.taskDefinitions = value;
}
start() {
this.processSupervisorService.recoverTasks();
}
get summarySweepIntervalMs() {
return Env.parseNumber(process.env.NEO_ORCHESTRATOR_SUMMARY_SWEEP_INTERVAL_MS, 'NEO_ORCHESTRATOR_SUMMARY_SWEEP_INTERVAL_MS')
?? AiConfig.orchestrator.intervals.summarySweepMs;
}
}Test injection patterns (per refactor)
- AiConfig.data mutation:
AiConfig.data.orchestrator.intervals.summarySweepMs = 600000
- Env var with try/finally restore:
process.env.NEO_ORCHESTRATOR_X = '600000'; try { ... } finally { delete process.env.NEO_ORCHESTRATOR_X }
- Service-DI: direct instance override
orchestrator.healthService = mockHealthService
Files (touched)
ai/daemons/Orchestrator.mjs (refactor ~1000 lines → ~400-500)
ai/daemons/TaskDefinitions.mjs (delete 8 DEFAULT_X_INTERVAL_MS constants)
test/playwright/unit/ai/daemons/Orchestrator.spec.mjs (refactor createTestOrchestrator() for new injection patterns; add parent-config-propagation tests)
- (Sub 3 picks up downstream parser-surface deletions)
Authored by: [Claude Opus 4.7] (Claude Code)
Parent Epic
#11831 — Sub 1 of 6 (primary lane; DEPENDS ON Sub 6 for
Neo.util.Env).Scope
Refactor
ai/daemons/Orchestrator.mjs(~1000 lines → ~400-500 lines target) into masterclass-reference shape per Epic #11831 ACs 1-7. Sets the pattern for all subsequent daemon refactors (Sub 4 propagation).Acceptance Criteria
Inherits Epic #11831 ACs 1-7 directly. Specifically:
_-suffix configs (~30 properties without hooks → drop_). Keep_only for class-system-managed reactive collaborators with real hooks (cadenceEngine_,processSupervisorService_).Env.parseNumber(process.env.X, 'X') ?? AiConfig.namespace.X. Nooptions.X, noDEFAULT_Xconstant, no env-alias chain.cadenceEngine_reactive withbeforeSetCadenceEngine+ClassSystemUtil.beforeSetInstance(value, CadenceEngine, {defaults})+afterSetCadenceEnginelifecycle wiringprocessSupervisorService_reactive withbeforeSetProcessSupervisorService(parent-sourced config injection) + parentafterSet*propagation hooks fordataDir/taskDefinitions/taskStateService/healthService/spawnFn. Stable logger seamprocessSupervisorWriteLog(avoid per-call.bind()allocation). Explicit removal ofprocessSupervisorService.set({...this...})context-replay block instart().healthServicedirect import + instance field (after audit confirms no lifecycle need)summarySweepIntervalMs/pollIntervalMs/ etc. — lazy getters, no class config slotconfigure(options)REMOVED as runtime-policy resolver.afterSet*(NOT viastart()context replay).Implementation snippet (per Discussion #11828 body AFTER section)
import { CadenceEngine } from '../services/CadenceEngine.mjs'; import { ProcessSupervisorService } from '../services/ProcessSupervisorService.mjs'; import HealthService from '...'; import ClassSystemUtil from '../../src/util/ClassSystem.mjs'; import Env from '../../src/util/Env.mjs'; class Orchestrator extends Base { static config = { className: 'Neo.ai.daemons.Orchestrator', cadenceEngine_: null, processSupervisorService_: null } processSupervisorWriteLog = (level, msg) => this.writeLog(level, msg) healthService = HealthService beforeSetCadenceEngine(value, oldValue) { oldValue?.destroy?.(); return ClassSystemUtil.beforeSetInstance(value, CadenceEngine, {}); } beforeSetProcessSupervisorService(value) { return ClassSystemUtil.beforeSetInstance(value, ProcessSupervisorService, { dataDir : this.dataDir, taskDefinitions : this.taskDefinitions, taskStateService: this.taskStateService, healthService : this.healthService, writeLog : this.processSupervisorWriteLog, spawnFn : this.spawnFn }); } afterSetTaskDefinitions(value) { if (this.processSupervisorService) this.processSupervisorService.taskDefinitions = value; } // ... other afterSet* propagation hooks per parent-config audit start() { this.processSupervisorService.recoverTasks(); } get summarySweepIntervalMs() { return Env.parseNumber(process.env.NEO_ORCHESTRATOR_SUMMARY_SWEEP_INTERVAL_MS, 'NEO_ORCHESTRATOR_SUMMARY_SWEEP_INTERVAL_MS') ?? AiConfig.orchestrator.intervals.summarySweepMs; } }Test injection patterns (per refactor)
AiConfig.data.orchestrator.intervals.summarySweepMs = 600000process.env.NEO_ORCHESTRATOR_X = '600000'; try { ... } finally { delete process.env.NEO_ORCHESTRATOR_X }orchestrator.healthService = mockHealthServiceFiles (touched)
ai/daemons/Orchestrator.mjs(refactor ~1000 lines → ~400-500)ai/daemons/TaskDefinitions.mjs(delete 8DEFAULT_X_INTERVAL_MSconstants)test/playwright/unit/ai/daemons/Orchestrator.spec.mjs(refactorcreateTestOrchestrator()for new injection patterns; add parent-config-propagation tests)Authored by: [Claude Opus 4.7] (Claude Code)