aboutsummaryrefslogtreecommitdiff
path: root/modules/core/module-orchestrator.js
diff options
context:
space:
mode:
authorAgando <roduccomm@gmail.com>2026-10-23 19:01:46 +0200
committerAgando <roduccomm@gmail.com>2026-10-23 19:01:46 +0200
commit1ff8df65ffefbd2eaa9100374fbcfb7828fdb717 (patch)
treee42d2c6404211547ff4444596f79e24060580b8e /modules/core/module-orchestrator.js
parentb0404b1c9154c3438d0e124d69dc25d9140cfac4 (diff)
feat(newos4): on-demand worker labels and a server-canonical predictor store (v4.6.70)
Two decisions recorded by the operator on 2026-10-23, implemented. 6 — spawn-on-demand is the design, so stop reporting it as a defect: - OrchestratorModule counts delegations inside delegateTask() - status prints "Mode: on-demand — N/M worker(s) available, X delegation(s) this session" and drops the "spawning is not dispatching" warning - per-agent lines read "available, no delegations yet (spawned ...)" / "N delegation(s), last ..." 10 — per-user, server file canonical, browser VFS a TTL'd read-through cache: - store = the user's own WebDAV space (<space>/_data/predictor.json), so identity comes from credentials the app already has; no new endpoint, no unauthenticated write surface - 5 min TTL: cache wins while fresh, server wins when it differs; a banner naming both counts prints only on divergence - write-through on save (debounced 5 s) with a MKCOL retry when the collection does not exist yet; store mode is printed by predictor-status - the cache write is guarded and verified — a missing /system/metrics made writeFile fail silently, which is how the first version lost a pull Verified live on v4.6.70 with a stubbed transport: pull applies 5 pts/5 paired and writes a stamped cache, the divergence banner fires once and not on the second (equal) pull, write-through PUT is issued, status reads "Store: server". A real WebDAV round-trip still needs the user's WebDAV config in-app; the agando space has no _data collection yet, so GET returns 404 -> "server (empty)".
Diffstat (limited to 'modules/core/module-orchestrator.js')
-rw-r--r--modules/core/module-orchestrator.js18
1 files changed, 12 insertions, 6 deletions
diff --git a/modules/core/module-orchestrator.js b/modules/core/module-orchestrator.js
index d03f7eb..b3b76cd 100644
--- a/modules/core/module-orchestrator.js
+++ b/modules/core/module-orchestrator.js
@@ -25,6 +25,9 @@ class OrchestratorModule {
this._a2aPollTimer = null;
this._heartbeatInterval = null;
this._a2aMessages = {};
+ // v4.6.68: on-demand is the design (decision 2026-10-23). Delegations — not
+ // "agents ever scheduled" — is the number that means something.
+ this._delegations = 0;
this._restartBackoffs = {};
this._eventBus = {};
this._idleSweepInterval = null;
@@ -1077,6 +1080,7 @@ class OrchestratorModule {
async delegateTask(task, opts = {}) {
if (!task) return this.terminal.print('Usage: orchestrate <task description>', 'error');
+ this._delegations = (this._delegations || 0) + 1;
// Parallel fan-out: decompose and run sub-tasks concurrently
if (opts.parallel) {
@@ -1423,7 +1427,7 @@ class OrchestratorModule {
// v4.6.63: say what the state MEANS. A parked agent with 0 assignments and a parked
// agent between tasks looked identical from outside (packet §6).
const agentsArr = Object.values(this._agents);
- const everScheduled = agentsArr.filter(a => (a.tasksAssigned || 0) > 0).length;
+ const available = agentsArr.filter(a => a.status === 'idle' || a.status === 'sleeping').length;
const describe = (a) => {
const n = a.tasksAssigned || 0;
const since = a.createdAt ? String(a.createdAt).slice(0, 16).replace('T', ' ') : '?';
@@ -1436,19 +1440,21 @@ class OrchestratorModule {
}
if (n === 0) {
return (a.status === 'sleeping' ? 'parked' : a.status) +
- ` — NEVER SCHEDULED (0 tasks since ${since})`;
+ ` — available, no delegations yet (spawned ${since})`;
}
return (a.status === 'sleeping' ? 'parked' : a.status) +
- ` — waiting for work (${n} task(s), last ${last || '?'})`;
+ ` — ${n} delegation(s), last ${last || '?'}`;
};
agentsArr.forEach(a => {
const modelInfo = a.model ? ` [model: ${a.model}]` : '';
const backoff = a.nextRestartAt && a.nextRestartAt > Date.now() ? ` [restart in ${Math.round((a.nextRestartAt - Date.now()) / 1000)}s]` : '';
this.terminal.print(` ${a.id} [${a.role}] — ${describe(a)}${modelInfo}${backoff}`, 'info');
});
- this.terminal.print(` Dispatch: ${everScheduled}/${total} agent(s) have ever been given a task`, everScheduled === 0 && total > 0 ? 'warning' : 'info');
- if (everScheduled === 0 && total > 0) {
- this.terminal.print(' ⚠️ No agent has ever been scheduled — spawning is not dispatching. Use orchestrate <task> or orchestrator-spawn <role> <task>.', 'warning');
+ // v4.6.68: on-demand by design — a worker with nothing to do is healthy, not broken.
+ // The number to watch is how many delegations the AI actually made.
+ this.terminal.print(` Mode: on-demand — ${available}/${total} worker(s) available, ${this._delegations || 0} delegation(s) this session`, 'info');
+ if (total > 0 && (this._delegations || 0) === 0) {
+ this.terminal.print(' Workers wait for delegate_task — spawn-on-demand, no background dispatch queue by design.', 'text-muted');
}
}