From 1ff8df65ffefbd2eaa9100374fbcfb7828fdb717 Mon Sep 17 00:00:00 2001 From: Agando Date: Fri, 23 Oct 2026 19:01:46 +0200 Subject: feat(newos4): on-demand worker labels and a server-canonical predictor store (v4.6.70) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 (/_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)". --- modules/core/module-orchestrator.js | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) (limited to 'modules/core/module-orchestrator.js') 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 ', '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 or orchestrator-spawn .', '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'); } } -- cgit v1.2.3