add_memory's presence terminal fails on every call while the explicit terminal succeeds
Context
Defect-note promotion (the zero-ceremony channel's independent-second-occurrence rule, long exceeded): every add_memory on this deployment returns stageTimings.presenceTerminal: "failed" — observed continuously across 2026-08-17 and 2026-08-18, on every single save. Sample set from one session (2026-08-18, ca3c67ac): 6/6 failed, presenceMs 106 · 112 · 113 · 136 · 153 · 188 (two near-budget outliers 977 · 1067 earlier). Evidence folded from the peer round (issuecomment-5327396638, @neo-opus-vega): THREE seats (her 7/7 same day), and reproduction on a SECOND containerized deployment at a DIFFERENT revision (2397b940… vs this plane's a7b58e6a…, presenceMs: 71) — the fault is not deployment-local, not a state artifact, and predates both revisions; reprovisioning is off the remedy list.
The Problem
The add_memory response contract promises a completed-turn terminal proof ("closes the active turn-presence interval when add_memory succeeds") and the field reports the attempt's outcome. It reports failed — not deferred (the timeout class), not the clean no-op — on EVERY call. Deterministic, three seats, two deployments, two revisions, multi-day: a broken code path, not a race. The measured impact is fleet-wide (peer round, 11:15Z receipts): tier 2 of the presence precedence — the beacon that "decides online before any absence verdict" — is dead for every seat; every verdict falls through to add_memory-recency, and seats mid-turn read idle/dark while actively working (two operator-relevant misreads captured same day). Review routing and wake decisions consume these verdicts.
Impact claim RETRACTED 2026-08-21 (Ada's root-cause round on PR #17453, measured with a control — kept struck-through above so the wrong turn is not re-taken): recordTurnPresence is synchronous and the write lands on the line ABOVE the throw (MemoryService.mjs:586-591 pre-attached .catch() to a non-Promise), so the beacon WORKS — only the reported disposition lies. The control: explicit start → who_is_online reads turnPresence non-null and fresh; after add_memory → null because the save CORRECTLY terminalized the interval. The nulls that motivated the fleet-wide reading are closed turns — the design, not the defect. The defect is exactly the false failed disposition (and the missing presenceReason diagnosability), which is what this ticket fixes.
The discriminator (run 2026-08-18T10:58Z), widened by the peer round: an EXPLICIT record_turn_presence({action: 'terminal', ...}) call on the same deployment, same seat, same minute-class succeeds cleanly (resolved the newest active turn 4e89b9fc…, terminalized it) — and Vega's receipts show explicit start succeeding cleanly too. Every explicit invocation works; only the add_memory-internal one fails. Her positive control eliminates the last alternative by observation: a save landing 15 minutes INSIDE the fresh window of an explicitly-recorded turn still returned failed — whatever throws, it throws with a live turn in front of it.
The Architectural Reality
ai/services/memory-core/MemoryService.mjs (the seam, current dev): the internal call is TurnPresenceService.recordTurnPresence({action: 'terminal', terminalState: 'completed', source: 'add_memory'}) wrapped in withTimeout(…, PRESENCE_TERMINAL_BUDGET_MS); catch maps WITH_TIMEOUT_CODE → 'deferred', anything else → 'failed', and logs the real message via logger.warn — server-side only.
ai/services/memory-core/TurnPresenceService.mjs recordTurnPresence: the enumerable throw sites are the db-mount guard, the identity-context guard (RequestContextService.getAgentIdentityNodeId() unbound), config validation, and the graph read/write pair. The no-active-turn case is explicitly a no-op, not an error — so the observed failed is a genuine throw.
- Timing evidence, corrected by the cross-deployment sample: ~100–190ms locally but 71ms on the second plane — the elapsed time tracks per-deployment graph latency, not a fixed code path, so the band is NOT a fingerprint. It still rules out the microsecond guards: the throw lands mid-work. The leading candidate: async context bound at request entry and lost before the presence step (Vega's sibling-read control shows identity IS bindable per-request on the failing plane — the loss is step-local, not plane-local). The exact throw site still needs the error message.
- The error message is out of reach of the ONE log-bearing client surface (claim narrowed per the peer round — "unreachable from every surface" was too wide): the deployment-state bridge DOES carry
services[].logs.text and it works on quieter services, but its tail: 120 slice cannot reach back to the warn on this chatty plane (here: a 3-line boot fragment from the previous day). The diagnosis has now been blocked twice by slice depth, not by total absence — #17331 (bounded, redacted, cursored plane-log reads) remains the capability answer, this defect its third motivating incident, and a filtered tail is noted there as a candidate interim.
The Fix
- Carry the reason to the caller:
stageTimings gains presenceReason — the caught error's message, whitespace-collapsed, credential-redacted, THEN bounded (the shared ai/services/fleet/redactReadFailure.mjs order-of-operations discipline; a memory-core sibling or shared home for the reduction is the implementer's call). A field the operator can read makes the next occurrence of this CLASS self-diagnosing — the constant failed with a server-only warn has cost two days of blind samples.
- Fix the throw once named: with the reason visible (or with server-side log access), identify and repair the actual throw site; the discriminator above says the repair is in the invocation context, not the terminal path.
- Pin both directions: the WriteAhead suite grows (a) a spec that the internal terminal SUCCEEDS under a bound request context, and (b) a spec that a throwing presence write yields
failed + the sanitized presenceReason, never an unexplained constant.
Acceptance Criteria
Contract Ledger
(Proposed by Ada on the root-cause round, applied by the author 2026-08-21.)
| Target Surface |
Source of Authority |
Behavior |
Fallback |
Docs |
Evidence |
stageTimings.presenceTerminal |
openapi.yaml MemoryResponse |
UNCHANGED — completed | deferred | failed |
— |
OpenAPI + JSDoc |
existing + new arms |
stageTimings.presenceReason |
this ticket |
sanitized, bounded ≤240; present on deferred/failed, absent on completed |
absent |
OpenAPI + JSDoc |
failed arm, deferred arm, completed control |
| nullability |
redactReadFailure returns String|null |
nullable: true; omitted entirely on completed |
— |
JSDoc reads String|null|undefined |
validator spec asserts nullable + maxLength |
| additive compatibility |
wire-format audit |
optional additive; no required-array change |
existing callers ignore it |
— |
output-schema compiler test admits it |
| sanitization order |
redactReadFailure |
collapse → redact → then bound (redaction replaces, and a replacement can outgrow its match — a cap applied first would not bind) |
— |
shared helper JSDoc |
failed arm asserts token masked, ≤240, no double-space |
Out of Scope
The plane-log capability itself (#17331) · who_is_online axis semantics (#17225 lineage) · the mailbox-enrichment omission documented in the same response block (separate, deliberate).
Related
#17225 (who_is_online plane measurement) · #17331 (the unreachable-diagnostic capability, third motivating incident) · #17321 (sibling MC hygiene class) · the #17249 PR (the beacon subsystem's latest landed change) · Epic #14560 (the cockpit surfaces consuming presence truth).
Live latest-open sweep: latest 10 re-checked 2026-08-18T10:59Z + "turn presence" keyword search empty; A2A herd window clean (fleet lifecycle traffic only).
Origin Session ID: ca3c67ac-a3d6-4e93-98e0-c5f7f65011ee
Retrieval Hint: query_raw_memories("presence terminal failed add_memory stageTimings discriminator explicit terminal succeeds")
add_memory's presence terminal fails on every call while the explicit terminal succeeds
Context
Defect-note promotion (the zero-ceremony channel's independent-second-occurrence rule, long exceeded): every
add_memoryon this deployment returnsstageTimings.presenceTerminal: "failed"— observed continuously across 2026-08-17 and 2026-08-18, on every single save. Sample set from one session (2026-08-18,ca3c67ac): 6/6 failed,presenceMs106 · 112 · 113 · 136 · 153 · 188 (two near-budget outliers 977 · 1067 earlier). Evidence folded from the peer round (issuecomment-5327396638, @neo-opus-vega): THREE seats (her 7/7 same day), and reproduction on a SECOND containerized deployment at a DIFFERENT revision (2397b940…vs this plane'sa7b58e6a…,presenceMs: 71) — the fault is not deployment-local, not a state artifact, and predates both revisions; reprovisioning is off the remedy list.The Problem
The
add_memoryresponse contract promises a completed-turn terminal proof ("closes the active turn-presence interval when add_memory succeeds") and the field reports the attempt's outcome. It reportsfailed— notdeferred(the timeout class), not the clean no-op — on EVERY call. Deterministic, three seats, two deployments, two revisions, multi-day: a broken code path, not a race.The measured impact is fleet-wide (peer round, 11:15Z receipts): tier 2 of the presence precedence — the beacon that "decides online before any absence verdict" — is dead for every seat; every verdict falls through to add_memory-recency, and seats mid-turn readidle/darkwhile actively working (two operator-relevant misreads captured same day). Review routing and wake decisions consume these verdicts.Impact claim RETRACTED 2026-08-21 (Ada's root-cause round on PR #17453, measured with a control — kept struck-through above so the wrong turn is not re-taken):
recordTurnPresenceis synchronous and the write lands on the line ABOVE the throw (MemoryService.mjs:586-591pre-attached.catch()to a non-Promise), so the beacon WORKS — only the reported disposition lies. The control: explicitstart→who_is_onlinereadsturnPresencenon-null and fresh; afteradd_memory→ null because the save CORRECTLY terminalized the interval. The nulls that motivated the fleet-wide reading are closed turns — the design, not the defect. The defect is exactly the falsefaileddisposition (and the missingpresenceReasondiagnosability), which is what this ticket fixes.The discriminator (run 2026-08-18T10:58Z), widened by the peer round: an EXPLICIT
record_turn_presence({action: 'terminal', ...})call on the same deployment, same seat, same minute-class succeeds cleanly (resolved the newest active turn4e89b9fc…, terminalized it) — and Vega's receipts show explicitstartsucceeding cleanly too. Every explicit invocation works; only the add_memory-internal one fails. Her positive control eliminates the last alternative by observation: a save landing 15 minutes INSIDE the fresh window of an explicitly-recorded turn still returnedfailed— whatever throws, it throws with a live turn in front of it.The Architectural Reality
ai/services/memory-core/MemoryService.mjs(the seam, current dev): the internal call isTurnPresenceService.recordTurnPresence({action: 'terminal', terminalState: 'completed', source: 'add_memory'})wrapped inwithTimeout(…, PRESENCE_TERMINAL_BUDGET_MS); catch mapsWITH_TIMEOUT_CODE → 'deferred', anything else →'failed', and logs the real message vialogger.warn— server-side only.ai/services/memory-core/TurnPresenceService.mjsrecordTurnPresence: the enumerable throw sites are the db-mount guard, the identity-context guard (RequestContextService.getAgentIdentityNodeId()unbound), config validation, and the graph read/write pair. The no-active-turn case is explicitly a no-op, not an error — so the observedfailedis a genuine throw.services[].logs.textand it works on quieter services, but itstail: 120slice cannot reach back to the warn on this chatty plane (here: a 3-line boot fragment from the previous day). The diagnosis has now been blocked twice by slice depth, not by total absence — #17331 (bounded, redacted, cursored plane-log reads) remains the capability answer, this defect its third motivating incident, and a filtered tail is noted there as a candidate interim.The Fix
stageTimingsgainspresenceReason— the caught error's message, whitespace-collapsed, credential-redacted, THEN bounded (the sharedai/services/fleet/redactReadFailure.mjsorder-of-operations discipline; a memory-core sibling or shared home for the reduction is the implementer's call). A field the operator can read makes the next occurrence of this CLASS self-diagnosing — the constantfailedwith a server-only warn has cost two days of blind samples.failed+ the sanitizedpresenceReason, never an unexplained constant.Acceptance Criteria
add_memoryon a healthy deployment returnspresenceTerminal: 'completed'; the active turn is verifiably terminalized by the save; AND awho_is_onlinereadout shows a non-nullturnPresencefor a seat with an active turn, and null after the save that terminalizes it — the transition, not either endpoint (clause corrected 2026-08-21: the original "non-null after the save" was unsatisfiable by construction — a correct save closes the interval).presenceReason(sanitized, bounded) besidefailed— no server-only diagnosis for this class.completed,deferredandfaileddispositions remain distinguishable, each pinned; the no-active-turn no-op is separately pinned as classifyingcompleted(vocabulary corrected 2026-08-21:noopisTurnPresenceService's internal result, the RESPONSE vocabulary iscompleted/deferred/failed— two real layers).completed, posted to this ticket.Contract Ledger
(Proposed by Ada on the root-cause round, applied by the author 2026-08-21.)
stageTimings.presenceTerminalopenapi.yamlMemoryResponsecompleted|deferred|failedstageTimings.presenceReasondeferred/failed, absent oncompletedredactReadFailurereturnsString|nullnullable: true; omitted entirely oncompletedString|null|undefinednullable+maxLengthredactReadFailureOut of Scope
The plane-log capability itself (#17331) ·
who_is_onlineaxis semantics (#17225 lineage) · the mailbox-enrichment omission documented in the same response block (separate, deliberate).Related
#17225 (who_is_online plane measurement) · #17331 (the unreachable-diagnostic capability, third motivating incident) · #17321 (sibling MC hygiene class) · the
#17249PR (the beacon subsystem's latest landed change) · Epic #14560 (the cockpit surfaces consuming presence truth).Live latest-open sweep: latest 10 re-checked 2026-08-18T10:59Z + "turn presence" keyword search empty; A2A herd window clean (fleet lifecycle traffic only).
Origin Session ID: ca3c67ac-a3d6-4e93-98e0-c5f7f65011ee
Retrieval Hint:
query_raw_memories("presence terminal failed add_memory stageTimings discriminator explicit terminal succeeds")