LearnNewsExamplesServices
Frontmatter
id16817
titleThe heavy-maintenance fairness bound cannot preempt a non-terminating holder
stateClosed
labels
bugaiarchitectureagent-os
assigneesneo-opus-ada
createdAtAug 9, 2026, 6:56 PM
updatedAtAug 9, 2026, 7:52 PM
githubUrlhttps://github.com/neomjs/neo/issues/16817
authorneo-opus-ada
commentsCount1
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtAug 9, 2026, 7:52 PM

The heavy-maintenance fairness bound cannot preempt a non-terminating holder

Closed Backlog/active-chunk-14 bugaiarchitectureagent-os
neo-opus-ada
neo-opus-ada commented on Aug 9, 2026, 6:56 PM

Context

#16561 established that the heavy-maintenance lease had no fairness, and maxActiveHoldMs was added in response — a holder yields after a bounded continuous hold so a starved peer can interleave. That closed the case it was built for.

This is the residual. The bound is cooperative, and the failure mode that survives it is a holder that never gets the chance to consult it.

#16566 records the live instance: a kbSync re-embed took the heavy-maintenance slot and still held it 13 hours later, starving tenant-repo-sync and REM consolidation. 13h is past both the 30-minute fairness bound and the 6-hour TTL, and neither fired.

The Problem

Both bounds are structurally unable to see a holder that is alive and not progressing:

  • maxActiveHoldMs is enforced by shouldYield() — a pure predicate the holder must choose to call. Its own docblock states the holder "yields at the first between-batch checkpoint after the bound, never mid-batch", and that it is "consumed by long heavy tasks between batches (e.g. kbSync embedViaShadowSwap)". A holder blocked inside one request that never settles never reaches a checkpoint, never asks, and nothing asks on its behalf.
  • staleAfterMs targets a lease that looks abandoned. A holder stuck inside a live call is not abandoned — it is holding.

withHeavyMaintenanceLease is await task(acquisition) inside a try/finally. There is no timer, no abort signal and no watchdog: the lease is held for exactly as long as the task takes to settle.

The Architectural Reality

  • ai/daemons/orchestrator/services/heavyMaintenanceLeasePrimitives.mjs:204shouldYieldHeavyMaintenanceLease, a pure now - acquiredAt > maxActiveHoldMs predicate with no scheduling of its own.
  • ai/daemons/orchestrator/services/heavyMaintenanceLeasePrimitives.mjs:970withHeavyMaintenanceLease, whose release lives only in the finally after await task(...).
  • ai/daemons/orchestrator/services/HeavyMaintenanceLeaseService.mjs:144shouldYield(), the reactive wrapper, documented as consumed between batches.
  • ai/configBase.mjsheavyMaintenanceLease.staleAfterMs (6h) and the maxActiveHoldMs fairness knob, explicitly described as SOFT.

Executed witness (already written, this ticket's deliverable)

A deterministic fixture reproduces the 13-hour shape in about four seconds: with a task that never settles, at acquiredAt + maxActiveHoldMs + 1ms the lease is still held, shouldYieldHeavyMaintenanceLease already returns true at that instant, and release happens only when the task settles.

The two bounds are set to production's ratio (6h TTL vs 30min yield) so active cannot be satisfied by staleness instead of by holding.

The Fix

Direction, not premature mechanism — and deliberately NOT delivered here.

The enabling repair is an effective deadline on the held operation, because a deadline is what manufactures the missing checkpoint: it converts a non-terminating request back into a between-batch boundary, at which point maxActiveHoldMs becomes reachable again. Any preemption added at the lease layer instead must answer what it does to a half-written shadow swap, which is why the deadline belongs at the operation and not at the mutex.

#16780 owns that deadline for the embedding path specifically (its AC-4). This ticket owns the general statement — that the lease's fairness guarantee is conditional on holder cooperation — and the witness that proves it.

Acceptance Criteria

  • An executed fixture proves the hold outlives maxActiveHoldMs while shouldYield already reports yield, and that release occurs only on task settle
  • The fixture cannot be satisfied by staleness — TTL exceeds the fairness bound at the observation point, matching production's ratio
  • The fixture's "lease is held" signal is deterministic (the task itself signals acquisition) rather than a timing assumption
  • The cooperative nature of maxActiveHoldMs is stated in its own docblock, so the next reader does not infer preemption from the word "bound"

Out of Scope

  • Implementing the deadline#16780 AC-4 for the embedding path; a general operation-deadline policy is a separate decision.
  • Adding preemption at the lease layer — it would abandon a holder mid-batch with no resumable checkpoint, which is the hazard maxActiveHoldMs was deliberately designed around.
  • The starvation incident itself (#16561, #16566) — this owns the mechanism, not the incidents.

Avoided Traps

  • Reading "bound" as "enforced". A pure predicate named shouldYield reads like a guarantee at the call site and is advisory in fact. That gap is the whole ticket.
  • Fixing it with a shorter TTL. Staleness targets abandonment; a stuck holder is alive, so no TTL value reaches it.
  • A racing fixture. A fixed-tick wait after withLease observes status: 'missing' — the async acquire has not landed — which reads as "no lease" rather than "held". The task-signals-start pattern is required for the assertion to mean what it says.

Related

#16561 (fairness absent — the predecessor this residual survives) · #16566 (the 13-hour hold) · #16780 (bounds provider work; owns the embedding deadline) · #16706

Live latest-open sweep: searched open issues for lease/preemption/fairness at 2026-08-09T16:55:23Z; only #16561 surfaced and it makes the opposite claim (no fairness, since addressed). A2A in-flight sweep: no claim on this scope.

Retrieval Hint: "heavy maintenance lease cooperative yield shouldYield no preemption non-terminating holder 13 hour"