LearnNewsExamplesServices
Frontmatter
id16723
titleMemory Core's longest completed calls disappear into aggregate telemetry
stateClosed
labels
enhancementaiperformanceagent-os
assigneesneo-gpt-emmy
createdAtAug 8, 2026, 7:12 PM
updatedAtAug 8, 2026, 9:09 PM
githubUrlhttps://github.com/neomjs/neo/issues/16723
authorneo-gpt-emmy
commentsCount0
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtAug 8, 2026, 9:09 PM

Memory Core's longest completed calls disappear into aggregate telemetry

Closed Backlog/active-chunk-14 enhancementaiperformanceagent-os
neo-gpt-emmy
neo-gpt-emmy commented on Aug 8, 2026, 7:12 PM

Context

#16677 records a live Memory Core failure family where the process remains alive while MCP calls time out. #16685 / PR #16686 added the first required discriminator: a redacted telemetry row is persisted before tool dispatch and remains visible as unfinishedCalls until the call completes.

The deployed recorder exposed a second, measured gap. At 2026-08-08T17:03Z, get_memory_core_tool_metrics({sinceMs: 21600000, limit: 30}) reported zero unfinished calls, while its completed aggregates contained:

  • add_message max duration: 474,347 ms;
  • healthcheck max duration: 283,564 ms;
  • list_messages max duration: 283,526 ms;
  • query_raw_memories max duration: 71,886 ms.

The SDK caller deadline is 60 seconds. These rows prove that some operations crossed the server dispatch boundary and returned from server code minutes after the client had timed out. A same-day peer receipt also captured an add_message timeout that did not land at all. The two signatures cannot be correlated after recovery because completed rows collapse into per-tool aggregates.

Problem

The unfinished-call observer is useful only while a row remains unfinished. When a multi-minute call eventually completes, its exact start, completion, outcome, and opaque call ID disappear from the public diagnostic projection. An operator arriving after self-recovery sees only a maximum duration mixed into an aggregate and cannot reconstruct which call occupied the incident window.

The underlying SQLite row already carries the required redacted facts. The gap is projection, not storage.

Architectural Reality

  • ai/services/memory-core/MemoryCoreRecorderService.mjs persists one row per call with opaque ID, tool, start timestamp, completion timestamp, duration, success, and failure stage.
  • get_memory_core_tool_metrics currently returns completed per-tool aggregates plus bounded oldest-first unfinished rows.
  • ai/mcp/server/memory-core/openapi.yaml owns the public response and input contract.
  • Raw arguments, results, agent IDs, user IDs, and session IDs are intentionally excluded from public unfinished telemetry and remain excluded here.
  • Recorder writes are best-effort with a 50 ms SQLite busy timeout, so absence of a row is not proof that a request never reached the server.

Intended Solution

Extend the existing metrics operation; do not add another MCP tool.

MemoryCoreRecorderService.getMemoryCoreToolMetrics({sinceMs, limit, slowAfterMs = 60000}) will return:

  • the existing completed aggregates;
  • the existing unfinished rows;
  • slowAfterMs, the effective completed-call threshold; and
  • recentSlowCalls, newest-completed first and bounded by the existing effective limit.

Each slow row is a redacted projection:

{callId, tool, startedAt, completedAt, durationMs, success, failureStage}

The default 60,000 ms threshold matches the ordinary MCP SDK request deadline documented by the canonical plane client. Callers may lower or raise it for a bounded incident window; invalid/non-positive values fall back to the default. Disabled or unavailable telemetry returns an empty slow-call list.

Contract Ledger

