aboutsummaryrefslogtreecommitdiff
path: root/modules/core/module-orchestrator.js
diff options
context:
space:
mode:
authorAgando <roduccomm@gmail.com>2026-07-09 06:00:32 +0200
committerAgando <roduccomm@gmail.com>2026-07-09 06:00:32 +0200
commit2538c91876455fe96febf441fc8dde20f2606023 (patch)
treeb74c949cdf4296d10103dd57ef8178ba427a5b39 /modules/core/module-orchestrator.js
parentea4d09fb5ccba12ed774fcf7c913edfa1e6a851a (diff)
auto: 2026-07-09_06:00 changes
Diffstat (limited to 'modules/core/module-orchestrator.js')
-rw-r--r--modules/core/module-orchestrator.js140
1 files changed, 129 insertions, 11 deletions
diff --git a/modules/core/module-orchestrator.js b/modules/core/module-orchestrator.js
index ba43dfd..59d9136 100644
--- a/modules/core/module-orchestrator.js
+++ b/modules/core/module-orchestrator.js
@@ -32,6 +32,8 @@ class OrchestratorModule {
this._activeConsensus = null;
this._pipelineHistory = [];
this._PIPELINE_CONFIG = { autoFix: false, stageTimeout: 30000 };
+ this._schedules = [];
+ this._scheduleTimer = null;
this._loadConfig();
}
_loadConfig() {
@@ -84,6 +86,9 @@ class OrchestratorModule {
this.commands['consensus-status'] = () => this.consensusStatus();
this.commands['consensus-history'] = () => this.consensusHistory();
this.commands['pipeline'] = (args) => this.runPipeline(args);
+ this.commands['orchestrator-schedule'] = (args) => this.addSchedule(args);
+ this.commands['orchestrator-schedules'] = () => this.listSchedules();
+ this.commands['orchestrator-unschedule'] = (args) => this.removeSchedule(args[0]);
}
// ─── Task Decomposition Engine ───
_decomposeGoal(goal) {
@@ -146,6 +151,9 @@ class OrchestratorModule {
'consensus-status': 'consensus-status — Show current/active consensus round status',
'consensus-history': 'consensus-history — Show past consensus rounds and their resolutions',
'pipeline': 'pipeline <test|deploy|verify|history|config> — Autonomous CI/CD: test agents/queue/modules, deploy (test→auto-fix→verify), history (show runs), config (show config)',
+ 'orchestrator-schedule': 'orchestrator-schedule <role> <interval_seconds> <task...> — Schedule a recurring task delegation',
+ 'orchestrator-schedules': 'orchestrator-schedules — List all active recurring schedules',
+ 'orchestrator-unschedule': 'orchestrator-unschedule <id> — Remove a recurring schedule',
'orchestrator-status': 'orchestrator-status — Show agent pool status and config',
'orchestrator-spawn': 'orchestrator-spawn [role] [capabilities...] — Manually spawn a new agent',
'orchestrator-kill': 'orchestrator-kill <agent-id> — Terminate an agent and revoke leases',
@@ -170,13 +178,17 @@ class OrchestratorModule {
this._reanimateFromVFS();
// A2A mailbox polling loop (500ms)
if (this._A2A_POLLING) {
- this._a2aPollTimer = setInterval(() => this._pollAgentMailboxes(), this._A2A_POLL_INTERVAL);
+ this._a2aPollTimer = this.terminal.moduleManager._setInterval(() => this._pollAgentMailboxes(), this._A2A_POLL_INTERVAL);
}
// Heartbeat watchdog
- this._heartbeatInterval = setInterval(() => this._checkHeartbeats(), this._HEARTBEAT_CHECK);
+ this._heartbeatInterval = this.terminal.moduleManager._setInterval(() => this._checkHeartbeats(), this._HEARTBEAT_CHECK);
// Idle timeout sweep
- this._idleSweepInterval = setInterval(() => this._sweepIdle(), this._IDLE_SWEEP_INTERVAL);
+ this._idleSweepInterval = this.terminal.moduleManager._setInterval(() => this._sweepIdle(), this._IDLE_SWEEP_INTERVAL);
this.terminal.print('🧠 Orchestrator loaded — A2A mailbox transport active', 'success');
+ // Load recurring schedules from VFS
+ this._loadSchedules();
+ // Pre-spawn default agents after a short delay (let other modules init first)
+ setTimeout(() => this._preSpawnDefaultAgents(), 2000);
// Subscribe to event bus for in-memory A2A delivery (instant, no VFS poll)
this._on('a2a:message', (msg) => {
if (msg.type === 'heartbeat' && msg.from && this._agents[msg.from]) {
@@ -561,16 +573,16 @@ class OrchestratorModule {
'a2a-polling': () => {
this._A2A_POLLING = val === 'on' || val === 'true';
if (this._A2A_POLLING && !this._a2aPollTimer) {
- this._a2aPollTimer = setInterval(() => this._pollAgentMailboxes(), this._A2A_POLL_INTERVAL);
+ this._a2aPollTimer = this.terminal.moduleManager._setInterval(() => this._pollAgentMailboxes(), this._A2A_POLL_INTERVAL);
} else if (!this._A2A_POLLING && this._a2aPollTimer) {
- clearInterval(this._a2aPollTimer);
+ this.terminal.moduleManager._clearTimer(this._a2aPollTimer);
this._a2aPollTimer = null;
}
return `a2a-polling ${this._A2A_POLLING ? 'on' : 'off'}`;
},
- 'a2a-poll-interval': () => { const n = parseInt(val); if (isNaN(n) || n < 50 || n > 30000) return 'Must be 50-30000ms'; clearInterval(this._a2aPollTimer); this._a2aPollTimer = null; this._A2A_POLL_INTERVAL = n; if (this._A2A_POLLING) { this._a2aPollTimer = setInterval(() => this._pollAgentMailboxes(), n); } return `a2a-poll-interval set to ${n}ms`; },
- 'heartbeat-check': () => { const n = parseInt(val); if (isNaN(n) || n < 500 || n > 60000) return 'Must be 500-60000ms'; clearInterval(this._heartbeatInterval); this._HEARTBEAT_CHECK = n; this._heartbeatInterval = setInterval(() => this._checkHeartbeats(), n); return `heartbeat-check set to ${n}ms`; },
- 'idle-sweep-interval': () => { const n = parseInt(val); if (isNaN(n) || n < 5000 || n > 600000) return 'Must be 5000-600000ms'; clearInterval(this._idleSweepInterval); this._IDLE_SWEEP_INTERVAL = n; this._idleSweepInterval = setInterval(() => this._sweepIdle(), n); return `idle-sweep-interval set to ${n}ms`; },
+ 'a2a-poll-interval': () => { const n = parseInt(val); if (isNaN(n) || n < 50 || n > 30000) return 'Must be 50-30000ms'; this.terminal.moduleManager._clearTimer(this._a2aPollTimer); this._a2aPollTimer = null; this._A2A_POLL_INTERVAL = n; if (this._A2A_POLLING) { this._a2aPollTimer = this.terminal.moduleManager._setInterval(() => this._pollAgentMailboxes(), n); } return `a2a-poll-interval set to ${n}ms`; },
+ 'heartbeat-check': () => { const n = parseInt(val); if (isNaN(n) || n < 500 || n > 60000) return 'Must be 500-60000ms'; this.terminal.moduleManager._clearTimer(this._heartbeatInterval); this._HEARTBEAT_CHECK = n; this._heartbeatInterval = this.terminal.moduleManager._setInterval(() => this._checkHeartbeats(), n); return `heartbeat-check set to ${n}ms`; },
+ 'idle-sweep-interval': () => { const n = parseInt(val); if (isNaN(n) || n < 5000 || n > 600000) return 'Must be 5000-600000ms'; this.terminal.moduleManager._clearTimer(this._idleSweepInterval); this._IDLE_SWEEP_INTERVAL = n; this._idleSweepInterval = this.terminal.moduleManager._setInterval(() => this._sweepIdle(), n); return `idle-sweep-interval set to ${n}ms`; },
'dead-purge-timeout': () => { const n = parseInt(val); if (isNaN(n) || n < 5000 || n > 600000) return 'Must be 5000-600000ms'; this._DEAD_PURGE_TIMEOUT = n; return `dead-purge-timeout set to ${n}ms`; },
'consensus-size': () => { const n = parseInt(val); if (isNaN(n) || n < 2 || n > 10) return 'Must be 2-10'; this._CONSENSUS_SIZE = n; return `consensus-size set to ${n}`; },
'consensus-strategy': () => { if (!['majority', 'unanimous', 'weighted'].includes(val)) return 'Must be majority, unanimous, or weighted'; this._CONSENSUS_STRATEGY = val; return `consensus-strategy set to ${val}`; },
@@ -983,7 +995,7 @@ class OrchestratorModule {
// Fix 2: Restart A2A poller if stopped
if (!this._A2A_POLLING) {
this._A2A_POLLING = true;
- this._a2aPollTimer = setInterval(() => this._pollAgentMailboxes(), this._A2A_POLL_INTERVAL);
+ this._a2aPollTimer = this.terminal.moduleManager._setInterval(() => this._pollAgentMailboxes(), this._A2A_POLL_INTERVAL);
this.terminal.print(' Restarted A2A polling', 'warning');
fixed++;
}
@@ -1113,9 +1125,12 @@ class OrchestratorModule {
}
}
- // Create a lease
+ // Create a lease — with --write if agent has write capabilities
const leaseModule = this.terminal.moduleManager.modules['LeaseModule'];
- const lease = leaseModule ? leaseModule.createLease(['lease-create', agent.id, '60', task]) : null;
+ const hasWriteCap = (agent.capabilities || []).some(function(c) { return c === 'write_file' || c === 'edit_module' || c === 'delete_file'; });
+ const leaseArgs = ['lease-create', agent.id, '60', task];
+ if (hasWriteCap) leaseArgs.push('--write');
+ const lease = leaseModule ? leaseModule.createLease(leaseArgs) : null;
// Mark agent as busy
agent.status = 'busy';
@@ -1184,6 +1199,109 @@ class OrchestratorModule {
return outboxResult || 'completed';
}
+ // ─── Recurring Scheduling ───
+
+ addSchedule(args) {
+ if (args.length < 2) return this.terminal.print('Usage: orchestrator-schedule <role> <interval_seconds> <task...>', 'error');
+ const role = args[0];
+ const interval = parseInt(args[1]);
+ if (isNaN(interval) || interval < 10) return this.terminal.print('Interval must be >= 10 seconds', 'error');
+ const task = args.slice(2).join(' ');
+ if (!task) return this.terminal.print('Task description required', 'error');
+
+ const schedule = {
+ id: 'sched-' + Date.now(),
+ role: role,
+ interval: interval * 1000,
+ task: task,
+ createdAt: new Date().toISOString(),
+ lastRun: null,
+ nextRun: Date.now() + interval * 1000,
+ runs: 0
+ };
+ this._schedules.push(schedule);
+ this._saveSchedules();
+ this._startScheduleTimer();
+ this.terminal.print('📅 Schedule created: "' + task.substring(0, 40) + '" every ' + interval + 's (role: ' + role + ')', 'success');
+ return schedule;
+ }
+
+ listSchedules() {
+ if (this._schedules.length === 0) return this.terminal.print('No active schedules', 'info');
+ this.terminal.print('📅 Recurring Schedules (' + this._schedules.length + '):', 'header');
+ for (const s of this._schedules) {
+ const nextIn = Math.max(0, Math.round((s.nextRun - Date.now()) / 1000));
+ this.terminal.print(' ' + s.id + ' — "' + s.task.substring(0, 40) + '" every ' + (s.interval / 1000) + 's [' + s.role + '] (next: ' + nextIn + 's, runs: ' + s.runs + ')', 'info');
+ }
+ }
+
+ removeSchedule(id) {
+ const idx = this._schedules.findIndex(function(s) { return s.id === id; });
+ if (idx === -1) return this.terminal.print('Schedule ' + id + ' not found', 'error');
+ this._schedules.splice(idx, 1);
+ this._saveSchedules();
+ if (this._schedules.length === 0 && this._scheduleTimer) {
+ clearInterval(this._scheduleTimer);
+ this._scheduleTimer = null;
+ }
+ this.terminal.print('Schedule ' + id + ' removed', 'success');
+ }
+
+ _startScheduleTimer() {
+ if (this._scheduleTimer) return;
+ this._scheduleTimer = setInterval(function() {
+ var now = Date.now();
+ for (var i = 0; i < this._schedules.length; i++) {
+ var s = this._schedules[i];
+ if (now >= s.nextRun) {
+ s.lastRun = new Date().toISOString();
+ s.nextRun = now + s.interval;
+ s.runs++;
+ this.terminal.print('📅 Schedule trigger: "' + s.task.substring(0, 40) + '"', 'info');
+ this.delegateTask(s.task);
+ }
+ }
+ this._saveSchedules();
+ }.bind(this), 5000);
+ }
+
+ _saveSchedules() {
+ try {
+ this.terminal.fs.fsWriteJSON('/orchestrator/schedules.json', this._schedules);
+ } catch (e) { console.warn('[Orchestrator] Schedule save error:', e.message); }
+ }
+
+ _loadSchedules() {
+ try {
+ const data = this.terminal.fs.fsReadJSON('/orchestrator/schedules.json');
+ if (data && Array.isArray(data)) {
+ this._schedules = data;
+ if (this._schedules.length > 0) this._startScheduleTimer();
+ }
+ } catch (e) { this._schedules = []; }
+ }
+
+ _preSpawnDefaultAgents() {
+ // Spawn 3 role-specific agents at boot
+ var roles = [
+ { role: 'auto-learner', caps: ['list_files', 'read_file', 'write_file', 'view_module'], task: 'audit skills' },
+ { role: 'kernel-architect', caps: ['list_files', 'read_file', 'write_file', 'run_command'], task: 'index VFS' },
+ { role: 'searcher', caps: ['list_files', 'read_file', 'web_search'], task: 'check stale data' }
+ ];
+ for (var i = 0; i < roles.length; i++) {
+ var r = roles[i];
+ // Check if an agent with this role already exists
+ var existing = Object.values(this._agents).find(function(a) { return a.role === r.role; });
+ if (!existing) {
+ var agent = this.spawnAgent(r.role, r.caps);
+ if (agent) {
+ agent.restartPolicy = 'always';
+ this.terminal.print('🤖 Pre-spawned agent: ' + r.role + ' (' + r.caps.length + ' caps)', 'info');
+ }
+ }
+ }
+ }
+
// ─── Monitoring ───
_checkHeartbeats() {