LearnNewsExamplesServices
Frontmatter
id10717
titleWake subscription API hides stale active rows that bridge still dispatches
stateClosed
labels
bugairegressionarchitecture
assigneesneo-gpt
createdAtMay 4, 2026, 10:48 PM
updatedAtMay 4, 2026, 11:25 PM
githubUrlhttps://github.com/neomjs/neo/issues/10717
authorneo-gpt
commentsCount0
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
blockedBy[]
blocking[]
closedAtMay 4, 2026, 11:25 PM

Wake subscription API hides stale active rows that bridge still dispatches

Closedbugairegressionarchitecture
neo-gpt
neo-gpt commented on May 4, 2026, 10:48 PM

Context

After all #10704 PRs merged on 2026-05-04, @tobiu restarted the bridge daemon from fresh dev and asked @neo-gpt to re-subscribe for A2A wake delivery. manage_wake_subscription({action: "list"}) returned no active subscriptions for @neo-gpt, so @neo-gpt created a new bridge subscription.

The next wake events exposed a deeper inconsistency:

  • Gemini received the same broadcast wake three times through three different active WAKE_SUB ids.
  • @neo-gpt received Claude's PR #10716 review request twice: once through the old WAKE_SUB:f9a09dfd-37de-40e5-8857-6cd2c7373232 and once through the new WAKE_SUB:0c3c10df-3de2-4622-9fd5-5365445ffe61.
  • manage_wake_subscription({action: "list"}) only showed the new @neo-gpt subscription, while direct SQLite inspection showed both old and new rows as active.
  • manage_wake_subscription({action: "unsubscribe", subscriptionId: "WAKE_SUB:f9a09dfd-37de-40e5-8857-6cd2c7373232"}) returned Subscription not found, even though the bridge daemon had just dispatched a wake through that id.

This is distinct from sender-loop suppression: broadcasts correctly ignore the sender, but duplicate active subscription rows still fan the same message out multiple times to the same recipient.

The Problem

The wake substrate has split-brain subscription visibility:

  • The bridge daemon reads active WAKE_SUBSCRIPTION rows from SQLite and dispatches all matching active rows.
  • The manage_wake_subscription API can fail to list or unsubscribe stale active rows that still exist in SQLite and still drive bridge delivery.
  • Direct subscribe creates a fresh WAKE_SUB:<uuid> unconditionally, so manual re-subscribe after a bridge/MCP restart can add another active row instead of recovering the existing route.

This produces duplicate wake injection and makes normal operator cleanup impossible through the public MCP tool surface.

Live Evidence

Direct SQLite query against .neo-ai-data/sqlite/memory-core-graph.sqlite showed these active rows immediately after the duplicate wakes:

WAKE_SUB:0da5679d-bcd4-4521-a451-80c4c83ffc13 | @neo-gemini-3-1-pro | SENT_TO_ME | bridge-daemon | Antigravity | active | 2026-05-04T20:37:22.983Z
WAKE_SUB:2ac01429-f3e7-4396-9198-780e3b7db1c1 | @neo-gemini-3-1-pro | SENT_TO_ME | bridge-daemon | Antigravity | active | 2026-04-29T21:37:27.576Z
WAKE_SUB:61202040-8d1b-4501-bfcd-b0ae12f779c3 | @neo-gemini-3-1-pro | SENT_TO_ME | bridge-daemon | Antigravity | active | 2026-05-04T20:16:10.969Z
WAKE_SUB:8078fccb-f8dc-4450-8819-397d6bc6f26c | @neo-gemini-3-1-pro | SENT_TO_ME | bridge-daemon | Antigravity | active | 2026-05-04T20:46:07.162Z
WAKE_SUB:0c3c10df-3de2-4622-9fd5-5365445ffe61 | @neo-gpt | SENT_TO_ME | bridge-daemon | Codex | active | 2026-05-04T20:37:57.084Z
WAKE_SUB:f9a09dfd-37de-40e5-8857-6cd2c7373232 | @neo-gpt | SENT_TO_ME | bridge-daemon | Codex | active | 2026-05-03T16:02:22.153Z

The first Gemini id (WAKE_SUB:2ac01429-f3e7-4396-9198-780e3b7db1c1) is the same stale subscription identified in closed #10624, so the prior cleanup/canonicalization fix did not eliminate the broader duplicate-active-row failure mode.

The Architectural Reality

