LearnNewsExamplesServices
Frontmatter
id10442
titlemanage_wake_subscription update action: overwrite-vs-merge semantics on harnessTargetMetadata
stateClosed
labels
bugaiarchitecture
assigneesneo-gpt
createdAtApr 27, 2026, 9:45 PM
updatedAtJun 7, 2026, 7:22 PM
githubUrlhttps://github.com/neomjs/neo/issues/10442
authorneo-opus-ada
commentsCount2
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
blockedBy[]
blocking[]
closedAtJun 3, 2026, 1:56 AM

manage_wake_subscription update action: overwrite-vs-merge semantics on harnessTargetMetadata

Closed Backlog/active-chunk-8 bugaiarchitecture
neo-opus-ada
neo-opus-ada commented on Apr 27, 2026, 9:45 PM

Context

Surfaced empirically during post-#10441 wake-substrate routing diagnosis (2026-04-27, session c68a7d4b). @neo-gemini-pro invoked manage_wake_subscription({action: 'update', ...}) to patch her own WAKE_SUB's stale appName: 'Cursor' (per #10440). Per her A2A description: "I have also updated my local wake subscription WAKE_SUB:de10611c-bd3a-4ec4-8dc0-49c487a4f5c2 via manage_wake_subscription (action: update)".

Empirically observed outcome:

  • The original WAKE_SUB:de10611c-... was deleted (no longer present in canonical sqlite, neither active nor inactive)
  • A NEW WAKE_SUB:b3d1179c-2d75-4816-9cb1-17669df51241 was created
  • The new sub's harnessTargetMetadata is {} (empty object) — neither appName nor tabShortcut populated
  • This caused silent misrouting in the bridge daemon (the parallel ticket on the silent-fallback issue)

The Problem

The update action of manage_wake_subscription apparently performs a wholesale replacement rather than a field-level merge. Two specific concerns:

  1. Identity replacement: the operation deleted the original sub-id and created a fresh one. Update operations conventionally mutate in place — preserving the canonical identifier so downstream consumers (bridge daemon's running cache, logged history, references in graph edges) remain valid.
  2. Metadata overwrite: the resulting sub has an empty harnessTargetMetadata object. If Gemini's call passed only the field she wanted to change (e.g., {harnessTargetMetadata: {appName: 'Antigravity'}}), the implementation may have replaced the entire metadata object with whatever was passed (potentially {} if the call shape was malformed). PATCH-style updates conventionally merge fields by key.

Both behaviors are surprising and warrant explicit decision: replace-vs-merge is a contract-level choice that should be documented clearly in the OpenAPI spec + tested with both shapes.

The Architectural Reality

  • ai/mcp/server/memory-core/services/WakeSubscriptionService.mjs:update() — owns the action handler
  • ai/mcp/server/memory-core/openapi.yamlmanage_wake_subscription documented surface
  • test/playwright/unit/ai/mcp/server/memory-core/services/WakeSubscriptionService.spec.mjs — existing test surface includes update cases (lines ~270-300 from prior reads)

Need to inspect update()'s implementation to determine:

  • Does it call subscribe() then unsubscribe() (effective replace with new ID)?
  • Does it mutate the existing node's properties in place?
  • How are absent fields treated (preserved vs overwritten)?

The empirical evidence says replace-with-new-ID; the source code may confirm or contradict.

The Fix (Pending Diagnosis)

Likely shape based on REST/CRUD conventions + the empirical evidence:

  1. Preserve sub-id across updates. update should mutate the existing node's properties in place; the sub-id should be invariant. Consumers (bridge daemon, MCP notification subscribers, A2A references) shouldn't have to handle sub-id churn.
  2. Field-level merge for nested objects. When updating harnessTargetMetadata, merge with existing values rather than replacing. Pass harnessTargetMetadata: {appName: 'Antigravity'} should preserve tabShortcut if it already exists. Pass harnessTargetMetadata: null or explicit empty object could be the explicit-replace opt-in.
  3. Test coverage for both replace-style and merge-style call shapes; assert sub-id invariance.
  4. OpenAPI documentation clarifying the merge semantics so callers know whether to pass full metadata objects or just changed fields.

Acceptance Criteria

  • Diagnose actual current behavior of update() from the source — confirm replace-vs-merge empirically
  • Document the contract decision (merge-style strongly preferred per CRUD conventions)
  • Implement merge-style if not already (preserve unspecified fields)
  • Preserve sub-id across updates (no replace-with-new-id behavior)
  • Add regression test asserting update preserves unspecified harnessTargetMetadata fields
  • Add regression test asserting sub-id invariance across update calls
  • OpenAPI doc updated with the merge semantics

Out of Scope

  • Migration of existing badly-replaced subs from #10440-era updates (Gemini's b3d1179c is the only known case; she'll re-update with explicit full metadata as the immediate fix)
  • Refactoring subscribe() / unsubscribe() separately (unless update() is implemented in terms of them, in which case the fix may need to touch them)
  • New patch / merge action variants (the existing update should be the merge-style path; introducing a separate action would fragment the surface)

Avoided Traps

  • Trap: Patch only Gemini's specific sub via SQL UPDATE. Avoided: that fixes one symptom; the architectural contract of update is the underlying concern. Future agents using update would re-encounter the same surprise.
  • Trap: Document the current behavior as intended without challenging the contract. Avoided: REST/CRUD conventions (and substrate consumers like bridge daemon) expect merge-style updates with sub-id invariance. Codifying the surprising behavior would make the substrate harder to reason about, not easier.

Related

  • Empirical anchor surfaced via #10440's live-data fix attempt
  • Adjacent silent-fallback misroute concern (filed separately as the second follow-up from this session)
  • Bootstrap action that creates the canonical metadata: #10412 (raw-SQL idempotency)

Origin Session ID: c68a7d4b-909a-4965-9bf9-116906d271a3

Retrieval Hint: "manage_wake_subscription update action overwrite merge semantics sub-id replacement empty harnessTargetMetadata"