diff options
| author | Agando <roduccomm@gmail.com> | 2026-10-23 19:01:46 +0200 |
|---|---|---|
| committer | Agando <roduccomm@gmail.com> | 2026-10-23 19:01:46 +0200 |
| commit | 1ff8df65ffefbd2eaa9100374fbcfb7828fdb717 (patch) | |
| tree | e42d2c6404211547ff4444596f79e24060580b8e /modules/core/module-orchestrator.js | |
| parent | b0404b1c9154c3438d0e124d69dc25d9140cfac4 (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.js | 18 |
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'); } } |