Target Surface Source of Authority Proposed Behavior Fallback / Edge Case Docs Evidence
MemoryCoreRecorderService.getMemoryCoreToolMetrics({sinceMs, limit, slowAfterMs = 60000}) Existing #16685 recorder API Add effective slowAfterMs and bounded recentSlowCalls without changing existing fields Invalid/non-positive threshold falls back to 60,000 ms; unavailable/disabled returns an empty list Method JSDoc + OpenAPI operation text Focused SQLite unit tests
recentSlowCalls[] Existing mc_tool_call_log row lifecycle Newest-completed rows at/above threshold: opaque ID, tool, start/completion ISO times, duration, success, failure stage Equal completion times order deterministically by opaque ID; rows outside sinceMs are excluded OpenAPI response schema Threshold, ordering, window, and limit tests
Sensitive telemetry boundary #16685 redaction contract Expose no args/results or agent/user/session identity Recorder absence remains unknown, never “request did not dispatch” JSDoc + schema descriptions Secret and identity negative assertions
get_memory_core_tool_metrics observer Existing completion-only observer contract Current observer remains absent from its own in-progress snapshot; prior observer calls may appear only if they exceed the slow threshold No recursive or second observer path Inline comment Facade regression test
Incident consumer #16677 Parent liveness ticket Correlate a prior unfinished call with its eventual slow completion after self-recovery A slow server completion does not prove the client received the response Parent comment + post-deploy receipt Live exact-row receipt

Acceptance Criteria

  • Completed calls at or above slowAfterMs appear in recentSlowCalls; faster and out-of-window rows do not.
  • Rows are newest-completed first with deterministic tie ordering and are bounded by the effective existing limit.
  • Each row exposes exactly callId, tool, startedAt, completedAt, durationMs, success, and failureStage.
  • Raw arguments/results and agent, user, and session identifiers cannot appear in the projection.
  • Invalid/non-positive slowAfterMs falls back to 60,000 ms; disabled/unavailable states disclose the effective threshold and return recentSlowCalls: [].
  • Existing aggregate and unfinished-call contracts remain unchanged.
  • OpenAPI input/output schemas and service parity stay green.
  • Focused Neo unit coverage fails against the pre-change projection and passes after the change.
  • A post-deploy read captures the exact redacted rows behind at least one previously aggregate-only multi-minute maximum, or records NOT_YET_MEASURED if the deployment starts a fresh telemetry epoch.

Out of Scope

  • Claiming the operation behind #16677 is the root cause merely because it was slow.
  • Event-loop, heap/GC, pulse, or container-health instrumentation.
  • Automatic restart, restore, replay, retry, or client deduplication.
  • Persisting raw request/response payloads or caller identity in the public projection.
  • Changing the MCP SDK deadline.
  • Closing #16677.

Avoided Traps

  • Server completion equals response delivery. It proves server code returned, not that the disconnected/timed-out client received it.
  • No telemetry row equals no dispatch. The recorder is intentionally best-effort; absence remains unknown.
  • Longest aggregate equals incident owner. Exact timestamps must overlap the measured incident window before correlation.
  • A new diagnostic deserves a new tool. The existing metrics observer already owns this data family.
  • Unbounded “recent” history. The lookback, threshold, and existing limit all bind the query.

Decision Record Impact

None. This extends the existing redacted observer without adding a diagnosis/action class or changing ADR 0025/0026 recovery authority.

Related

  • Parent incident: #16677
  • Start-boundary leaf: #16685
  • Delivered recorder PR: #16686
  • Canonical client deadline context: #16167

Creation-Gate Record

  • Live premise check: deployed get_memory_core_tool_metrics returned 2,672 completed calls, zero unfinished calls, and 283–474 second per-tool maxima on the current process.
  • Source census: the required per-call fields already exist in mc_tool_call_log; only the public projection is missing.
  • Duplicate sweep: latest open queue, all-state GitHub title/body search, Knowledge Base ticket search, and current A2A claims checked; no equivalent ticket or competing claim found. The closest KB hit (#12743) is an unrelated historical cost ledger.
  • Memory Core archaeology: two targeted searches returned explicit Chroma/embedding degradation, not genuine no-match; this gap is disclosed rather than converted into a clean precedent claim.

Origin Session ID

019fe0b3-53bc-7ef2-8665-41a0ef3f7b62

Retrieval Hint

Memory Core recent slow completed calls exact timestamps mc_tool_call_log #16677 #16685

tobiu referenced in commit 14c8f7d - "feat(memory-core): retain recent slow call timelines (#16723) (#16724) on Aug 8, 2026, 9:09 PM
tobiu closed this issue on Aug 8, 2026, 9:09 PM