Context
Sub of #13624 (orchestrator heavy-maintenance fairness). Root-cause located: the long summary task holds the heavy-maintenance lease for its whole backlog run (SummaryService.mjs:221-247 loops through all pending sessions in one lease-hold via gemma-4-31b), so an overdue dream (and its dependent golden-path) gets its window hours late behind the summary. Calibrated by @neo-opus-vega + @tobiu: the dream is not never-starved — it runs, but LONG/late. This is a fairness/latency defect: no single heavy task should be able to hold the lease long enough to starve a timely peer.
Per the #13624 divide-and-conquer (@neo-opus-vega), this leaf is the lease-domain half: the cooperative-yield primitive + the maxActiveHoldMs knob. (Ada owns the SummaryService chunked-yield + the picker window; Vega owns the Decision Record / fairness contract.)
The Problem
withHeavyMaintenanceLease holds the lease for the entire duration of task(). A long task (summary of N sessions) therefore monopolizes the single heavy-maintenance lease — there is no mechanism for it to cooperatively yield mid-run so an overdue higher-need task (dream) can interleave. The mutex is correct; what's missing is a bounded-active-hold signal a long task can poll to release-and-requeue.
The Architectural Reality
ai/daemons/orchestrator/services/HeavyMaintenanceLeaseService.mjs — acquireHeavyMaintenanceLease / withHeavyMaintenanceLease / releaseHeavyMaintenanceLease + the lease descriptor (acquiredAt, staleAfterMs). staleAfterMs (currently 6h) governs dead-holder reclamation — orthogonal to the active monopoly this leaf addresses.
- The lease descriptor already carries
acquiredAt, so active-hold age is computable read-only.
The Fix
A pure, exported helper, consumed by long heavy tasks:
shouldYieldHeavyMaintenanceLease({acquiredAt, now, maxActiveHoldMs}) — returns true when now - acquiredAt > maxActiveHoldMs. A long task polls it between work units and, when true, returns early (releasing the lease) so the picker can grant an overdue peer its window; the next periodic-sweep re-acquires for the remaining work.
The maxActiveHoldMs value/default and the caller wiring are OUT of scope for #13780 (see Evolution): the helper is value-agnostic — the caller passes the budget. The value belongs to #13624 (Vega's fairness Decision Record); the wiring to the consuming integration (Ada's SummaryService chunked-yield).
This is the lease-domain primitive the SummaryService chunked-yield (Ada) calls; it's uniform, so any heavy task inherits the fairness floor.
Contract Ledger Matrix
| Target Surface |
Source of Authority |
Proposed Behavior |
Fallback / Edge Case |
Docs |
Evidence |
shouldYieldHeavyMaintenanceLease (NEW exported helper) |
this ticket + #13624 |
pure: true when active-hold exceeds maxActiveHoldMs |
maxActiveHoldMs unset/0 ⇒ never yields (back-compat: current behavior) |
JSDoc |
unit: exceeds/within/boundary + unset |
maxActiveHoldMs config leaf — DESCOPED from #13780 (see Evolution) |
#13624 (Vega) + the consuming integration |
the helper is value-agnostic; the caller supplies the budget — value + wiring are NOT #13780's scope |
— |
Vega's ADR |
n/a here |
Evolution
Contract reconciliation (per @neo-gpt's #13781 review, 2026-06-21): #13780 ships the value-agnostic helper only (shouldYieldHeavyMaintenanceLease). The maxActiveHoldMs value/default and the caller wiring are explicitly out of scope — the helper takes the budget as a parameter; the value belongs to #13624 (Vega's fairness Decision Record) and the wiring to the consuming integration (Ada's SummaryService chunked-yield). This removes the close-target mismatch: #13781 (the helper + its unit tests) fully satisfies #13780's reconciled contract. The keep-vs-close question for the helper itself (uniform time-floor vs per-task count-caps) is the separate open #13624 convergence.
Acceptance Criteria
Out of Scope
- The SummaryService chunked-yield integration (process M per hold, release-and-requeue) — Ada, #13624.
- The picker giving the overdue dream its window — Ada, #13624.
- The fairness Decision Record / ADR — Vega, #13624.
staleAfterMs re-tuning — separate (dead-holder latency), tracked under #13624 if pursued.
Related
- Parent fairness epic: #13624
- Root-cause sibling: #13750 (golden-path latency), #13755 (heavy-lease typed-outcome)
Live latest-open sweep: checked latest 25 open issues 2026-06-21; no equivalent lease-yield ticket found.
Origin Session ID: 80932414-00dc-4d2e-96e6-d0bcf1529733
Retrieval Hint: "heavy-maintenance lease cooperative-yield maxActiveHoldMs summary monopolizes dream"
Context
Sub of #13624 (orchestrator heavy-maintenance fairness). Root-cause located: the long
summarytask holds the heavy-maintenance lease for its whole backlog run (SummaryService.mjs:221-247loops through all pending sessions in one lease-hold via gemma-4-31b), so an overduedream(and its dependentgolden-path) gets its window hours late behind the summary. Calibrated by @neo-opus-vega + @tobiu: the dream is not never-starved — it runs, but LONG/late. This is a fairness/latency defect: no single heavy task should be able to hold the lease long enough to starve a timely peer.Per the #13624 divide-and-conquer (@neo-opus-vega), this leaf is the lease-domain half: the cooperative-yield primitive + the
maxActiveHoldMsknob. (Ada owns the SummaryService chunked-yield + the picker window; Vega owns the Decision Record / fairness contract.)The Problem
withHeavyMaintenanceLeaseholds the lease for the entire duration oftask(). A long task (summary of N sessions) therefore monopolizes the single heavy-maintenance lease — there is no mechanism for it to cooperatively yield mid-run so an overdue higher-need task (dream) can interleave. The mutex is correct; what's missing is a bounded-active-hold signal a long task can poll to release-and-requeue.The Architectural Reality
ai/daemons/orchestrator/services/HeavyMaintenanceLeaseService.mjs—acquireHeavyMaintenanceLease/withHeavyMaintenanceLease/releaseHeavyMaintenanceLease+ the lease descriptor (acquiredAt,staleAfterMs).staleAfterMs(currently 6h) governs dead-holder reclamation — orthogonal to the active monopoly this leaf addresses.acquiredAt, so active-hold age is computable read-only.The Fix
A pure, exported helper, consumed by long heavy tasks:
shouldYieldHeavyMaintenanceLease({acquiredAt, now, maxActiveHoldMs})— returnstruewhennow - acquiredAt > maxActiveHoldMs. A long task polls it between work units and, when true, returns early (releasing the lease) so the picker can grant an overdue peer its window; the next periodic-sweep re-acquires for the remaining work.The
maxActiveHoldMsvalue/default and the caller wiring are OUT of scope for #13780 (see Evolution): the helper is value-agnostic — the caller passes the budget. The value belongs to #13624 (Vega's fairness Decision Record); the wiring to the consuming integration (Ada's SummaryService chunked-yield).This is the lease-domain primitive the SummaryService chunked-yield (Ada) calls; it's uniform, so any heavy task inherits the fairness floor.
Contract Ledger Matrix
shouldYieldHeavyMaintenanceLease(NEW exported helper)truewhen active-hold exceedsmaxActiveHoldMsmaxActiveHoldMsunset/0 ⇒ never yields (back-compat: current behavior)— DESCOPED from #13780 (see Evolution)maxActiveHoldMsconfig leafEvolution
Contract reconciliation (per @neo-gpt's #13781 review, 2026-06-21): #13780 ships the value-agnostic helper only (
shouldYieldHeavyMaintenanceLease). ThemaxActiveHoldMsvalue/default and the caller wiring are explicitly out of scope — the helper takes the budget as a parameter; the value belongs to #13624 (Vega's fairness Decision Record) and the wiring to the consuming integration (Ada's SummaryService chunked-yield). This removes the close-target mismatch: #13781 (the helper + its unit tests) fully satisfies #13780's reconciled contract. The keep-vs-close question for the helper itself (uniform time-floor vs per-task count-caps) is the separate open #13624 convergence.Acceptance Criteria
shouldYieldHeavyMaintenanceLease({acquiredAt, now, maxActiveHoldMs})returnstrueiffnow - acquiredAt > maxActiveHoldMs;falsewhen within, at boundary, ormaxActiveHoldMsfalsy (back-compat).staleAfterMs(dead-holder reclamation).Out of Scope
staleAfterMsre-tuning — separate (dead-holder latency), tracked under #13624 if pursued.Related
Live latest-open sweep: checked latest 25 open issues 2026-06-21; no equivalent lease-yield ticket found.
Origin Session ID: 80932414-00dc-4d2e-96e6-d0bcf1529733
Retrieval Hint: "heavy-maintenance lease cooperative-yield maxActiveHoldMs summary monopolizes dream"