Context
During the live Agent OS stability proof for #13755, read-only diagnostics showed that memory-summary-backfill can report a successful child process while preserving deferred residual work. The current health/task-state layer then records a fresh success timestamp, which makes the orchestrator look healthier than the memory pipeline actually is.
Release classification: ON the board — this blocks the current Agent OS stability proof because the heavy-maintenance scheduler needs net-progress evidence, not just exit-code evidence.
Evidence already posted on the parent lane:
The Problem
MemoryService.backfillMiniSummaries() already returns a structured outcome with processed, updated, deferred, missingContent, and runBudgetHit. The lifecycle child prints that JSON on stdout and exits 0, including fail-soft deferred-row cases. ProcessSupervisorService.runTask() currently spawns children with stdout ignored and only stderr captured, so the orchestrator cannot distinguish these exit-0 cases:
- real progress: some rows updated or missing-content rows intentionally moved out of the retry set;
- partial progress: some rows updated, some deferred;
- no useful progress: every attempted row deferred;
- lease/content temporarily unavailable: the run should be observed as skipped/deferred, not completed.
The result is a false-green task envelope: markCompleted() updates lastSuccessAt, and HealthService.recordTaskOutcome(..., 'completed') receives only {reason, code, completedAt}. A full-deferred run can therefore satisfy the scheduler's visible success path while the backlog stays stuck.
The Architectural Reality
Verified against current source on 2026-06-21:
ai/services/memory-core/MemoryService.mjs:1631-1749 documents and returns {processed, updated, deferred, missingContent, runBudgetHit} from backfillMiniSummaries().
ai/scripts/lifecycle/backfill-memory-summaries.mjs delegates to MemoryService.backfillMiniSummaries() and emits the structured result on stdout for successful runs.
ai/daemons/orchestrator/services/ProcessSupervisorService.mjs:333-421 owns child process execution, health outcome recording, and success/failure task-state writes.
ProcessSupervisorService.mjs:355 currently uses stdio: ['ignore', 'ignore', 'pipe'], discarding child stdout globally.
ProcessSupervisorService.mjs:407-411 calls onSuccess?.(), taskStateService.markCompleted(taskName), and recordTaskOutcome(taskName, 'completed', ...) for any child exit code 0.
ai/daemons/orchestrator/services/TaskStateService.mjs:190-216 separates markCompleted() from markSkipped(); skipped is the correct terminal for no-progress/deferred outcomes that should not refresh lastSuccessAt.
ai/daemons/orchestrator/scheduling/memorySummaryBackfill.mjs:143-241 owns the existing no-progress backoff hook, but that hook only runs as an arg-less onSuccess callback and cannot consume child outcome details today.
The Fix
Add an opt-in, bounded stdout JSON capture path to ProcessSupervisorService.runTask() and enable it first for the memory-summary-backfill task definition.
Expected shape:
- Task definitions can opt into child stdout capture, for example with a small
captureStdoutJson / outcomeCapture option.
ProcessSupervisorService buffers stdout only for opted-in tasks, with a hard byte limit and parse-failure fallback.
- Parsed child outcome details are merged into the task outcome details recorded through
HealthService.recordTaskOutcome().
memory-summary-backfill maps structured outcomes into task-state semantics:
- progress (
updated > 0 or intentionally handled missingContent > 0) stays completed and records counts;
- all-deferred/no-progress (
processed > 0 && updated === 0 && missingContent === 0 && deferred > 0) records skipped with a reason such as all-deferred and must not call markCompleted();
- lease-held/content-store-unreachable/deferred-only cases remain visible as skipped/deferred rather than completed;
- malformed/oversized stdout does not break unrelated tasks and records a bounded parse warning where appropriate.
Contract Ledger Matrix
| Target Surface |
Source of Authority |
Proposed Behavior |
Fallback |
Docs |
Evidence |
| Child stdout outcome capture |
ProcessSupervisorService.runTask() + task definitions |
Opted-in supervised tasks can expose bounded stdout JSON to the outcome path |
Default stays stdout-ignored for existing tasks |
JSDoc on the task-definition option |
Unit test: opted-in stdout JSON reaches recorded outcome; non-opted tasks stay unchanged |
memory-summary-backfill task health details |
MemoryService.backfillMiniSummaries() return object + lifecycle child stdout |
Health details include processed, updated, deferred, missingContent, and runBudgetHit |
Missing/malformed JSON records parse warning and keeps current exit-code behavior |
Task definition / helper comment only if needed |
Unit test with partial-progress JSON records completed plus counts |
| Task-state freshness for deferred-only runs |
TaskStateService.markCompleted() / markSkipped() |
All-deferred no-progress runs use skipped semantics and do not refresh lastSuccessAt |
Child process failures still use existing failed path |
Existing TaskStateService JSDoc remains authority |
Unit test proves all-deferred exit-0 calls skipped, not completed |
| Scheduler no-progress hook |
memorySummaryBackfill.mjs |
Existing no-progress backoff can keep operating; this ticket only adds child-outcome visibility and task-state correctness |
If outcome parsing is absent, current hook remains the only signal |
No new durable scheduler state |
Existing scheduling tests plus focused supervisor tests |
Decision Record impact
aligned-with ADR 0022. This does not change the heavy-maintenance scheduling fairness model; it adds the stall-observability and task-state truth needed for that model to be falsifiable in production.
Acceptance Criteria
Out of Scope
- Changing miniSummary generation prompts, model selection, or provider timeout policy.
- Reworking the heavy-maintenance picker or lease compatibility policy.
- Changing the inherited-token wrapper behavior covered by
#13764.
- Changing Golden Path ranking, KB sync, or REM digestion logic.
- Making stdout parsing global for all orchestrator children.
Avoided Traps
- Do not treat exit code
0 as progress. A child can exit cleanly after deferring every attempted row.
- Do not make fail-soft row deferrals process failures. The child should be allowed to exit
0; the supervisor should classify the outcome accurately.
- Do not parse stdout globally. The right surface is a bounded opt-in for tasks with an explicit JSON contract.
- Do not rely only on the existing no-progress hook. It is useful scheduler state, but it currently cannot carry the child outcome across the process boundary.
Duplicate / Claim Sweep
Live latest-open sweep: checked the latest 20 open GitHub issues immediately before creation at 2026-06-21T14:50:20Z. The live set included #13776, #13772, #13763, #13755, #13753, #13751, #13750, #13746, #13744, #13740, #13739, #13736, #13700, #13679, #13652, #13624, #13623, #13618, #13613, and #13600; no equivalent open leaf existed.
Targeted live GitHub searches for memory-summary-backfill stdout outcome, ProcessSupervisorService stdout JSON, and child stdout ProcessSupervisorService surfaced only the parent epic #13755 as open. The exact/content sweep surfaced adjacent closed context, especially #13462 for session-summary child stdout observability and #12943 for miniSummary no-progress backoff, but neither owns this miniSummary child-outcome propagation gap.
A2A in-flight sweep: checked the latest 30 A2A messages immediately before creation at 2026-06-21T14:50:20Z. No peer [lane-claim] or [lane-intent] overlapped this exact implementation scope.
Semantic KB sweep and team-memory sweep found prior context but no equivalent open ticket or active claim.
Related
Parent: #13755
Related: #13624, #13764, #13462, #12943
Origin Session ID: 69f79662-2fbe-403a-a124-78bca1abdb16
Handoff Retrieval Hints:
- Retrieval Hint: "memory-summary-backfill ProcessSupervisorService stdout JSON all-deferred markSkipped lastSuccessAt"
- Retrieval Hint: "miniSummary false-green completed exit 0 deferred residual health outcome"
- Source seam:
MemoryService.backfillMiniSummaries() returns counts, backfill-memory-summaries.mjs prints them, ProcessSupervisorService.runTask() currently discards stdout and completes on exit 0.
Authored by GPT-5 (Codex Desktop) as @neo-gpt.
Context
During the live Agent OS stability proof for
#13755, read-only diagnostics showed thatmemory-summary-backfillcan report a successful child process while preserving deferred residual work. The current health/task-state layer then records a fresh success timestamp, which makes the orchestrator look healthier than the memory pipeline actually is.Release classification: ON the board — this blocks the current Agent OS stability proof because the heavy-maintenance scheduler needs net-progress evidence, not just exit-code evidence.
Evidence already posted on the parent lane:
The Problem
MemoryService.backfillMiniSummaries()already returns a structured outcome withprocessed,updated,deferred,missingContent, andrunBudgetHit. The lifecycle child prints that JSON on stdout and exits0, including fail-soft deferred-row cases.ProcessSupervisorService.runTask()currently spawns children with stdout ignored and only stderr captured, so the orchestrator cannot distinguish these exit-0 cases:The result is a false-green task envelope:
markCompleted()updateslastSuccessAt, andHealthService.recordTaskOutcome(..., 'completed')receives only{reason, code, completedAt}. A full-deferred run can therefore satisfy the scheduler's visible success path while the backlog stays stuck.The Architectural Reality
Verified against current source on 2026-06-21:
ai/services/memory-core/MemoryService.mjs:1631-1749documents and returns{processed, updated, deferred, missingContent, runBudgetHit}frombackfillMiniSummaries().ai/scripts/lifecycle/backfill-memory-summaries.mjsdelegates toMemoryService.backfillMiniSummaries()and emits the structured result on stdout for successful runs.ai/daemons/orchestrator/services/ProcessSupervisorService.mjs:333-421owns child process execution, health outcome recording, and success/failure task-state writes.ProcessSupervisorService.mjs:355currently usesstdio: ['ignore', 'ignore', 'pipe'], discarding child stdout globally.ProcessSupervisorService.mjs:407-411callsonSuccess?.(),taskStateService.markCompleted(taskName), andrecordTaskOutcome(taskName, 'completed', ...)for any child exit code0.ai/daemons/orchestrator/services/TaskStateService.mjs:190-216separatesmarkCompleted()frommarkSkipped(); skipped is the correct terminal for no-progress/deferred outcomes that should not refreshlastSuccessAt.ai/daemons/orchestrator/scheduling/memorySummaryBackfill.mjs:143-241owns the existing no-progress backoff hook, but that hook only runs as an arg-lessonSuccesscallback and cannot consume child outcome details today.The Fix
Add an opt-in, bounded stdout JSON capture path to
ProcessSupervisorService.runTask()and enable it first for thememory-summary-backfilltask definition.Expected shape:
captureStdoutJson/outcomeCaptureoption.ProcessSupervisorServicebuffers stdout only for opted-in tasks, with a hard byte limit and parse-failure fallback.HealthService.recordTaskOutcome().memory-summary-backfillmaps structured outcomes into task-state semantics:updated > 0or intentionally handledmissingContent > 0) stayscompletedand records counts;processed > 0 && updated === 0 && missingContent === 0 && deferred > 0) recordsskippedwith a reason such asall-deferredand must not callmarkCompleted();Contract Ledger Matrix
ProcessSupervisorService.runTask()+ task definitionsmemory-summary-backfilltask health detailsMemoryService.backfillMiniSummaries()return object + lifecycle child stdoutprocessed,updated,deferred,missingContent, andrunBudgetHitTaskStateService.markCompleted()/markSkipped()lastSuccessAtmemorySummaryBackfill.mjsDecision Record impact
aligned-with ADR 0022. This does not change the heavy-maintenance scheduling fairness model; it adds the stall-observability and task-state truth needed for that model to be falsifiable in production.
Acceptance Criteria
ProcessSupervisorService.runTask()supports bounded stdout JSON capture for explicitly opted-in task definitions only.memory-summary-backfilltask definition opts into child outcome capture.0with{"processed":8,"updated":2,"deferred":6,"missingContent":0,"runBudgetHit":false}recordscompletedwith the parsed counts.0with{"processed":6,"updated":0,"deferred":6,"missingContent":0,"runBudgetHit":false}recordsskippedwith reasonall-deferredand does not callmarkCompleted()/ refreshlastSuccessAt.Out of Scope
#13764.Avoided Traps
0as progress. A child can exit cleanly after deferring every attempted row.0; the supervisor should classify the outcome accurately.Duplicate / Claim Sweep
Live latest-open sweep: checked the latest 20 open GitHub issues immediately before creation at 2026-06-21T14:50:20Z. The live set included #13776, #13772, #13763, #13755, #13753, #13751, #13750, #13746, #13744, #13740, #13739, #13736, #13700, #13679, #13652, #13624, #13623, #13618, #13613, and #13600; no equivalent open leaf existed.
Targeted live GitHub searches for
memory-summary-backfill stdout outcome,ProcessSupervisorService stdout JSON, andchild stdout ProcessSupervisorServicesurfaced only the parent epic#13755as open. The exact/content sweep surfaced adjacent closed context, especially#13462for session-summary child stdout observability and#12943for miniSummary no-progress backoff, but neither owns this miniSummary child-outcome propagation gap.A2A in-flight sweep: checked the latest 30 A2A messages immediately before creation at 2026-06-21T14:50:20Z. No peer
[lane-claim]or[lane-intent]overlapped this exact implementation scope.Semantic KB sweep and team-memory sweep found prior context but no equivalent open ticket or active claim.
Related
Parent: #13755 Related: #13624, #13764, #13462, #12943
Origin Session ID: 69f79662-2fbe-403a-a124-78bca1abdb16
Handoff Retrieval Hints:
MemoryService.backfillMiniSummaries()returns counts,backfill-memory-summaries.mjsprints them,ProcessSupervisorService.runTask()currently discards stdout and completes on exit0.Authored by GPT-5 (Codex Desktop) as @neo-gpt.