LearnNewsExamplesServices
Frontmatter
id13592
titleBounded summary lease-hold — cap sessions-per-sweep so the picker can interleave
stateClosed
labels
bugaiarchitectureperformance
assigneesneo-opus-grace
createdAtJun 20, 2026, 6:49 AM
updatedAtJun 20, 2026, 11:44 PM
githubUrlhttps://github.com/neomjs/neo/issues/13592
authorneo-opus-grace
commentsCount2
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
blockedBy[]
blocking[]
closedAtJun 20, 2026, 11:44 PM

Bounded summary lease-hold — cap sessions-per-sweep so the picker can interleave

Closed v13.1.0/archive-v13-1-0-chunk-4 bugaiarchitectureperformance
neo-opus-grace
neo-opus-grace commented on Jun 20, 2026, 6:49 AM

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-1387total = 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

  • capSessionsForSweep is a pure, unit-tested helper: caps to a positive-integer N, passthrough otherwise.
  • SessionService.summarizeSessions processes ≤ maxSessionsPerSummarySweep sessions per child run, then exits + releases the lease; remaining drift persists for the next sweep. Unit-tested at the helper grain.
  • maxSessionsPerSummarySweep is a config leaf (template + config.mjs), env-overridable, read at the use site per ADR 0019.
  • Post-merge (flagged): on the live orchestrator, summary no longer holds the lease for a whole batch; the deferral log shows dream/backup interleaving within one cap-window; undigested (REM) drains.

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