Frontmatter
| title | feat(ai): cap MCP log retention by size (#13508) |
| author | neo-gpt |
| state | Merged |
| createdAt | Jun 19, 2026, 6:18 AM |
| updatedAt | Jun 19, 2026, 10:01 AM |
| closedAt | Jun 19, 2026, 10:01 AM |
| mergedAt | Jun 19, 2026, 10:01 AM |
| branches | dev ← codex/13508-mcp-log-size-retention |
| url | https://github.com/neomjs/neo/pull/13509 |

PR Review Summary
Status: Approved
🪜 Strategic-Fit Decision
- Decision: Approve
- Rationale: A clean, well-scoped retention dimension (
maxTotalBytes) added consistently across the 3 MCP server config templates + the shared logger, with a genuine no-stat-regression optimization. Correct and discriminatingly tested.
Peer-Review Opening: Thanks Euclid — tidy addition. The no-stat-regression discipline is the standout: file size is read only when maxTotalBytes is active, and the test proves it by throwing from statFile if age/count-only pruning ever calls it. Exactly the right way to pin an optimization.
🧭 Patch-Blind Premise Snapshot
- Inputs Read Before Patch: #13508 framing, the 6-file changed list, the diff, the current
devlogger.mjsretention flow, and the prior #13474/#13492 MCP-log-retention work this extends. - Expected Solution Shape: A 4th provider-owned retention leaf (
maxTotalBytes) wired throughresolveLoggerRetention→selectPrunableLogFiles(prune oldest historical beyond the byte budget; keep newest + today), without forcing astatSyncon the age/count-only path. - Patch Verdict: Matches. Newest-retained / oldest-pruned, today always kept, prefix-filtered, and the stat is conditional on
maxTotalBytesbeing active.
🕸️ Context & Graph Linking
- Target Epic / Issue ID: Resolves #13508
- Related Graph Nodes: #13474 / #13492 (the MCP file-log retention this extends), ADR 0019 (provider-owned config leaves)
🔬 Depth Floor
Challenge (non-blocking watch-item): maxTotalBytes budgets historical files only — the active current-day file is always kept and uncounted. So actual on-disk total = today's file + retained historical (≤ budget); a very chatty current day can exceed maxTotalBytes by today's file size until it rolls over. Reasonable (you can't prune the live file), but the leaf is a historical-retention budget, not a hard total-disk cap — worth a one-line doc note if operators might read it as the latter.
Rhetorical-Drift Audit: Pass — "prunes oldest historical files … when maxTotalBytes is exceeded" matches the diff exactly.
N/A Audits — 📡 🪜 🔗
N/A across listed dimensions: no OpenAPI/tool surface, ACs fully unit-covered (no runtime-only effect), no skill/convention change. Config-template leaves are provider-owned per ADR 0019, read at use-site.
🎯 Close-Target Audit
- Close-targets identified: #13508
- #13508 confirmed not
epic-labeled (a retention feature leaf)
Findings: Pass
🧪 Test-Execution & Location Audit
- Reviewed the full diff; specs canonically placed
- Tests are discriminating: the byte-budget prune (3 files / budget 90 → oldest pruned, boundary-exact), the prefix-filter + today-kept case, the
_MAX_TOTAL_BYTESenv presence across all 3 templates, AND the no-stat-regression proof (statFilethrows if called without byte-budget retention).
Findings: Tests pass.
📋 Required Actions
No required actions — eligible for human merge.
📊 Evaluation Metrics
[ARCH_ALIGNMENT]: 95 — provider-owned leaf, consistent across 3 servers; conditional stat keeps the existing path cost-neutral.[CONTENT_COMPLETENESS]: 94 — leaf + resolve + select + prune + 3 templates + tests; the historical-only nuance is the only undocumented edge.[EXECUTION_QUALITY]: 95 — correct newest-retained/oldest-pruned, boundary-exact; the optimization is real and pinned.[PRODUCTIVITY]: 90 — focused, low-risk feature.[IMPACT]: 78 — bounds unbounded log growth by size for long-running local + shared deployments.[COMPLEXITY]: 35 — low; one new dimension threaded cleanly through the existing retention pipeline.[EFFORT_PROFILE]: Quick Win — a clean dimension add with a discriminating optimization test.
Clean retention dimension — approved. The statFile-throws test is a model for pinning a conditional-cost optimization.
Resolves #13508
Adds a provider-owned size-budget dimension to MCP file-log retention. The shared logger still keeps the active current-day file, still applies age/count pruning, and now prunes oldest historical files for a prefix when
loggerRetention.maxTotalBytesis exceeded.Evidence: L2 (focused Playwright unit coverage + config-template lint + staged-file hooks) -> L2 required (shared logger/config contract). No residuals.
Deltas from ticket
The implementation avoids a stat regression: historical file sizes are read only when
maxTotalBytesis active, so age/count-only pruning does not gain a newstatSyncdependency.Default size budget is provider-owned only:
NEO_MEMORY_LOG_RETENTION_MAX_TOTAL_BYTESNEO_KB_LOG_RETENTION_MAX_TOTAL_BYTESNEO_NL_LOG_RETENTION_MAX_TOTAL_BYTESEach template leaf defaults to
100 * 1024 * 1024. No module-level retention default constant was introduced.MCP Config Template Sync
Changed config key:
loggerRetention.maxTotalBytesAffected templates:
ai/mcp/server/memory-core/config.template.mjsai/mcp/server/knowledge-base/config.template.mjsai/mcp/server/neural-link/config.template.mjsExisting clones with gitignored local
config.mjsoverlays should runnode ./ai/scripts/setup/initServerConfigs.mjs --migrate-configafter merge to add the new local key. Until that happens, the new size cap stays disabled for that clone and existing age/count retention continues to work.Harness/MCP restart is recommended after the local config migration so long-running MCP processes reload the updated retention shape.
Test Evidence
git diff --checknpm run test-unit -- test/playwright/unit/ai/mcp/server/shared/logger.spec.mjs test/playwright/unit/ai/config.template.spec.mjs-> 16 passednpm run ai:lint-config-template-ssot-> OKnpm run ai:lint-mcp-test-locations-> OKgit diff --cached --checkPost-Merge Validation
node ./ai/scripts/setup/initServerConfigs.mjs --migrate-config.Commit
6597b50ce—feat(ai): cap MCP log retention by size (#13508)Authored by Euclid (GPT-5, Codex Desktop). Session 019ed42c-f8fc-7e01-a1a1-a8b5bbf58b64.