Context
Parent #16561 established that a priority-0 task can win selection yet remain unable to acquire the heavy-maintenance lease. Its first measurement slice shipped in #16564: repeated deferrals keep a non-sliding anchor in one process. The remaining prerequisite exposed by PR #16900 is narrower: that anchor lived only in MaintenanceBackpressureService.deferralStreakStarts, so an orchestrator restart erased the elapsed wait.
Exact-head review at 2cbf5f9f49d475dca95bf6c4956f1878445bfe68 independently removed the production taskStateService carriage and proved the focused suites still passed 57/57. That does not falsify the source repair; it identifies the production-binding test debt retained below.
The Problem
A process-local deferral anchor makes a starvation threshold structurally restart-sensitive. A task can remain continuously unable to run while every daemon restart makes its wait appear fresh. The future health slice in #16561 therefore cannot consume a trustworthy elapsed duration until the first-deferral timestamp survives process lifetimes and clears only when the task actually starts.
This leaf is intentionally smaller than #16561. It delivers the durable measurement prerequisite; it does not claim the health threshold, its unprotected-data-window derivation, or lease fairness.
The Architectural Reality
ai/daemons/orchestrator/services/TaskStateService.mjs owns the persisted per-task envelope and merges stored fields over the current default on read.
ai/daemons/orchestrator/services/MaintenanceBackpressureService.mjs owns deferral recording and publishes deferredSince on skipped outcomes.
ai/daemons/orchestrator/Orchestrator.mjs already injects the canonical TaskStateService into MaintenanceBackpressureService.
TaskStateService.markStarted() is the universal transition proving that the task is no longer deferred.
- ADR 0022 AC-3 requires stall and deferral observation to ride the durable task-state flow rather than an ephemeral in-memory collection.
Structure-map gate: npm run --silent ai:structure-map -- --files --loc resolves this work to the existing ai/daemons/orchestrator/services/ owners; no new module is introduced.
The Fix
- Add nullable
deferralStreakStartedAt to the persisted task-state envelope.
- Open it with
??= on the first deferral so later polls and blocker changes cannot slide the anchor.
- Persist and return it through
TaskStateService.markDeferred().
- Make
MaintenanceBackpressureService.recordDeferral() prefer that durable result over its process-local fallback.
- Clear the streak in
TaskStateService.markStarted().
- Preserve legacy state files by merging an absent field to
null; no migration writer is required.
Contract Ledger Matrix
| Target Surface |
Source of Authority |
Proposed Behavior |
Fallback |
Docs |
Evidence |
persisted task-state deferralStreakStartedAt |
TaskStateService |
first deferral opens once; subsequent deferrals preserve it; markStarted() clears it |
legacy/absent field reads as null |
JSDoc on the envelope and writer |
first-anchor, restart, clear, and fresh-after-run unit controls in PR #16900 |
skipped outcome deferredSince |
MaintenanceBackpressureService.recordDeferral() consuming the task-state writer |
durable anchor wins over the process-local map |
direct callers without a task-state service retain the existing process-local behavior |
existing outcome JSDoc |
production source trace; composition mutation debt is owned by #16904 |
Decision Record impact
aligned-with ADR 0022 — specifically AC-3's durable task-state placement. The work does not change ADR 0014's task taxonomy, the picker, lease ownership, or scheduling behavior.
Acceptance Criteria
Out of Scope
- The health-surface threshold and its unprotected-data-window derivation, retained by #16561.
- Lease waiter registration, preemption, or fairness policy.
- Changes to
PRIORITY_ZERO_TASKS, staleAfterMs, cadence, or picker ranking.
- Live restart validation on a deployed plane; PR #16900 records this as L3-deferred.
Avoided Traps
- Closing #16561 from a prerequisite-only PR. Four parent acceptance criteria remain intentionally open.
- Persisting every skip. “Nothing to do” is not deferral; only the deferral writer opens the streak.
- Sliding the timestamp on every poll. That would make the eventual threshold unreachable.
- Deleting the process-local fallback. Direct pure-function callers retain their prior bounded behavior.
Related
- Parent: #16561
- Prior non-sliding measurement slice: #16564
- Implementation: PR #16900
- Production-binding and write-churn follow-up: #16904
- Durable-flow authority: ADR 0022
Live latest-open sweep: checked the 20 newest open issues immediately before creation; no equivalent restart-durability leaf existed. Exact title/body sweep found only parent #16561 and resolved predecessor #16564. A2A in-flight sweep: checked the latest 30 messages across read states; Vega's #16561 implementation lane was the sole overlapping claim and is the implementation this leaf records.
Origin Session ID: d813fe26-4096-4c43-81fc-be4241b270cb
Retrieval Hint: query_raw_memories("priority-zero deferral streak process-local restart TaskStateService #16561 #16900")
Context
Parent #16561 established that a priority-0 task can win selection yet remain unable to acquire the heavy-maintenance lease. Its first measurement slice shipped in #16564: repeated deferrals keep a non-sliding anchor in one process. The remaining prerequisite exposed by PR #16900 is narrower: that anchor lived only in
MaintenanceBackpressureService.deferralStreakStarts, so an orchestrator restart erased the elapsed wait.Exact-head review at
2cbf5f9f49d475dca95bf6c4956f1878445bfe68independently removed the productiontaskStateServicecarriage and proved the focused suites still passed 57/57. That does not falsify the source repair; it identifies the production-binding test debt retained below.The Problem
A process-local deferral anchor makes a starvation threshold structurally restart-sensitive. A task can remain continuously unable to run while every daemon restart makes its wait appear fresh. The future health slice in #16561 therefore cannot consume a trustworthy elapsed duration until the first-deferral timestamp survives process lifetimes and clears only when the task actually starts.
This leaf is intentionally smaller than #16561. It delivers the durable measurement prerequisite; it does not claim the health threshold, its unprotected-data-window derivation, or lease fairness.
The Architectural Reality
ai/daemons/orchestrator/services/TaskStateService.mjsowns the persisted per-task envelope and merges stored fields over the current default on read.ai/daemons/orchestrator/services/MaintenanceBackpressureService.mjsowns deferral recording and publishesdeferredSinceon skipped outcomes.ai/daemons/orchestrator/Orchestrator.mjsalready injects the canonicalTaskStateServiceintoMaintenanceBackpressureService.TaskStateService.markStarted()is the universal transition proving that the task is no longer deferred.Structure-map gate:
npm run --silent ai:structure-map -- --files --locresolves this work to the existingai/daemons/orchestrator/services/owners; no new module is introduced.The Fix
deferralStreakStartedAtto the persisted task-state envelope.??=on the first deferral so later polls and blocker changes cannot slide the anchor.TaskStateService.markDeferred().MaintenanceBackpressureService.recordDeferral()prefer that durable result over its process-local fallback.TaskStateService.markStarted().null; no migration writer is required.Contract Ledger Matrix
deferralStreakStartedAtTaskStateServicemarkStarted()clears itnulldeferredSinceMaintenanceBackpressureService.recordDeferral()consuming the task-state writerDecision Record impact
aligned-with ADR 0022— specifically AC-3's durable task-state placement. The work does not change ADR 0014's task taxonomy, the picker, lease ownership, or scheduling behavior.Acceptance Criteria
deferralStreakStartedAtvalue and later deferrals do not slide it.TaskStateServiceinstance reading the same state file recovers that original value.null.Out of Scope
PRIORITY_ZERO_TASKS,staleAfterMs, cadence, or picker ranking.Avoided Traps
Related
Live latest-open sweep: checked the 20 newest open issues immediately before creation; no equivalent restart-durability leaf existed. Exact title/body sweep found only parent #16561 and resolved predecessor #16564. A2A in-flight sweep: checked the latest 30 messages across read states; Vega's #16561 implementation lane was the sole overlapping claim and is the implementation this leaf records.
Origin Session ID: d813fe26-4096-4c43-81fc-be4241b270cb
Retrieval Hint:
query_raw_memories("priority-zero deferral streak process-local restart TaskStateService #16561 #16900")