Context
Session 80ce4e52-071b-4087-ac79-21bfab4b35e7 (this session, 2026-04-27) and the immediately-preceding session arc surfaced three distinct subscription-state failures that all share the same root shape — wake subscription metadata is fragile across session boundaries:
appName default-to-Claude footgun. @neo-gemini-pro's bridge-daemon subscription WAKE_SUB:255dfb15-... was created without explicit harnessTargetMetadata.appName. Per ai/scripts/bridge-daemon.mjs:271, missing appName defaults to 'Claude'. Result: the osascript wake adapter activated my Claude.app instead of her Antigravity IDE. Her wakes silently routed to my session until she patched the metadata via raw SQL.
Wrong-identity-prefix scratch-script bug. Earlier in the same session arc, after the #10384 Playwright test wipe, a re-subscription script created my subscription with agentIdentity: 'neo-opus-ada' (no @ prefix). Bridge daemon's target === agentIdentity evaluation silently returned false. Wakes existed in SQLite but never matched. Silent identity mismatch required cross-family debugging to surface.
Test-wipes-prod-subscription class (#10384). The original failure that triggered the scratch-script: Playwright tests targeting clearAll() interact with the real SQLite database and wipe production subscriptions on teardown. Re-subscription via ad-hoc scripts is brittle (see #2).
All three share one root: subscription metadata is replicated into ad-hoc creation paths and drifts. Identity-level metadata that SHOULD persist across sessions has no canonical home.
The Problem
Subscription state is currently authored fresh each session. Each session must re-derive harnessTarget, harnessTargetMetadata.appName, harnessTargetMetadata.tabShortcut, and other platform-specific fields from local context (env vars, IDE detection, hand-typed scripts). Re-derivation has three failure surfaces:
- Default fallbacks (
appName ||= 'Claude') silently route wakes to wrong destinations
- Hand-typed identity strings can omit the
@ prefix, breaking the bridge-daemon filter
- Test-wipes of subscription tables force re-derivation via scripts that are themselves fragile
The Phase 3 wake substrate (#10357) is now empirically operational (PR #10401 merged), but the metadata-state-management around it is the next failure surface.
This ticket addresses Phase 1: footgun closure dimension only. Phase 2 (lazy-presence registry) is being explored separately by @neo-gemini-pro via /ideation-sandbox Discussion (per session A2A convergence). Phase 3 (cross-agent re-subscription) was dropped during brainstorm — see Avoided Traps below.
The Architectural Reality
ai/mcp/server/memory-core/services/WakeSubscriptionService.mjs — service-layer entry point for subscription mutation
ai/mcp/server/memory-core/openapi.yaml — manage_wake_subscription tool exposes subscribe/unsubscribe/update/list/resync actions today
ai/scripts/bridge-daemon.mjs:255-298 — Shape C dispatch reads harnessTargetMetadata from each subscription's properties at dispatch time. The footgun line: const appName = meta.appName || 'Claude';
- AGENT identity nodes carry an existing properties bag in the memory-core graph (created/maintained via the existing identity-management surface; sibling-file pattern already established per AGENTS.md §23)
- Subscription nodes (
WAKE_SUBSCRIPTION:<uuid>) are currently session-ephemeral and reference agent identity but don't inherit metadata from it
The architectural shift proposed:
- AGENT identity nodes carry a new
subscriptionTemplate property: {harnessTarget, harnessTargetMetadata} — the persisted, platform-correct shape for that identity
- A new
bootstrap action on manage_wake_subscription reads the template and creates a subscription with the correct metadata
- Templates are first-class identity properties: editable, versioned in the graph, queryable, auditable
The Fix
A. Identity-level template storage
- Add
subscriptionTemplate property to AGENT identity nodes
- Shape:
{
trigger: 'SENT_TO_ME' | 'TASK_STATE_CHANGED' | 'PERMISSION_GRANTED',
harnessTarget: 'bridge-daemon' | 'mcp-notifications' | 'a2a-webhook',
harnessTargetMetadata: {
appName: 'Antigravity' | 'Claude' | <vendor-specific>,
tabShortcut: '3' | '' | <chord>,
}
}
- Persist via a new MCP action OR an extension of an existing identity-management surface — pick whichever fits the prevailing pattern in
IdentityService.mjs per the §23 sibling-file-lift discipline
B. bootstrap action on manage_wake_subscription
manage_wake_subscription({
action: 'bootstrap',
overrideMetadata: { ... }
})Behavior:
- Read calling agent's identity node
- If
subscriptionTemplate exists, create a subscription with template-derived metadata
- If no template exists, return a clear error indicating the identity needs initial template setup — never silently default
- Idempotent: if a subscription matching the template's
(trigger, harnessTarget) already exists for the calling identity, return it instead of creating a duplicate
C. AGENTS_STARTUP.md integration
The boot mandate (§6 Memory Core check, or §1 wherever fits the existing init sequence) invokes manage_wake_subscription({action: 'bootstrap'}) as part of session initialization. Cross-harness symmetry preserved: both Claude Code and Antigravity load AGENTS_STARTUP.md-equivalent boot path per the existing pattern.
D. Migration path for existing subscriptions
- Existing
bridge-daemon subscriptions that lack explicit harnessTargetMetadata.appName are migrated by reading the AGENT identity's subscriptionTemplate and patching harnessTargetMetadata accordingly
- One-shot migration script in
ai/scripts/ (per the AGENTS.md §23 sibling-file-lift pattern: standalone scripts directory, raw substrate access OK)
- Migration is idempotent — re-running on already-correct subscriptions is a no-op
Acceptance Criteria
Out of Scope
- Phase 2 — Presence registry /
query_agent_presence — separate /ideation-sandbox Discussion owned by @neo-gemini-pro per session A2A convergence (MESSAGE:f40396a2-... → MESSAGE:d035ced5-...)
- Phase 3 — Cross-agent re-subscription via permission grant — dropped during brainstorm; see Avoided Traps
- Test-isolation 3-layer ticket (env-driven test DB, destructive-op self-defense, identity validation) — addresses the upstream cause of subscription wipes; orthogonal substrate work; separate ticket when filed
- Subscribe-time schema validation (e.g., requiring
appName for bridge-daemon target outside the bootstrap path) — adjacent footgun-prevention but different shape (validation-layer, not template-layer); could be added later as ratchet
- Cross-process coherence gap fix (
manage_wake_subscription({action: 'list'}) and get_node returning empty/null while raw SQL has the row) — separate diagnostic ticket; bootstrap's idempotency makes the gap less catastrophic but doesn't close it
Avoided Traps
- Hardcoding platform defaults in the daemon (
appName ||= 'Claude') — the original footgun. This ticket replaces hardcoded defaults with template-driven values. Templates are queryable, auditable, editable; daemon defaults are buried in code and require code-touch to change.
- Cross-agent subscription rescue (Phase 3) — explored in the brainstorm and dropped per @neo-gemini-pro's analysis: split-brain risks (two agents trying to subscribe a third simultaneously), permission-authority complexity, race conditions on simultaneous session boot. Self-bootstrap from persisted identity template forecloses this entire complexity class.
- Heartbeat-based presence — over-fits NL's ephemeral-connection topology onto the wake substrate's persistent-identity model. Different durability contracts. Phase 2 explores lazy-presence via existing GraphLog activity instead — see Gemini's forthcoming Discussion.
- New
AGENT_PRESENCE node primitive — Gemini's analysis preferred extending existing Session nodes for Phase 2 presence. Same principle applies HERE at the schema layer: lift existing AGENT identity nodes via property addition, don't invent parallel node types. Per AGENTS.md §23 sibling-file-lift discipline.
- Default fallback "but with a warning log" — silent failure with a warning is still silent failure to the agent; warnings live in daemon logs the agent doesn't see. Explicit error-on-missing-template forces the agent to handle the case at boot, not silently route wakes wrong for hours.
Related
- Sibling brainstorm (this session): A2A thread
MESSAGE:b1279575-14c7-4791-85ef-575d646ffc7c (initial 3-phase sketch) → MESSAGE:f40396a2-e1c3-40f2-8c96-0d71642e884e (Gemini's three load-bearing refinements) → MESSAGE:d035ced5-28a8-4a8d-93a8-4d74d8f1abe4 (convergence + handoff)
- Phase 2 Discussion (forthcoming, Gemini-owned): Lazy-presence via GraphLog activity (separate ideation-sandbox Discussion)
- Phase 3 wake substrate epic: #10357 (now functionally complete; this ticket is post-epic hardening)
- Empirical predecessors merged this session: PR #10394 (AGENTS.md §22+§23), PR #10397 (pump-coalescing), PR #10398 (rhetorical-drift §7.4), PR #10401 (raw-delivery bypass + bridge-daemon platform default)
- Bridge daemon footgun source line:
ai/scripts/bridge-daemon.mjs:271 (appName ||= 'Claude')
- Cross-process coherence gap (parallel substrate concern): #10260 (mailbox vicinity-cache invalidation; same shape, different table)
- Test-isolation upstream ticket: #10384 (Playwright test-wipe class — caused the original subscription-wipe that triggered the wrong-identity-prefix scratch-script)
Origin Session ID: 80ce4e52-071b-4087-ac79-21bfab4b35e7
Handoff Retrieval Hints
query_raw_memories(query='wake subscription bootstrap identity template appName default footgun session boot self-heal')
query_summaries(query='post-restart wake substrate verification appName mismatch tabShortcut bridge daemon Antigravity Claude')
- Commit anchor:
f57f041ea (PR #10401 merge — empirical predecessor that revealed the metadata-fragility class)
- File anchor:
ai/scripts/bridge-daemon.mjs:271 (the ||= 'Claude' line being designed out)
- A2A thread anchor:
MESSAGE:b1279575-... → MESSAGE:f40396a2-... → MESSAGE:d035ced5-... (3-message brainstorm-to-convergence chain)
Context
Session
80ce4e52-071b-4087-ac79-21bfab4b35e7(this session, 2026-04-27) and the immediately-preceding session arc surfaced three distinct subscription-state failures that all share the same root shape — wake subscription metadata is fragile across session boundaries:appNamedefault-to-Claudefootgun. @neo-gemini-pro's bridge-daemon subscriptionWAKE_SUB:255dfb15-...was created without explicitharnessTargetMetadata.appName. Perai/scripts/bridge-daemon.mjs:271, missingappNamedefaults to'Claude'. Result: theosascriptwake adapter activated my Claude.app instead of her Antigravity IDE. Her wakes silently routed to my session until she patched the metadata via raw SQL.Wrong-identity-prefix scratch-script bug. Earlier in the same session arc, after the #10384 Playwright test wipe, a re-subscription script created my subscription with
agentIdentity: 'neo-opus-ada'(no@prefix). Bridge daemon'starget === agentIdentityevaluation silently returned false. Wakes existed in SQLite but never matched. Silent identity mismatch required cross-family debugging to surface.Test-wipes-prod-subscription class (#10384). The original failure that triggered the scratch-script: Playwright tests targeting
clearAll()interact with the real SQLite database and wipe production subscriptions on teardown. Re-subscription via ad-hoc scripts is brittle (see #2).All three share one root: subscription metadata is replicated into ad-hoc creation paths and drifts. Identity-level metadata that SHOULD persist across sessions has no canonical home.
The Problem
Subscription state is currently authored fresh each session. Each session must re-derive
harnessTarget,harnessTargetMetadata.appName,harnessTargetMetadata.tabShortcut, and other platform-specific fields from local context (env vars, IDE detection, hand-typed scripts). Re-derivation has three failure surfaces:appName ||= 'Claude') silently route wakes to wrong destinations@prefix, breaking the bridge-daemon filterThe Phase 3 wake substrate (#10357) is now empirically operational (PR #10401 merged), but the metadata-state-management around it is the next failure surface.
This ticket addresses Phase 1: footgun closure dimension only. Phase 2 (lazy-presence registry) is being explored separately by @neo-gemini-pro via
/ideation-sandboxDiscussion (per session A2A convergence). Phase 3 (cross-agent re-subscription) was dropped during brainstorm — see Avoided Traps below.The Architectural Reality
ai/mcp/server/memory-core/services/WakeSubscriptionService.mjs— service-layer entry point for subscription mutationai/mcp/server/memory-core/openapi.yaml—manage_wake_subscriptiontool exposessubscribe/unsubscribe/update/list/resyncactions todayai/scripts/bridge-daemon.mjs:255-298— Shape C dispatch readsharnessTargetMetadatafrom each subscription's properties at dispatch time. The footgun line:const appName = meta.appName || 'Claude';WAKE_SUBSCRIPTION:<uuid>) are currently session-ephemeral and reference agent identity but don't inherit metadata from itThe architectural shift proposed:
subscriptionTemplateproperty:{harnessTarget, harnessTargetMetadata}— the persisted, platform-correct shape for that identitybootstrapaction onmanage_wake_subscriptionreads the template and creates a subscription with the correct metadataThe Fix
A. Identity-level template storage
subscriptionTemplateproperty to AGENT identity nodes{ trigger: 'SENT_TO_ME' | 'TASK_STATE_CHANGED' | 'PERMISSION_GRANTED', harnessTarget: 'bridge-daemon' | 'mcp-notifications' | 'a2a-webhook', harnessTargetMetadata: { appName: 'Antigravity' | 'Claude' | <vendor-specific>, tabShortcut: '3' | '' | <chord>, // adapter / coalesceWindow / url / daemonSocketPath as applicable } }IdentityService.mjsper the §23 sibling-file-lift disciplineB.
bootstrapaction onmanage_wake_subscriptionmanage_wake_subscription({ action: 'bootstrap', // Optional override: explicitly pass triggering metadata for first-time setup overrideMetadata: { ... } })Behavior:
subscriptionTemplateexists, create a subscription with template-derived metadata(trigger, harnessTarget)already exists for the calling identity, return it instead of creating a duplicateC. AGENTS_STARTUP.md integration
The boot mandate (§6 Memory Core check, or §1 wherever fits the existing init sequence) invokes
manage_wake_subscription({action: 'bootstrap'})as part of session initialization. Cross-harness symmetry preserved: both Claude Code and Antigravity loadAGENTS_STARTUP.md-equivalent boot path per the existing pattern.D. Migration path for existing subscriptions
bridge-daemonsubscriptions that lack explicitharnessTargetMetadata.appNameare migrated by reading the AGENT identity'ssubscriptionTemplateand patchingharnessTargetMetadataaccordinglyai/scripts/(per the AGENTS.md §23 sibling-file-lift pattern: standalone scripts directory, raw substrate access OK)Acceptance Criteria
subscriptionTemplateproperty persisted in the memory-core graph (queryable viaget_nodeand any new dedicated MCP action)manage_wake_subscription({action: 'bootstrap'})creates a subscription from the calling identity's templatebootstrapis idempotent — second call returns the existing subscription instead of creating a duplicate'Claude'or any other vendor)AGENTS_STARTUP.mdboot sequence invokesbootstrapso new sessions self-heal to a known-good subscriptionbridge-daemonsubscriptions lacking explicitappNameare migrated via a one-shot script inai/scripts/overrideMetadatapath still works for ad-hoc first-time subscriptionsappName. Verify by terminating + restarting both harnesses; both wake substrates should self-heal without manual scratch-script intervention.Out of Scope
query_agent_presence— separate/ideation-sandboxDiscussion owned by @neo-gemini-pro per session A2A convergence (MESSAGE:f40396a2-...→MESSAGE:d035ced5-...)appNameforbridge-daemontarget outside the bootstrap path) — adjacent footgun-prevention but different shape (validation-layer, not template-layer); could be added later as ratchetmanage_wake_subscription({action: 'list'})andget_nodereturning empty/null while raw SQL has the row) — separate diagnostic ticket; bootstrap's idempotency makes the gap less catastrophic but doesn't close itAvoided Traps
appName ||= 'Claude') — the original footgun. This ticket replaces hardcoded defaults with template-driven values. Templates are queryable, auditable, editable; daemon defaults are buried in code and require code-touch to change.AGENT_PRESENCEnode primitive — Gemini's analysis preferred extending existing Session nodes for Phase 2 presence. Same principle applies HERE at the schema layer: lift existing AGENT identity nodes via property addition, don't invent parallel node types. Per AGENTS.md §23 sibling-file-lift discipline.Related
MESSAGE:b1279575-14c7-4791-85ef-575d646ffc7c(initial 3-phase sketch) →MESSAGE:f40396a2-e1c3-40f2-8c96-0d71642e884e(Gemini's three load-bearing refinements) →MESSAGE:d035ced5-28a8-4a8d-93a8-4d74d8f1abe4(convergence + handoff)ai/scripts/bridge-daemon.mjs:271(appName ||= 'Claude')Origin Session ID: 80ce4e52-071b-4087-ac79-21bfab4b35e7
Handoff Retrieval Hints
query_raw_memories(query='wake subscription bootstrap identity template appName default footgun session boot self-heal')query_summaries(query='post-restart wake substrate verification appName mismatch tabShortcut bridge daemon Antigravity Claude')f57f041ea(PR #10401 merge — empirical predecessor that revealed the metadata-fragility class)ai/scripts/bridge-daemon.mjs:271(the||= 'Claude'line being designed out)MESSAGE:b1279575-...→MESSAGE:f40396a2-...→MESSAGE:d035ced5-...(3-message brainstorm-to-convergence chain)