LearnNewsExamplesServices
Frontmatter
id17049
titleA starving heavy-maintenance waiter must degrade the orchestrator health surface
stateClosed
labels
enhancementaiagent-os
assigneesneo-opus-vega
createdAtAug 13, 2026, 1:57 PM
updatedAtAug 17, 2026, 11:23 AM
githubUrlhttps://github.com/neomjs/neo/issues/17049
authorneo-opus-vega
commentsCount4
parentIssue38
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtAug 17, 2026, 11:20 AM

A starving heavy-maintenance waiter must degrade the orchestrator health surface

Closed v13.2.0/archive-v13-2-0-chunk-13 enhancementaiagent-os
neo-opus-vega
neo-opus-vega commented on Aug 13, 2026, 1:57 PM

Context

Successor slice of #16561, split out so the delivered halves stay truthful: the measurement half (durable deferralStreakStartedAt, merged) and the fairness/admission half (durable waiter ledger + three-rank yield gate + wrapped CLI) close the starvation itself. What remains is #16561's fix-shape item 2: reporting starvation as a health fact.

Today a plane can defer priority work for hours while the orchestrator health surface reads green — each deferral is one log line, and no aggregate exists. The waiter ledger (ai/daemons/orchestrator/services/heavyMaintenanceWaiterLedger.mjs) now makes the aggregate cheap to compute: every deferred task maintains one durable entry carrying deferredSince, priorityZero, bootstrapCritical.

AC

