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:
- Make
subscribe idempotent for the canonical active route tuple (agentIdentity, trigger, harnessTarget, normalized filters, canonical harnessTargetMetadata route key such as appName/url).
- 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.
- Add an operator-safe cleanup path for stale duplicate rows owned by the caller, without raw SQL surgery.
- 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
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.
Context
After all #10704 PRs merged on 2026-05-04, @tobiu restarted the bridge daemon from fresh
devand 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:
WAKE_SUBids.WAKE_SUB:f9a09dfd-37de-40e5-8857-6cd2c7373232and once through the newWAKE_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"})returnedSubscription 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:
WAKE_SUBSCRIPTIONrows from SQLite and dispatches all matching active rows.manage_wake_subscriptionAPI can fail to list or unsubscribe stale active rows that still exist in SQLite and still drive bridge delivery.subscribecreates a freshWAKE_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.sqliteshowed these active rows immediately after the duplicate wakes: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#subscribegenerates a fresh subscription id every time and does not perform a uniqueness check for(agentIdentity, trigger, harnessTarget, harnessTargetMetadata.appName).WakeSubscriptionService.mjs#bootstraphas a raw-SQL idempotency path added for #10410/#10412, but the publicsubscribepath still permits duplicates.manage_wake_subscription({action: "list"})andunsubscribecan disagree with the bridge daemon because the API path does not surface every active SQLite row that the bridge dispatch path sees.(messageId, subscriptionId)tuple. This ticket is different: there are multiple subscription ids for the same recipient/route tuple.Antigravity, and @neo-gpt duplicates use canonicalCodex.The Fix
Unify active-subscription semantics across API and bridge paths:
subscribeidempotent for the canonical active route tuple(agentIdentity, trigger, harnessTarget, normalized filters, canonical harnessTargetMetadata route key such as appName/url).listandunsubscriberead from the same durable SQLite source of truth that the bridge daemon uses, or otherwise hydrate stale rows before returning.Acceptance Criteria
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.Antigravityand @neo-opus-4-7 /Clauderoute 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.subscribe, thenlist/unsubscribe/bridge dispatch behavior.Out of Scope
(messageId, subscriptionId)tuple; that was #10430.Avoided Traps / Gold Standards Rejected
Related
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.