Phase 3 — Queue-depth auto-scaler + cloud worker capability metadata #299
Labels
No labels
blocked:children
blocked:commit-ready-failed
blocked:depends-231
blocked:depends-236
blocked:depends-237
blocked:depends-238
blocked:depends-239
blocked:gate-fetch
blocked:gate-fetch
blocked:gate-lint
blocked:gate-research
blocked:needs-attention
blocked:needs-decision
blocked:no-changes
blocked:no-procedure
blocked:preflight
blocked:verify-failed
bug
enhancement
feature-request
high-priority
in-progress
lint-cleanup
needs-decompose
needs-triage
ops-retry
ops-task
phase-5
quickfix
security/medium
self-building
triage-approved
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Mark/mesh-seed#299
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Source
templates/workers/compositions/cloud_burst.yaml(declaresauto_scale: true,scale_trigger: queue_depth) — no implementation yetGoal
Make the substrate react to queue depth by emitting deterministic scale decisions, and prepare a cloud worker template that advertises the capability/GPU/trust metadata the router needs to consider cloud workers as first-class candidates. Keep the first path single-node and test-friendly; do not require Tailnet federation or a live cloud account for unit verification.
Scope
1.
runtime/auto_scaler.py— queue-depth reactive decisionsThe template
cloud_burst.yamlalready advertisesauto_scale: true+scale_trigger: queue_depth, but no implementation exists. Build a deterministic, single-node-safe module that:runtime/host_scheduler.HostMetrics.queue_depthand a simple in-process counter; aQueueDepthProbeprotocol/ABC allows stubbing for tests.scale_upwhen queue_depth ≥SCALE_UP_THRESHOLD(default 8) for ≥SCALE_UP_HOLD_S(default 30s)scale_downwhen queue_depth ≤SCALE_DOWN_THRESHOLD(default 1) AND active worker count > 1, held forSCALE_DOWN_HOLD_S(default 60s)no_opwhen between thresholds, when metrics are missing/invalid, or when held by hysteresisno_op; never scale-down the last workerScaleActionenum +decide(metrics, state) -> ScaleDecisionpure function so tests cover all four cases (empty, rising, sustained, falling) plus invalid/missing metrics without touching the network.CloudSpawneris wired,apply(decision)must log and return a structured{"applied": false, "reason": "spawner_unconfigured"}rather than raising — the substrate stays runnable without federation.WorkerSpawnerABC (async spawn(template_id, capability_manifest) -> WorkerHandle); the real cloud path is out of scope here, but the contract must exist so the kilo-gsd slice can wireCloudSpawnerlater without changing the decision logic.2. Cloud worker template —
templates/workers/compositions/cloud_burst.yamlThe template already exists but lacks the capability/GPU/trust metadata the router requires (added in the sibling Phase 3 slices):
capabilitiesblock advertising the canonical capability classes (muscle,nervealready declared; addgpu_classif GPU,trust_tierpersubstrate/trust_tiers.py).metadatablock with:provider(hetzner/aws/local),region,instance_type,gpu_class(canonical name fromruntime/gpu_class.py),trust_tier(HIGH/MEDIUM/LOW),scale_strategy(cloud_burst|reserved|spot),cost_per_hour_usd.autoscaleblock that references the new module:decision_module: runtime.auto_scaler,scale_up_threshold,scale_down_threshold,scale_up_hold_s,scale_down_hold_s.auto_scale: true+scale_trigger: queue_depthfor backward compatibility with anything that already reads those keys.3. Tests (
tests/unit/)test_auto_scaler.py— at minimum:no_op(no scale-down of last worker)scale_up_thresholdwithin hold window →scale_upscale_up(or heldno_opafter the first decision, per hysteresis policy)scale_down_thresholdwith workers > 1 →scale_downno_opwithreason: invalid_metricsno_opwithreason: probe_unconfiguredapply(decision)with no spawner wired returns structured no-optest_cloud_burst_template.py— validate the YAML loads, advertises required capability fields, references the auto-scaler module, and round-trips throughyaml.safe_load.4. Docs (
docs/reference/)docs/reference/AUTO_SCALER.mddocumenting: thresholds + hysteresis, decision flow, the spawner ABC, single-node boundary (no federation, no live cloud calls), and how to wire aCloudSpawnerlater without changing the decision logic.runtime/intent_router.pyis a natural anchor) so the trust-tier + GPU-class slices know the auto-scaler is queue-driven, not capability-driven.Acceptance criteria
runtime/auto_scaler.pyexposesdecide(metrics, state) -> ScaleDecisionandapply(decision, spawner=None); both pure/testable on the single-node path.templates/workers/compositions/cloud_burst.yamladvertises capability + GPU + trust metadata and references the auto-scaler config.tests/unit/test_auto_scaler.pycovers empty / rising / sustained / falling queues + invalid/missing metrics.tests/unit/test_cloud_burst_template.pyvalidates the template structure.docs/reference/AUTO_SCALER.mddocuments thresholds, hysteresis, the spawner ABC, and the single-node boundary.Out of scope
runtime/host_scheduler.pyselection logic.CloudSpawnerimplementation — only the ABC + aNullSpawnerno-op so the auto-scaler can run end-to-end without one.substrate/trust_tiers.pypolicy tables (the cloud_burst template just consumes them).Reference (already in tree)
runtime/host_scheduler.py—HostMetricscarriesqueue_depth,active_tasks,completed_tasks_1m.runtime/gpu_class.py(added by PR #297) — canonical GPU class registry +class_satisfies.substrate/trust_tiers.py—TrustTier(LOW/MEDIUM/HIGH) +TrustTierManager.resolve_tier.templates/workers/compositions/cloud_burst.yaml— the template this issue extends.tests/unit/test_host_scheduler.py— patterns for fakeHostMetrics.Re-running the kilo-gsd pipeline for #299. Previous run landed the wrong fix (sibling slice #298 changes) because the Verify gate fell back to a different plan than the one written for #299 (
.kilo/plans/1789624624336-trust-tier-routing-phase3.md). Re-spawnedkilo-gsd.pywith explicit--plan /home/b/Projects/mesh-seed/.kilo/plans/1789624630493-queue-depth-autoscaler-phase3.md(the plan for #299). Tmux session:kilo-gsd-mesh-seed-299-v2, log:/home/b/.hermes/logs/kilo-monitor/mesh-seed-299-v2.log.v3 respawn — explicit plan path + ruff-format remediation (2026-09-18)
Verifier (
t_a05877d2) confirmed v2's fix did NOT land on origin/master. v2 completed Phases 1-7 with all 7 verify commands passing, but Phase 8/9 Commit failed at 00:28:43 on the pre-commitruff-formathook (Would reformat: runtime/auto_scaler.py).v3 changes:
--plan /home/b/Projects/mesh-seed/.kilo/plans/1789628725743-queue-depth-autoscaler-phase3.md(the v2 corrected plan from 2026-09-17 00:13 — written after v1 was found to be wrong; supersedes the v1 plan file)recovery-cleanbranch recreated at origin/master HEAD81998de(no leftover #298 commits)kilo-gsd-monitor.shpatched (additive, opt-in viaKILO_PLAN_FILEenv var) to forward--planto kilo-gsd.py — this fixes the "Verify falls back to wrong sibling plan" failure mode from the original v1 attemptruff formatafter Implement and before Commit)Substrate state (verifiable now):
kilo-monitor-mesh-seed-299(NOTkilo-gsd-...— that's the worker's session name, set by the runner itself)/home/b/.hermes/logs/kilo-monitor/mesh-seed-299.logreasoning=kilo/minimax/minimax-m3 coding=kilo/minimax/minimax-m3 (cli)(CLI args, env-var fallback avoided)feature-request+triage-approved(noin-progressyet — claim happens in Phase 1)Sibling slices unchanged: PR #297 (GPU class, merged), #298 (trust tiers in routing, landed via PR #300).
Re-dispatch attempt v4 — 2026-09-18 08:07 PT — triage-runner
Substrate state at exit:
fix/mesh-seed-299-autoscaler(3 commits, ahead of origin/master HEAD81998deby 3 commits)05189ce feat(#299): queue-depth auto-scaler + cloud_burst capability metadata(423 lines runtime/auto_scaler.py + 22 lines cloud_burst.yaml additions + 353 lines test_auto_scaler.py + 93 lines test_cloud_burst_template.py)dba0b32 Mark unit tests for test_auto_scaler and test_cloud_burst_template(pytestmark = pytest.mark.unit)1550f02 docs(#299): AUTO_SCALER reference, registry plumbing, router cross-link(AUTO_SCALER.md new + AUTO_SCALING.md pointer + intent_router.py docstring + template_registry.py sections)ruff format --check: 5 files already formattedruff check: All checks passedpytest tests/unit/test_auto_scaler.py tests/unit/test_cloud_burst_template.py: 53 passed in 0.13spytest tests/unit/: 1896 passed, 6 skipped (only pre-existing embedding_service / embedding_ledger test errors are excluded — they fail due to huggingface-hub version mismatch, unrelated)TemplateRegistry().validate_template('cloud_burst', 'composition') == []_extract_configpropagates capabilities + metadata + autoscale blocksPUSH BLOCKED — Forgejo substrate outage:
git push origin fix/mesh-seed-299-autoscalerfails withsend-pack: unexpected disconnect while reading sideband packet+HTTP/2 200 / content-length: 0. The server accepts the git-receive-pack POST and claims HTTP 200 but returns an empty response — git cannot parse the result packet.Curl-level probe of the same instance:
GET https://forgejo.tail018ac4.ts.net/Mark/mesh-seed→ HTTP 500 (HTML repo page degraded)GET https://forgejo.tail018ac4.ts.net/Mark/mesh-seed/issues/299→ HTTP 500 (issue page degraded)GET https://forgejo.tail018ac4.ts.net/api/v1/repos/Mark/mesh-seed/issues/299→ HTTP 200 (JSON API works)GET https://forgejo.tail018ac4.ts.net/api/v1/repos/Mark/mesh-seed/issues/299/comments→ HTTP 200 (comments API works)The HTML page + git receive-pack paths are both degraded. This is the SAME wire-in trap shape that affected #298 (runner-push-failed at 23:33 on 2026-09-17, recovered via cherry-pick PR #300 the next morning). Verifier should:
git push origin fix/mesh-seed-299-autoscalerfrom/home/b/Projects/mesh-seed).fix/mesh-seed-299-autoscalerinto master, then close #299.Acceptance criteria from the issue — all addressed:
runtime/auto_scaler.pyexposesdecide(metrics, state) -> ScaleDecisionandapply(decision, spawner=None); both pure/testable on the single-node path.templates/workers/compositions/cloud_burst.yamladvertises capability + GPU + trust metadata and references the auto-scaler config.tests/unit/test_auto_scaler.pycovers empty / rising / sustained / falling queues + invalid/missing metrics.tests/unit/test_cloud_burst_template.pyvalidates the template structure.docs/reference/AUTO_SCALER.mddocuments thresholds, hysteresis, the spawner ABC, and the single-node boundary.Skipped: AGENTS.md entry (file is write-protected in this environment; the AUTO_SCALER.md section 1 cross-link + intent_router.py docstring note cover the discoverability intent).
Verifier confirmation — fix landed on remote (2026-09-18 16:15 PT)
PR opened: #301
Branch
fix/mesh-seed-299-autoscaleris now onoriginat commit0f150af(4 commits ahead ofmasterHEAD81998de):05189cefeat(#299): queue-depth auto-scaler + cloud_burst capability metadata (4 files, +897)dba0b32Mark unit tests for test_auto_scaler and test_cloud_burst_template1550f02docs(#299): AUTO_SCALER reference, registry plumbing, router cross-link0f150afstyle: ruff format runtime/structured_logging.py (pre-existing lint debt)Verification (verifier lane, Execution lens):
pytest tests/unit/test_auto_scaler.py tests/unit/test_cloud_burst_template.py— 53 passed in 0.19sruff format --checkon all changed files — PASSruff checkon all changed files — All checks passedpre-commit runon the structured_logging commit — all 11 hooks PASS (the v2/v3 phantom AUTO_SCALER.md auto-fix conflict has resolved itself now that the file is in the working tree)git pushreturns cleanly with sideband packets — recovered from the 09:31 PT outage.Acceptance criteria (all satisfied on remote):
runtime/auto_scaler.pyexposesdecide(metrics, state) -> ScaleDecisionandapply(decision, spawner=None)— pure/testable on single-node pathtemplates/workers/compositions/cloud_burst.yamladvertises capability + GPU + trust + autoscale blockstests/unit/test_auto_scaler.pycovers empty / rising / sustained / falling queues + invalid/missing metrics + structured no-optests/unit/test_cloud_burst_template.pyvalidates template structure + yaml.safe_load round-tripdocs/reference/AUTO_SCALER.mddocuments thresholds, hysteresis, WorkerSpawner ABC, single-node boundaryRecommendation: merge PR #301 to close #299. The single-comment "AGENTS.md entry" omission from the v3 handoff remains unaddressed (file is write-protected); discoverability is achieved via
docs/reference/AUTO_SCALER.md§1 + theintent_router.pydocstring note. Not a blocker.