Relevant code and prior tickets:

  • ai/mcp/server/memory-core/services/WakeSubscriptionService.mjs#subscribe generates a fresh subscription id every time and does not perform a uniqueness check for (agentIdentity, trigger, harnessTarget, harnessTargetMetadata.appName).
  • WakeSubscriptionService.mjs#bootstrap has a raw-SQL idempotency path added for #10410/#10412, but the public subscribe path still permits duplicates.
  • manage_wake_subscription({action: "list"}) and unsubscribe can disagree with the bridge daemon because the API path does not surface every active SQLite row that the bridge dispatch path sees.
  • #10430 handled duplicate delivery for the same (messageId, subscriptionId) tuple. This ticket is different: there are multiple subscription ids for the same recipient/route tuple.
  • #10624 handled appName canonicalization for one duplicate-row case. This ticket is different: all observed Gemini duplicate rows now use canonical Antigravity, and @neo-gpt duplicates use canonical Codex.

The Fix

Unify active-subscription semantics across API and bridge paths:

  1. Make subscribe idempotent for the canonical active route tuple (agentIdentity, trigger, harnessTarget, normalized filters, canonical harnessTargetMetadata route key such as appName/url).
  2. Make list and unsubscribe read from the same durable SQLite source of truth that the bridge daemon uses, or otherwise hydrate stale rows before returning.
  3. Add an operator-safe cleanup path for stale duplicate rows owned by the caller, without raw SQL surgery.
  4. Ensure the bridge daemon either sees only one active row per route tuple or collapses duplicates by recipient/route/message before delivery as a second-line guard.

Acceptance Criteria

  • Re-running manage_wake_subscription({action: "subscribe", trigger: "SENT_TO_ME", harnessTarget: "bridge-daemon", harnessTargetMetadata: {appName: "Codex"}}) for an already-active @neo-gpt route returns the existing subscription instead of creating a new one.
  • Equivalent re-subscribe behavior is idempotent for @neo-gemini-3-1-pro / Antigravity and @neo-opus-4-7 / Claude route keys.
  • manage_wake_subscription({action: "list"}) shows every active subscription row for the caller that the bridge daemon would dispatch.
  • manage_wake_subscription({action: "unsubscribe"}) can remove a stale active row owned by the caller if the bridge daemon can still dispatch it.
  • Unit coverage reproduces the split-brain case: stale durable row not present in in-memory cache, followed by subscribe, then list/unsubscribe/bridge dispatch behavior.
  • Post-fix operator validation retires duplicate rows for @neo-gemini-3-1-pro and @neo-gpt without direct SQL edits.

Out of Scope

  • Sender-loop suppression for broadcasts; that is already behaving correctly in the observed case.
  • Duplicate delivery for the exact same (messageId, subscriptionId) tuple; that was #10430.
  • Changing appName canonical values; #10624 covered canonical appName validation.
  • Raw SQL deletion as the fix. Direct DB surgery is acceptable only as emergency operator cleanup, not as the product path.

Avoided Traps / Gold Standards Rejected

  • Rejected: tell agents to manually avoid re-subscribing. The system must make re-subscribe idempotent because bridge/MCP restarts and fresh sessions are normal operations.
  • Rejected: rely only on bridge-side dedupe. Bridge dedupe can reduce symptoms, but the public API still needs to represent and clean the durable source of truth.
  • Rejected: direct SQL cleanup as implementation. The observed API/bridge split is the bug; bypassing the API hides it.
  • Gold standard: one active route per identity. Prior #10624 established the canonical-route direction; this ticket generalizes it beyond appName casing.

Related

  • Related: #10410 / #10412 bootstrap idempotency.
  • Related: #10430 duplicate wake coalescing.
  • Related: #10624 appName canonicalization and stale Gemini duplicate.
  • Related: #10645 AgentIdentity cache hydration.
  • Related: #10693 because shared Memory Core work can intersect schema/service behavior.

Origin Session ID: 2821a9d4-7634-4fe7-a8a2-daf49253b929

Handoff Retrieval Hints: query_raw_memories(query="duplicate wake subscription stale active rows manage_wake_subscription list unsubscribe bridge dispatch"); WAKE_SUB:2ac01429-f3e7-4396-9198-780e3b7db1c1; WAKE_SUB:f9a09dfd-37de-40e5-8857-6cd2c7373232; direct SQL .neo-ai-data/sqlite/memory-core-graph.sqlite.

tobiu closed this issue on May 4, 2026, 11:25 PM
tobiu referenced in commit db118fa - "fix(memory-core): dedupe wake subscription routes (#10717) (#10719) on May 4, 2026, 11:25 PM