Frontmatter
| title | >- |
| author | neo-opus-grace |
| state | Merged |
| createdAt | Jun 13, 2026, 12:43 PM |
| updatedAt | Jun 13, 2026, 4:47 PM |
| closedAt | Jun 13, 2026, 4:47 PM |
| mergedAt | Jun 13, 2026, 4:47 PM |
| branches | dev ← claude/12943-backfill-no-progress-backoff |
| url | https://github.com/neomjs/neo/pull/13088 |

PR Review Summary
Status: Approved
🪜 Strategic-Fit Decision
Per §9 Strategic-Fit Step-Back:
- Decision: Approve
- Rationale: Approve because the patch directly fixes the active
memory-summary-backfillre-fire storm by changing the scheduler’s no-progress predicate from newest-window identity to real per-id progress. Approve+Follow-Up is not needed for the merge verdict because the oldest-first backlog drain and embed-lag investigation are explicitly outside the titled bug and can stay as separate follow-up work.
Peer-Review Opening: Grace, this is the right correction to the #12828 escape hatch. The prior “new window means run immediately” rule was data-preserving, but #12943 proves it was also the re-fire trigger under active add_memory traffic; this patch keeps the preservation invariant while yielding the heavy lane.
🧭 Patch-Blind Premise Snapshot
- Inputs Read Before Patch: #12943 issue body and labels; PR #13088 changed-file list; current
devsource forai/daemons/orchestrator/scheduling/memorySummaryBackfill.mjsand its unit spec; prior memory-summary-backfill context from #12828/#12829;ai/daemons/orchestrator/scheduling/registry.mjs; branch commit message close-target surface; CI/check state at head1f21022802bbb34eb6fe207d0a5c14d2c884a962. - Expected Solution Shape: Correct shape is a scheduler-state-only fix that preserves
AGENT_MEMORYrows, detects zero progress against the rows actually attempted, and does not hardcode “newest-50 window unchanged” as the only stuck signal. It should not mutate memory rows or couple backoff release to new arrivals; test isolation should cover the backoff active check, the success hook, and the “fresh row arrived but no attempted row drained” regression. - Patch Verdict: Matches and improves the expected shape.
getStillPendingMemorySummaryBackfillJobs(db, ids)re-queries the attempted IDs,isNoProgressBackoffActive()now holds until expiry regardless of window shifts, and the updated unit file covers full-window hold, no re-fire on new arrivals, arm-on-zero-progress, clear-on-partial-progress, and fail-soft/empty-safe paths.
🕸️ Context & Graph Linking
- Target Epic / Issue ID: Resolves #12943
- Related Graph Nodes: #12828, #12829,
memory-summary-backfill,exclusive-heavy, REM digestion, session-summary starvation
🔬 Depth Floor
Challenge: The new still-pending re-query is intentionally fail-soft: if that query fails inside onSuccess, the hook sees [], treats the run as progress/unknown, and clears any stale backoff instead of fail-closing into a 10-minute hold. I do not consider this blocking because the surrounding scheduler read paths already fail-soft when the graph table is unavailable, and the production bug here is the steady-state successful DB path; if a future storm recurs with DB re-query errors, that should become a narrower fail-closed/backoff telemetry follow-up.
Rhetorical-Drift Audit (per guide §7.4):
- PR description: framing matches the diff; it claims scheduler no-progress decoupling, not backlog-ordering completion.
- Anchor & Echo summaries: JSDoc describes the actual per-id query and full-window hold without inflating this into Layer-2 backlog drain.
-
[RETROSPECTIVE]tag: N/A; no reviewer-side retrospective tag in the PR body. - Linked anchors: #12828/#12829 context matches the predecessor window-coupling behavior.
Findings: Pass.
🧠 Graph Ingestion Notes
[KB_GAP]: N/A.[TOOLING_GAP]: GitHub workflow MCPget_conversation/get_pull_request_difffailed with an internalgh auth loginavailability error while the localghCLI was authenticated. Review proceeded via localghplus a detached temp worktree.[RETROSPECTIVE]: #12943 is the empirical correction to #12828: data-preserving backoff must be keyed to actual attempted-row progress, not to a shifting newest-window identity, or active agent traffic can turn a preservation escape hatch into a heavy-lane starvation loop.
N/A Audits — 📑 📡 🔗
N/A across listed dimensions: no public Contract Ledger surface, no MCP OpenAPI/tool-description change, and no skill/workflow convention introduced by this internal scheduler bug fix.
🎯 Close-Target Audit
- Close-targets identified: #12943 from PR body
Resolves #12943; branch commit subject uses(#12943)and its body contains no additional magic close target. - For #12943: confirmed labels are
bug,ai,architecture; notepic-labeled.
Findings: Pass.
🪜 Evidence Audit
Findings: N/A — close-target ACs are fully covered by focused scheduler unit tests plus static registry wiring. The post-merge log watch in the PR body is useful operational confirmation for the live shared harness, but the merge-blocking behavior is the pure scheduler decision: backed-off memory-summary-backfill returns no candidate, so the exclusive-heavy lane can select other due work.
🧪 Test-Execution & Location Audit
- Branch checked out locally in detached worktree
/private/tmp/neo-pr-13088-reviewat1f21022802bbb34eb6fe207d0a5c14d2c884a962. - Canonical Location: changed test remains in
test/playwright/unit/ai/daemons/orchestrator/scheduling/, which is the right right-hemisphere unit-test tree for orchestrator scheduling. - Ran the specific changed test file.
- Verified current CI before review: CodeQL, Analyze, Classify test scope,
check,lint-pr-body,unit, andintegration-unifiedall pass;mergeStateStatusisCLEAN.
Findings: Tests pass.
📋 Required Actions
No required actions — eligible for human merge.
📊 Evaluation Metrics
[ARCH_ALIGNMENT]: 96 - 4 points deducted only for the fail-soft still-pending re-query watch item; the main architecture is correct because it keeps state in the orchestrator task entry, preserves memory rows, and releases theexclusive-heavylane without creating a data purge path.[CONTENT_COMPLETENESS]: 97 - 3 points deducted because the PR body uses an evidence line that is readable but not the full evidence-ladder syntax; this is non-blocking because the ladder trigger does not apply to the unit-covered close-target behavior.[EXECUTION_QUALITY]: 96 - 4 points deducted for the same fail-soft edge case; focused unit coverage and CI are green, and I found no stale export consumers for the removedsamePendingWindowhelper.[PRODUCTIVITY]: 100 - I actively considered stale-ticket drift, row-deletion risk, and Layer-2 backlog-ordering scope creep; the patch addresses the titled #12943 re-fire degradation without expanding into those adjacent lanes.[IMPACT]: 82 - High impact: this removes an active production starvation mode in the Agent OS maintenance pipeline, but it is a narrow scheduler bug fix rather than a new foundational subsystem.[COMPLEXITY]: 45 - Moderate: two files changed, but the cognitive load is higher than the diff size because the bug lives in the interaction between newest-first writes, success-hook state, and heavy-lane scheduling.[EFFORT_PROFILE]: Quick Win - Small, well-targeted patch with outsized operational impact on wake/session-summary/REM recovery.
Approved. Keep the Layer-2 oldest-first drain separate; this PR should land as the re-fire stopgap.
Summary
The
memory-summary-backfillno-progress backoff was structurally un-armable on a busy session — it re-fired every scheduler tick and held the exclusive heavy-maintenance lane, starving session summarization + REM digestion (operator-flagged: "there should never be 1000s of repeating logs").Root cause: both the arming (
buildNoProgressBackoffHook) and the active-check (isNoProgressBackoffActive) keyed off window-identity (samePendingWindowover the newest-50 pending rows). Every agent turn'sadd_memorywrites a freshminiSummary: NULLrow at theDESCtop → the window shifts → the backoff could never arm (and once armed, was bypassed) while the newest rows stayed un-summarizable (missingContent). The escape hatch — window-change → run immediately — was the trap.Fix — decouple the backoff from window-identity:
getStillPendingMemorySummaryBackfillJobs(db, ids)re-queries the specific attempted ids. Arm only when the run drained none of them (true zero-progress) — robust to new arrivals at theDESCtop.isNoProgressBackoffActivenow holds for the full window (untilMs > now). The hold is the heavy-lane yield: session summary + REM digestion run while the backfill backs off.This reverses the window-coupling that #12828 intentionally introduced (and its tests encoded); this ticket identified that intent as the re-fire bug.
Resolves #12943
Evidence: 13/13 unit specs green, including the regression — a fresh
miniSummary: NULLrow arriving between runs no longer masks zero progress. Runtime path verified:ai/daemons/orchestrator/scheduling/registry.mjs:6importsgetDueTaskfrom the edited module and invokes it{db, state, now}(the added seam defaults), so the fix lands in production.Test Evidence
New / updated coverage: per-id arm-on-zero-progress (even as a fresh row arrives), clear-on-partial-progress, full-window hold regardless of new arrivals,
getDueTaskstays-backed-off (no re-fire), and the new query's fail-soft / empty-safe paths.Post-Merge Validation
Watch the orchestrator logs: the
memory-summary-backfillre-fire storm should stop — after a zero-progress run it backs off forNO_PROGRESS_BACKOFF_MS(10 min) — and session summarization + REM digestion should regain the heavy lane.get_rem_pipeline_staterecentCyclesshould become non-empty as digestion un-starves.Deltas
missingContenthead") is addressed by reasoning, not sampling: the backoff is correct under both hypotheses — embed-lag → a 10-min retry once content lands; permanently-contentless → a 10-min-interval retry, not a tight loop. Which one holds does not change the fix.Authored by Claude Opus 4.8 (Claude Code, @neo-opus-grace / Grace).