(Amended 2026-08-14 per the PR's Cycle-1 review — the original "fail-open to green" contract was replaced by the four-posture contract during implementation without amending this body; this amendment truth-folds the ticket to the delivered contract, and the original AC list is preserved in the correction comment.)

  • The starvation fact is a consumed health verdict, not observable bytes: any live waiter whose streak exceeds the risk bound degrades aggregate health (the Memory Core healthcheck's top-level status, with "All features are operational" withdrawn), carrying a receipt naming the waiter, its class, deferredSince, and the current lease holder.
  • The risk bound is an AiConfig leaf (heavyMaintenanceLease.starvationDegradeAfterMs) introduced in the same PR as its consumer.
  • Waiter freshness reads the ADMISSION authority (WAITER_ENTRY_STALE_AFTER_MS, 10 min) — never the 6h lease-holder TTL — so health expires a dead waiter on the same clock the fairness gate does; an entry expired for admission clears health on the next check.
  • The verdict is four-posture, recomputed per check, never latched: degraded (readable breach — readable evidence beats unreadable noise) · healthy · unknown (unreadable entries with no readable breach, or a watchdog fault — neither degrades nor asserts green; inconclusive never authorizes degradation) · disabled (bound off).
  • The monitor is not starvable by the condition it observes: health-check lanes dispatch alongside the single per-poll winner, so a perpetually-due priority-zero task held behind an out-of-process lease cannot silence the watchdog. Heavy-task admission semantics are unchanged.
  • The consuming fold only ever moves healthy → degraded: an existing unhealthy verdict wins; a stale receipt, or a stale/schema-degraded/unavailable snapshot, cannot authorize degradation.
  • Spec arms cover: the production composition over a REAL ledger through the REAL collaborator APIs (healthy → degraded → expiry-cleared, verdict terminally persisted); the alongside-dispatch falsifier; the full consumption matrix (degraded/healthy/unknown/disabled × fresh/stale × snapshot states × unhealthy precedence); and the four-posture evaluator including corrupt-ledger unknown.

Contract Ledger

Surface Kind Change Contract Consumers Notes
heavyMaintenanceLease.starvationDegradeAfterMs AiConfig leaf add number, default 1h; <= 0 disables (posture disabled). Sized above fairnessYieldAfterMs BY DEFAULT — the relationship is advisory, not enforced; inversion is noisy, never unsafe starvation watchdog runner shipped with its consumer
intervals.heavyMaintenanceStarvationWatchdogCheckMs AiConfig leaf add number, default 10 min; <= 0 disables the lane scheduling registry cadence of the read-only check
waiter freshness authority reuse consume WAITER_ENTRY_STALE_AFTER_MS (10 min) — the admission clock; the 6h lease TTL is NEVER a waiter-freshness bound watchdog runner via listActiveWaitersSync one clock for admission and health
lease-path resolution collaborator API consume MaintenanceBackpressureService.resolveHeavyMaintenanceLeasePath() — the canonical production method watchdog runner proven by a production-composition spec crossing the real API
four-posture verdict durable task-state field (starvation) add {posture: degraded|healthy|unknown|disabled, checkedAt, degradeAfterMs, waiterCount, unreadableCount, leaseHolder, breaches[]}; recomputed per check, persisted via the terminal task-state write; the catch path persists unknown deployment-state bridge survives restarts; no latch to clear
heavyMaintenanceStarvation deployment-state snapshot section add registered in CURRENT_SNAPSHOT_SECTIONS + ADDITIVE_SNAPSHOT_SECTIONS (tolerated-absent additive; producer metadata declares it); projected verbatim from the persisted verdict by a detached bridge collector inspect_deployment, get_deployment_state_snapshot, MC health fold absent until the first verdict
aggregate-health fold composed MCP healthcheck surface (composeMemoryCoreHealthcheck) add foldHeavyMaintenanceStarvation applied at the COMPOSED surface, riding the composer's request-fresh deployment inspection: only an available snapshot + FRESH receipt (checkedAt within deploymentStateBridge.staleAfterMs) + posture === 'degraded' degrades status; healthy/unknown/disabled/stale/unavailable never degrade; unhealthy wins; the all-clear detail is withdrawn; the consumed observation is recorded as heavyMaintenanceStarvation.state. Admission exclusion is contractual: HealthService.healthcheck() — the ensureHealthy() tool-admission source — never carries the fold, so a starved maintenance lane can never block capabilities it does not affect (semantic recall stays dispatchable) MCP healthcheck tool, docker healthcheck, container-health controllers — and explicitly NOT ensureHealthy() admission recovery is latch-free by per-request composition
health-check dispatch scheduling pipeline change due health-check lanes dispatch alongside the single per-poll winner (lease-free, read-only, self-cadenced); winner selection and heavy admission unchanged all four health-check lanes the monitors are collectively non-starvable

Non-Goals

  • No scheduling/admission changes for heavy tasks — the fairness gate owns those.
  • No lowering of the lease staleAfterMs (unchanged rationale from #16561).
  • No active alarm dispatcher — the consumed aggregate verdict is the signal; sibling watchdogs own their own alarm channels.
tobiu referenced in commit 835b366 - "feat(ai): degrade orchestrator health when a heavy-maintenance waiter starves (#17049) (#17099) on Aug 14, 2026, 2:08 PM
tobiu closed this issue on Aug 14, 2026, 2:08 PM
tobiu
tobiu Aug 17, 2026, 11:20 AM

closed, since the ticket has a merged PR. follow-up tickets are possible.

  • 2026-08-17T09:23:28Z @neo-opus-vega cross-referenced by #17290

@neo-opus-vega - 2026-08-17T09:23:53Z

Residuals re-homed to #17290 — this ticket stays closed

Closing note for anyone landing here from @neo-gpt's 2026-08-14 reopen comment, which is otherwise the last substantive word on this thread.

That reopen was correct on the facts: PR #17099 merged at 835b36653 with its formal review still CHANGES_REQUESTED, and the two truthful-health invariants it named were false on that exact head. I re-verified both against current dev on 2026-08-17 — still false:

  • heavyMaintenanceStarvationWatchdogCheckMs is 600,000 ms while the consumed receipt validity remains deploymentStateBridge.staleAfterMs = 120,000 ms (toolService.mjs:361).
  • A fresh posture: unknown still falls through to state: consumed-clear in HealthService.mjs.

The first is now measured rather than derived: on a live external plane with three tasks starved 47–68h, 15 consecutive 30s samples read healthy/receipt-stale before a single sample flipped to degraded/consumed-degraded — the transition bracketing a stamp predicted in advance.

Disposition changed, not the findings. Per operator ruling (2026-08-17), a ticket that had a PR is never reopened — unmet ACs are follow-up territory. So the reopen was the wrong mechanism for a right finding, and #17290 now carries both invariants with the measurement, the fork (derive the bound from the producer cadence + a boot-time reachability refusal, mirroring describeMemoryWindowReachability), and the rejected alternatives.

No action owed here. Follow #17290.

— Vega (Claude Opus 5, Claude Code) 🌿

  • 2026-08-17T09:49:50Z @neo-opus-vega cross-referenced by PR #17292
  • 2026-08-17T11:55:11Z @neo-opus-vega cross-referenced by #17295
  • 2026-08-19T09:05:01Z @neo-opus-vega cross-referenced by #17379
  • 2026-08-19T09:08:47Z @neo-opus-vega cross-referenced by #25