Context
#12833 / PR #12837 (merged today, b6273b086) bounded summarizeSession's synthesis with sessionSummaryTimeoutMs (flat 180s) + a degrade-on-timeout fallback — killing the unbounded ~30-min stall. This is its calibration follow-up: 180s is a single flat value, but the synthesis runs on either a local or a remote provider with wildly different latency profiles.
The sibling ask path already solved this (#12868): askSynthesis.{timeoutMs: 300s (local), timeoutMsRemote: 60s}, selected at the use-site by provider class — "no single value can serve both a 10-second remote and a 5-minute local synthesis" (its JSDoc, ai/mcp/server/knowledge-base/config.template.mjs:196-204). The summary leaf has no such split. 180s sits below the 300s local ceiling that path established for a smaller synthesis, so on local gemma a session summary will routinely time out and drop to the degraded tier (full-fidelity summaries effectively never produced locally — an inference from the ~287s ask benchmark; the summary-specific latency is what epic Sub 8 / #12074 measures). @neo-opus-grace surfaced this and agreed the split is the right follow-up; the degrades-not-fails distinction (below) was reconciled in A2A.
The Problem (precise)
As of #12837 a too-slow synthesis degrades (provenance-labeled compact summary), it does not fail/null (SessionService.mjs:617-645). So this is a summary-quality (degraded-frequency) calibration, NOT a job-failure fix. A genuinely null summary post-#12837 would mean the degraded path also failed — a separate defect, out of scope here.
The Fix
Mirror the askSynthesis shape on the summary leaf:
ai/mcp/server/memory-core/config.{template,}.mjs:145 — split sessionSummaryTimeoutMs into a local-class budget (keep NEO_MEMORY_SESSION_SUMMARY_TIMEOUT_MS; raise default 180s → 300s, the established local floor — defensible interim pending #12074) + a remote-class budget (*_REMOTE, ~60s fail-fast). No breaking change for deployments already setting the env var.
ai/services/memory-core/SessionService.mjs:576 — select the budget by provider class, reusing the existing :570-573 modelProvider branch.
- Keep the degraded retry (
:635) on a tight cap (the compact prompt clears fast) so worst-case lease hold isn't 2× localRaw — protects the HeavyMaintenanceLease / REM cycle from longer starvation.
Contract Ledger
| Surface |
Authority |
Behavior |
Fallback |
Evidence |
sessionSummaryTimeoutMs (memory-core config) |
this ticket; mirrors #12868 |
split local (300s, env) + remote (~60s); use-site provider selection |
current: flat 180s for all providers |
config.template.mjs:145; cf. knowledge-base/config.template.mjs:195,206 |
degraded-retry timeout (SessionService.mjs:635) |
:584-589 shared closure |
tight cap independent of local raw budget |
current: inherits summaryTimeoutMs (2× worst case) |
both calls re-enter runGuardrailed |
Decision Record impact
none — no ADR governs the summarization timeout (consistent with #12833); aligned-with the #12868 provider-class-timeout convention.
Acceptance Criteria
Out of Scope
- Final empirically-tuned local value — epic Sub 8 /
#12074 (operator-run gemma4 benchmark); this ships the shape + a defensible 300s interim.
null/failed-summary investigation (degraded path itself failing post-#12837) — separate bug if observed.
GoldenPathSynthesizer.mjs:927 no-explicit-timeout consistency nit (flagged by @neo-opus-grace; small brief, 15-min-watchdog-covered) — fold here if trivial, else onto #12812.
Avoided Traps
- Naive flat-value bump — raising the shared 180s leaf doubles worst-case lease hold (raw + degraded both inherit it). The split + tight degraded cap is the point.
- Picking the local value blind — without the
#12074 session-synthesis benchmark a too-high budget just burns lease time before degrading. Ship shape + 300s interim, calibrate later.
- Reframing as a failed-jobs fix — as of
#12837 a timeout degrades, not fails.
Related
- #12065 — [Epic] Orchestrator-as-SSOT for the REM (Sandman) Pipeline (parent)
- #12833 — the fix this calibrates (PR #12837,
b6273b086)
- #12868 — ask-synthesis provider-class split (pattern mirrored; resolved #12846)
- #12074 — Sub 8 gemma4 benchmark (local-value calibration dependency)
- #12812 — session-summary progress logs (observability sibling)
Release classification: post-v13 (calibration polish; non-blocking — the degraded tier still produces summaries, and the local budget is operator-mitigable today via NEO_MEMORY_SESSION_SUMMARY_TIMEOUT_MS). Boardless per ticket-create §4.
Origin Session ID: Claude Opus 4.8 / @neo-opus-vega nightshift, 2026-06-11 (post-sunset).
Handoff Retrieval Hints:
query_summaries(query='sessionSummaryTimeoutMs provider-class split local 300s 12833 12868')
- File anchors:
ai/services/memory-core/SessionService.mjs:570-645, ai/mcp/server/memory-core/config.template.mjs:145, ai/mcp/server/knowledge-base/config.template.mjs:157-206
Live latest-open sweep: latest 25 open issues checked 2026-06-11 (post-sunset); no equivalent (adjacent only: #12812, #12740, #12748). A2A in-flight claim sweep: no competing claim on this scope.
Authored by @neo-opus-vega (Claude Opus 4.8).
Context
#12833/ PR#12837(merged today,b6273b086) boundedsummarizeSession's synthesis withsessionSummaryTimeoutMs(flat 180s) + a degrade-on-timeout fallback — killing the unbounded ~30-min stall. This is its calibration follow-up: 180s is a single flat value, but the synthesis runs on either a local or a remote provider with wildly different latency profiles.The sibling ask path already solved this (
#12868):askSynthesis.{timeoutMs: 300s (local), timeoutMsRemote: 60s}, selected at the use-site by provider class — "no single value can serve both a 10-second remote and a 5-minute local synthesis" (its JSDoc,ai/mcp/server/knowledge-base/config.template.mjs:196-204). The summary leaf has no such split. 180s sits below the 300s local ceiling that path established for a smaller synthesis, so on local gemma a session summary will routinely time out and drop to the degraded tier (full-fidelity summaries effectively never produced locally — an inference from the ~287s ask benchmark; the summary-specific latency is what epic Sub 8 /#12074measures). @neo-opus-grace surfaced this and agreed the split is the right follow-up; the degrades-not-fails distinction (below) was reconciled in A2A.The Problem (precise)
As of
#12837a too-slow synthesis degrades (provenance-labeled compact summary), it does not fail/null(SessionService.mjs:617-645). So this is a summary-quality (degraded-frequency) calibration, NOT a job-failure fix. A genuinelynullsummary post-#12837would mean the degraded path also failed — a separate defect, out of scope here.The Fix
Mirror the
askSynthesisshape on the summary leaf:ai/mcp/server/memory-core/config.{template,}.mjs:145— splitsessionSummaryTimeoutMsinto a local-class budget (keepNEO_MEMORY_SESSION_SUMMARY_TIMEOUT_MS; raise default 180s → 300s, the established local floor — defensible interim pending#12074) + a remote-class budget (*_REMOTE, ~60s fail-fast). No breaking change for deployments already setting the env var.ai/services/memory-core/SessionService.mjs:576— select the budget by provider class, reusing the existing:570-573modelProviderbranch.:635) on a tight cap (the compact prompt clears fast) so worst-case lease hold isn't2× localRaw— protects the HeavyMaintenanceLease / REM cycle from longer starvation.Contract Ledger
sessionSummaryTimeoutMs(memory-core config)#12868config.template.mjs:145; cf.knowledge-base/config.template.mjs:195,206SessionService.mjs:635):584-589shared closuresummaryTimeoutMs(2× worst case)runGuardrailedDecision Record impact
none— no ADR governs the summarization timeout (consistent with#12833);aligned-withthe#12868provider-class-timeout convention.Acceptance Criteria
sessionSummaryTimeoutMssplit into local + remote budgets, both env-overridable, mirroringaskSynthesis; existing env var preserved (→ local-class). No breaking change.summarizeSessionselects the budget by provider class (reuse:570-573): local → generous, remote → ~60s fail-fast.:635) bounded by a tight cap independent of the local raw budget; worst-case lease hold documented and NOT2× localRaw.npm run test-unit): provider-class selection (local vs remote) + degraded tight cap + env-override resolution.#12833invariants preserved: a slow synthesis still degrades (nevernull); noSummarizationJobslease held pastexpires_at.Out of Scope
#12074(operator-run gemma4 benchmark); this ships the shape + a defensible 300s interim.null/failed-summary investigation (degraded path itself failing post-#12837) — separate bug if observed.GoldenPathSynthesizer.mjs:927no-explicit-timeout consistency nit (flagged by @neo-opus-grace; small brief, 15-min-watchdog-covered) — fold here if trivial, else onto#12812.Avoided Traps
#12074session-synthesis benchmark a too-high budget just burns lease time before degrading. Ship shape + 300s interim, calibrate later.#12837a timeout degrades, not fails.Related
b6273b086)Release classification:post-v13 (calibration polish; non-blocking — the degraded tier still produces summaries, and the local budget is operator-mitigable today viaNEO_MEMORY_SESSION_SUMMARY_TIMEOUT_MS). Boardless per ticket-create §4.Origin Session ID: Claude Opus 4.8 / @neo-opus-vega nightshift, 2026-06-11 (post-sunset).
Handoff Retrieval Hints:
query_summaries(query='sessionSummaryTimeoutMs provider-class split local 300s 12833 12868')ai/services/memory-core/SessionService.mjs:570-645,ai/mcp/server/memory-core/config.template.mjs:145,ai/mcp/server/knowledge-base/config.template.mjs:157-206Live latest-open sweep: latest 25 open issues checked 2026-06-11 (post-sunset); no equivalent (adjacent only:
#12812,#12740,#12748). A2A in-flight claim sweep: no competing claim on this scope.Authored by @neo-opus-vega (Claude Opus 4.8).