LearnNewsExamplesServices
Frontmatter
title>-
authorneo-opus-vega
stateMerged
createdAtJun 10, 2026, 2:24 PM
updatedAtJun 10, 2026, 2:37 PM
closedAtJun 10, 2026, 2:37 PM
mergedAtJun 10, 2026, 2:37 PM
branchesdevagent/12833-session-summary-timeout
urlhttps://github.com/neomjs/neo/pull/12837
Merged
neo-opus-vega
neo-opus-vega commented on Jun 10, 2026, 2:24 PM

Resolves #12833

SessionService.summarizeSession's synthesis call had no timeout — a slow in-band local synthesis ground to the provider socket cap (the ~30-min stall @neo-opus-ada surfaced root-causing #12830), stranding SummarizationJobs leases past their 5-min TTL (the 6 stuck in_progress-past-expiry jobs) and starving the REM cycle that defers behind the session-summary HeavyMaintenanceLease. This bounds the synthesis and degrades gracefully instead.

Authored by Claude Opus 4.8 (@neo-opus-vega).

Evidence: L1 (unit: timeout→degraded-fallback + within/over-band paths, mocked provider; withTimeout re-export; config-leaf migration confirmed) → L3 required for the live "synthesis aborts at the budget + no lease stuck past expiry" behavior. Residual: live timeout/lease behavior [#12833 Post-Merge].

What shipped

  • SessionService.summarizeSession — the synthesis call is now bounded: generateContent(prompt, {timeoutMs, operationLabel}) raced with a withTimeout backstop. The degraded-fallback trigger (previously size-precheck-skip only) now also fires on the timeout friction symptom, so a too-slow raw synthesis retries the compact (miniSummary/truncatedRaw) input instead of returning null.
  • sessionSummaryTimeoutMs — new env-overridable leaf (180000) in the memory-core config. Calibrated below the 300000ms SummarizationJobs lease TTL (claimSummarizationJob default) so the raw synthesis aborts before its own lease expires, leaving ~120s headroom for the degraded retry to finish within the lease. A synthesis approaching the lease TTL is already heading for the stuck-lease bug, so degrading it via the fast compact retry is the correct outcome.
  • withTimeout extracted from MemoryService to ai/services/memory-core/helpers/withTimeout.mjs (shared without a MemoryService ⇄ SessionService import cycle; re-exported from MemoryService so its existing spec is untouched).

Deltas from ticket

  • The ticket's consumerFrictionHelper.categorizeInvocationErrorPROVIDER_TIMEOUT_CODE structural adoption is deferred to a small follow-up. The degrade-on-timeout path already works via the existing /timed out/i regex (the provider's PROVIDER_TIMEOUT message matches it), so the structural check is a non-blocking robustness upgrade. Deferred here to keep this PR scoped to the SessionService stall fix and avoid churning unrelated grandfathered ticket-refs in that file's comments (a check-ticket-archaeology pre-commit friction). I'll carry it as a follow-up — it's also the sibling of the ask-path adoption in PR #12832, per the split with @neo-opus-grace.
  • Timeout default anchored to the lease TTL (180s < 300s) rather than a fresh model measurement (deferred — see Post-Merge Validation).

Test Evidence

  • npm run test-unit -- SessionSummaryDegradedFallback consumerFrictionHelper WithTimeout29 passed, incl. the new in-band raw that TIMES OUT → degraded compact summary regression + the existing oversize/within-band paths + the MemoryService.WithTimeout re-export spec.
  • The ESM import-race flake (#12693) surfaced transient failures in the untouched SessionService.ResumeValidation spec under concurrent workers; confirmed unrelated via an isolated 9/9 re-run.
  • SessionSummarization live-gemma4 tests are CI-skipped (NEO_TEST_SKIP_CI, bucket A #10903) + gated on a reachable local model; the 300s timeouts in local runs are the busy local endpoint, not this change (the 300s per-test cap is hit regardless of the 180s synthesis budget).
  • Config-leaf resolution verified: initServerConfigs --migrate-config propagates sessionSummaryTimeoutMs: leaf(180000) into the gitignored overlay; CI generates a fresh overlay from the template.

Config-template change (per mcp-config-template-change-guide)

  • Added key: sessionSummaryTimeoutMs (180000, env NEO_MEMORY_SESSION_SUMMARY_TIMEOUT_MS) in ai/mcp/server/memory-core/config.template.mjs.
  • Local config.mjs follow-up: each clone's gitignored config.mjs lacks the new leaf until regenerated — run npm run prepare -- --migrate-config (or node ./ai/scripts/setup/initServerConfigs.mjs --migrate-config). Until then aiConfig.sessionSummaryTimeoutMs resolves undefined; CI generates a fresh overlay so this is a clone-local step.
  • Harness restart: recommended for the memory-core server to load the new budget.
  • No gitignored config.mjs committed.

Post-Merge Validation

  • Regenerate config.mjs on active clones (--migrate-config) + restart the memory-core server so sessionSummaryTimeoutMs resolves.
  • Confirm a live slow/timed-out session synthesis aborts at ~180s and yields a degraded summary (not a 30-min stall, not a null skip); no SummarizationJobs lease stuck in_progress past expiry.
  • Refine sessionSummaryTimeoutMs against a worst-case session-synthesis measurement (must stay below the 300s lease TTL).

Related

  • #12065 — [Epic] Orchestrator-as-SSOT for the REM (Sandman) Pipeline (parent)
  • #12830 — corrupted-memory root-cause (@neo-opus-ada; handed this lane the L576 finding)
  • #12831 / PR #12832 — sibling KB-ask degraded-references (the PROVIDER_TIMEOUT_CODE split)
  • #12805 / #12722 — miniSummary timeout right-size / abort-hung (the mirrored pattern)
  • #12814 / #12818PROVIDER_TIMEOUT_CODE provider-timeout contract
tobiu
tobiu APPROVED reviewed on Jun 10, 2026, 2:37 PM

No review body provided.