Context
Split from #13586 (the heavy-maintenance fair picker). The picker (Part 1) landed in its PR and re-prioritizes the heavy lease at every release boundary — so a weeks-stale golden-path / starved dream now out-ranks a re-firing summary. This ticket is the separable Part 2: bounding how long summary holds the lease per sweep, so the picker gets release boundaries frequently rather than only after a whole drift batch.
It is filed separately because it touches a different subsystem (Memory Core SessionService + the MCP-server config template, ADR 0019 + mcp-config-template-change-guide), not the orchestrator scheduling layer #13586 lives in.
The Problem
SessionService.summarizeSessions drains the entire drift list in one child run (SessionService.mjs:1386, for (let i = 0; i < total; i += batchSize) over sessionsToSummarize = the full findSessionsToSummarize() result). While that child holds the heavy-maintenance lease, every other heavy task — dream, backup, memory-summary-backfill — is deferred. The live forensic (get_rem_pipeline_state: 371 undigested, recentCycles: []) shows dream starved behind exactly this hold.
Part 1's staleness picker only chooses a new winner when the lease is free (post-release). So the shorter each summary hold, the sooner dream/golden-path get their turn. With an unbounded hold, the picker waits out the whole batch.
The Architectural Reality
- Drain loop:
ai/services/memory-core/SessionService.mjs:1379-1387 — total = sessionsToSummarize.length; the loop processes all of them before the child exits + releases the lease.
- Config precedent:
summarizationBatchLimit is a leaf at ai/mcp/server/memory-core/config.template.mjs:123 (leaf(2000)) + mirrored in config.mjs, read at the use site as aiConfig.summarizationBatchLimit (ADR 0019 reactive Provider SSOT). A new maxSessionsPerSummarySweep leaf follows the same pattern.
- The drift sweep is self-continuing: processed sessions self-heal (count matches,
#13579), so the next sweep re-derives the remaining drift — capping per-sweep does not drop work, it chunks it.
The Fix
- Add a config-driven
maxSessionsPerSummarySweep leaf (small default, e.g. 5) in ai/mcp/server/memory-core/config.template.mjs + config.mjs, per the mcp-config-template-change-guide.
- Extract a pure
capSessionsForSweep(sessions, maxPerSweep) helper (slice to N when N is a positive integer, else passthrough); apply it to sessionsToSummarize before the loop. The child exits + releases the lease after ≤ N sessions; the next sweep continues the backlog.
- Read
aiConfig.maxSessionsPerSummarySweep at the use site (never re-implement/alias — ADR 0019).
Acceptance Criteria
Out of Scope
- The fair picker itself —
#13586 (delivered).
- The summary backlog never reaching idle (dead-letter + metric reconcile) —
#13590.
- The optimal cap value / per-session throughput tuning — an empirical follow-up once the mechanism lands.
Related
#13586 — heavy-maintenance fair picker (Part 1; this is Part 2).
#13590 — drift summarization never dead-letters / settles.
#12065 — Orchestrator-as-SSOT for the REM pipeline.
Decision Record impact
none — a config-driven bound within the existing summarization drain; ADR 0019-aligned (new leaf, use-site read).
Release classification: post-v13 agent-OS stability hardening. Boardless; board #13 (Agent Harness) candidate.
Retrieval Hint: "bounded summary lease-hold maxSessionsPerSummarySweep capSessionsForSweep" / SessionService.mjs:1386 drain loop / split from #13586 Part 2
Context
Split from
#13586(the heavy-maintenance fair picker). The picker (Part 1) landed in its PR and re-prioritizes the heavy lease at every release boundary — so a weeks-stalegolden-path/ starveddreamnow out-ranks a re-firingsummary. This ticket is the separable Part 2: bounding how longsummaryholds the lease per sweep, so the picker gets release boundaries frequently rather than only after a whole drift batch.It is filed separately because it touches a different subsystem (Memory Core
SessionService+ the MCP-server config template, ADR 0019 +mcp-config-template-change-guide), not the orchestrator scheduling layer#13586lives in.The Problem
SessionService.summarizeSessionsdrains the entire drift list in one child run (SessionService.mjs:1386,for (let i = 0; i < total; i += batchSize)oversessionsToSummarize= the fullfindSessionsToSummarize()result). While that child holds the heavy-maintenance lease, every other heavy task —dream,backup,memory-summary-backfill— is deferred. The live forensic (get_rem_pipeline_state: 371 undigested,recentCycles: []) showsdreamstarved behind exactly this hold.Part 1's staleness picker only chooses a new winner when the lease is free (post-release). So the shorter each
summaryhold, the soonerdream/golden-pathget their turn. With an unbounded hold, the picker waits out the whole batch.The Architectural Reality
ai/services/memory-core/SessionService.mjs:1379-1387—total = sessionsToSummarize.length; the loop processes all of them before the child exits + releases the lease.summarizationBatchLimitis a leaf atai/mcp/server/memory-core/config.template.mjs:123(leaf(2000)) + mirrored inconfig.mjs, read at the use site asaiConfig.summarizationBatchLimit(ADR 0019 reactive Provider SSOT). A newmaxSessionsPerSummarySweepleaf follows the same pattern.#13579), so the next sweep re-derives the remaining drift — capping per-sweep does not drop work, it chunks it.The Fix
maxSessionsPerSummarySweepleaf (small default, e.g.5) inai/mcp/server/memory-core/config.template.mjs+config.mjs, per the mcp-config-template-change-guide.capSessionsForSweep(sessions, maxPerSweep)helper (slice to N when N is a positive integer, else passthrough); apply it tosessionsToSummarizebefore the loop. The child exits + releases the lease after ≤ N sessions; the next sweep continues the backlog.aiConfig.maxSessionsPerSummarySweepat the use site (never re-implement/alias — ADR 0019).Acceptance Criteria
capSessionsForSweepis a pure, unit-tested helper: caps to a positive-integer N, passthrough otherwise.SessionService.summarizeSessionsprocesses ≤maxSessionsPerSummarySweepsessions per child run, then exits + releases the lease; remaining drift persists for the next sweep. Unit-tested at the helper grain.maxSessionsPerSummarySweepis a config leaf (template + config.mjs), env-overridable, read at the use site per ADR 0019.summaryno longer holds the lease for a whole batch; the deferral log showsdream/backupinterleaving within one cap-window;undigested(REM) drains.Out of Scope
#13586(delivered).#13590.Related
#13586— heavy-maintenance fair picker (Part 1; this is Part 2).#13590— drift summarization never dead-letters / settles.#12065— Orchestrator-as-SSOT for the REM pipeline.Decision Record impact
none— a config-driven bound within the existing summarization drain; ADR 0019-aligned (new leaf, use-site read).Release classification: post-v13 agent-OS stability hardening. Boardless; board #13 (Agent Harness) candidate.
Retrieval Hint: "bounded summary lease-hold maxSessionsPerSummarySweep capSessionsForSweep" /
SessionService.mjs:1386drain loop / split from #13586 Part 2