Context
#10580 landed the always-on file sink pattern for the Knowledge Base MCP server's logger. The same observability gap exists for the Memory Core and Neural Link MCP servers — their logger.mjs files are silent unless aiConfig.debug === true (Memory Core) or partially silent (Neural Link gates debug but not info/warn/error to file). Long-running operations (Memory Core summarization, Neural Link inspection chains) leave no tail-able diagnostic trail today.
The Avoided Traps section of #10576 explicitly deferred this symmetry as a follow-up. This ticket implements it.
Proposed Fix
Apply the same dual-sink pattern from #10580 to both servers:
Memory Core (ai/mcp/server/memory-core/logger.mjs): identical shape to pre-#10580 KB — debug-flag-gated stderr only. Refactor to:
- Always-on file sink at
${aiConfig.logPath}/mc-server-YYYY-MM-DD.log
- Stderr sink stays debug-flag-gated (preserves existing behavior, avoids stdio transport corruption)
- Lazy log-dir resolution per-write (same
${logDir}::${today} cache key as #10580)
- Error/circular-ref safe stringification (same
stringifyArg helper)
Neural Link (ai/mcp/server/neural-link/logger.mjs): already has ISO-timestamp prefix + always-on stderr for non-debug levels. Add:
- Always-on file sink at
${aiConfig.logPath}/nl-server-YYYY-MM-DD.log
- Preserve existing stderr semantics (
debug || level !== 'debug')
- Replace naked
JSON.stringify(args) with the Error/circular-safe stringifyArg (same defect as KB pre-#10580 — Error.stack/message lost)
- Lazy log-dir resolution
Both servers' config.template.mjs: add logPath default mirroring KB's:
logPath: path.resolve(neoRootDir, '.neo-ai-data/logs')
Acceptance Criteria
- Memory Core's
logger.log/info/warn/error/debug writes to mc-server-YYYY-MM-DD.log regardless of aiConfig.debug
- Neural Link's
logger.log/info/warn/error/debug writes to nl-server-YYYY-MM-DD.log regardless of aiConfig.debug; existing stderr-on-non-debug semantics preserved
- Both
config.template.mjs files have logPath defaulting to ${neoRootDir}/.neo-ai-data/logs/
- Both loggers'
stringifyArg helper preserves Error name/message/stack and falls back gracefully on circular references (no {} for Error, no throw for circular)
- Per-server unit tests covering AC1, AC2, AC4 (Error preservation + circular-ref guard)
- Both daily files share the same
aiConfig.logPath directory by default — single tail -f .neo-ai-data/logs/*-server-$(date +%Y-%m-%d).log works across all 3 MCP servers
Avoided Traps
- NOT a logger framework refactor. Each MCP server keeps its own
logger.mjs per the existing architectural pattern. No shared base class, no DI container. Symmetric pattern, distinct files.
- NOT structured-log format. Current human-readable timestamp + level + message stays. No JSON-Lines, no log levels routing. Out of scope.
- NOT a log-rotation library. Daily rotation via filename is the entire rotation strategy. Disk-bloat protection is per-deployment retention policy, out of scope here.
- NOT touching
aiConfig.logPath resolution semantics. Same default + same fallback pattern as #10580. Single source of truth across all 3 MCP server log files.
Origin
- Empirical surface: 2026-05-01 KB resync incident → #10576/#10580 closed Shape A for KB only
- Ticket: follow-up explicitly committed to in #10576 Avoided Traps + PR #10580 Test Plan
- Origin Session ID: 7a2c3c2a-d0f1-462a-8489-69b031221040
Context
#10580 landed the always-on file sink pattern for the Knowledge Base MCP server's logger. The same observability gap exists for the Memory Core and Neural Link MCP servers — their
logger.mjsfiles are silent unlessaiConfig.debug === true(Memory Core) or partially silent (Neural Link gatesdebugbut not info/warn/error to file). Long-running operations (Memory Core summarization, Neural Link inspection chains) leave no tail-able diagnostic trail today.The Avoided Traps section of #10576 explicitly deferred this symmetry as a follow-up. This ticket implements it.
Proposed Fix
Apply the same dual-sink pattern from #10580 to both servers:
Memory Core (
ai/mcp/server/memory-core/logger.mjs): identical shape to pre-#10580 KB — debug-flag-gated stderr only. Refactor to:${aiConfig.logPath}/mc-server-YYYY-MM-DD.log${logDir}::${today}cache key as #10580)stringifyArghelper)Neural Link (
ai/mcp/server/neural-link/logger.mjs): already has ISO-timestamp prefix + always-on stderr for non-debug levels. Add:${aiConfig.logPath}/nl-server-YYYY-MM-DD.logdebug || level !== 'debug')JSON.stringify(args)with the Error/circular-safestringifyArg(same defect as KB pre-#10580 — Error.stack/message lost)Both servers' config.template.mjs: add
logPathdefault mirroring KB's:logPath: path.resolve(neoRootDir, '.neo-ai-data/logs')Acceptance Criteria
logger.log/info/warn/error/debugwrites tomc-server-YYYY-MM-DD.logregardless ofaiConfig.debuglogger.log/info/warn/error/debugwrites tonl-server-YYYY-MM-DD.logregardless ofaiConfig.debug; existing stderr-on-non-debug semantics preservedconfig.template.mjsfiles havelogPathdefaulting to${neoRootDir}/.neo-ai-data/logs/stringifyArghelper preserves Error name/message/stack and falls back gracefully on circular references (no{}for Error, no throw for circular)aiConfig.logPathdirectory by default — singletail -f .neo-ai-data/logs/*-server-$(date +%Y-%m-%d).logworks across all 3 MCP serversAvoided Traps
logger.mjsper the existing architectural pattern. No shared base class, no DI container. Symmetric pattern, distinct files.aiConfig.logPathresolution semantics. Same default + same fallback pattern as #10580. Single source of truth across all 3 MCP server log files.Origin