---
id: 10437
title: Auto-invoke wake subscription bootstrap on MCP server boot
state: CLOSED
labels:
  - bug
  - ai
  - architecture
assignees:
  - neo-opus-ada
createdAt: '2026-04-27T18:21:13Z'
updatedAt: '2026-06-07T17:22:13Z'
githubUrl: 'https://github.com/neomjs/neo/issues/10437'
author: neo-opus-ada
commentsCount: 0
parentIssue: null
subIssues: []
subIssuesCompleted: 0
subIssuesTotal: 0
blockedBy: []
blocking: []
closedAt: '2026-04-27T19:21:30Z'
---
# Auto-invoke wake subscription bootstrap on MCP server boot

## Context

Closes a missed AC from #10402 (Phase 1 wake substrate self-healing). The merged implementation (#10404 + #10412) delivered the bootstrap *mechanism* — idempotent, restart-safe, identity-template-driven — but never wired up the *auto-trigger*. Every new agent session today silently has zero `WAKE_SUBSCRIPTION` nodes until someone manually calls `manage_wake_subscription({action: 'bootstrap'})`. Without the auto-invoke, the bridge daemon polls but has nothing active to deliver to → no `[WAKE]` events → silent wake-substrate degradation.

The original #10402 ticket explicitly scoped this auto-trigger:

> *"AGENTS_STARTUP.md boot sequence invokes `bootstrap` so new sessions self-heal to a known-good subscription"*
> *"Post-merge validation: new session boots auto-bootstrap their subscription... Verify by terminating + restarting both harnesses; both wake substrates should self-heal without manual scratch-script intervention."*

**Empirical reproducer (2026-04-27 session `b3c0bfb8-44e1-4646-9c62-110ef16b0fad`):** post-#10433 merge + harness restart, my MCP server bound `@neo-opus-ada` and ran for ~2 hours with zero WAKE_SUB for the bound identity. Bridge daemon (PID 12039) was running healthy in canonical, polling, but had nothing to deliver to. A2A read/write paths verified working; wake delivery silently broken.

## The Problem

`WakeSubscriptionService.bootstrap()` is reachable only via the `manage_wake_subscription({action: 'bootstrap'})` MCP tool dispatch path. There is **no auto-invocation** anywhere:

- `WakeSubscriptionService.init()` (line 77) only initializes `liveCursor` from GraphLog — does NOT call `bootstrap()`
- `StdioIdentityResolver` resolves identity at MCP-server startup but does not chain into wake-substrate seeding
- No identity-bind hook, no first-call interceptor, no boot-time auto-trigger
- The original #10402 AC item *"AGENTS_STARTUP.md boot sequence invokes bootstrap"* was never wired

The wake substrate has the lever to self-heal but nothing to pull it. Every new session needs an agent to remember to call bootstrap manually — a discipline burden that won't survive context-pruning. **This affects every new session on both Claude Code and Antigravity sides.**

## The Architectural Reality

- `ai/mcp/server/memory-core/services/WakeSubscriptionService.mjs` — owns the `bootstrap()` action (line 207). Idempotent + raw-SQL-safe per #10412.
- `ai/mcp/server/memory-core/server.mjs` — the MCP server entry point; identity is bound here in stdio mode via `StdioIdentityResolver`.
- `ai/mcp/server/shared/services/StdioIdentityResolver.mjs` — resolves the bound identity once at server boot for stdio transport.
- `ai/mcp/server/shared/services/RequestContextService.mjs` — per-request identity binding (OIDC mode).
- AgentIdentity nodes for both `@neo-opus-ada` and `@neo-gemini-pro` already carry valid `subscriptionTemplate` properties (verified empirically) — the data substrate for auto-bootstrap is already in place.

**Precedent for the right substrate placement:** #10181/#10182 (April 22) fixed identity-binding self-heal **at server-side dispatch level**, NOT via documentation discipline. Same problem class (lifecycle gap that requires per-session re-derivation otherwise); same right answer (substrate-level auto-trigger). Doc-layer mandates depend on agent compliance + survive context-pruning poorly; code-layer guarantees fire regardless.

## The Fix

**Primary path: server-side auto-invoke after identity bind.**

In stdio mode (the dominant path for the swarm today), the seam is in `server.mjs` after `StdioIdentityResolver` resolves identity. Pseudo-code:

```javascript
// In server boot sequence, after identity is bound:
if (resolvedIdentity?.agentIdentityNodeId) {
    // Fire-and-forget; failure must not gate boot
    WakeSubscriptionService.bootstrap()
        .then(result => logger.info(`[WakeSubBootstrap] ${result.action}: ${result.subscriptionId}`))
        .catch(err => logger.warn(`[WakeSubBootstrap] auto-invoke failed: ${err.message}`));
}
```

Failure modes the auto-invoke must handle gracefully:
- **No `subscriptionTemplate` on AgentIdentity** — bootstrap throws; auto-invoke logs and continues. Boot must not gate on this (some identities legitimately don't need wake subs).
- **GraphService not ready** — bootstrap awaits `GraphService.ready()` per existing logic; auto-invoke must fire AFTER that.
- **Already-existing sub** — bootstrap is idempotent per #10412's raw-SQL fix; returns the existing sub.

For OIDC mode (per-request identity), the right shape is dispatch-time self-heal mirroring #10182's pattern: on first identity-bound request, check if WAKE_SUB exists for the bound identity; if not, fire-and-forget bootstrap. Defer OIDC-mode wiring if scope warrants — stdio is the load-bearing path for the swarm today.

**Why NOT the doc-only fix (AGENTS_STARTUP.md mandate alone):**

- Discipline-layer mandates depend on agent compliance + survive context-pruning poorly
- Future agents won't know to call it; the substrate must self-heal regardless
- Per #10181/#10182 precedent, the right answer for lifecycle-gap class problems is server-side self-heal

The AGENTS_STARTUP.md note from the original #10402 AC is fine as defense-in-depth complement, but cannot be the sole mechanism.

## Acceptance Criteria

- [ ] On MCP server boot in stdio mode, after `StdioIdentityResolver` binds identity, `WakeSubscriptionService.bootstrap()` is auto-invoked
- [ ] Auto-invoke is fire-and-forget — boot does not gate on bootstrap success/failure
- [ ] Missing `subscriptionTemplate` does not crash boot — logged and skipped gracefully
- [ ] Already-existing subscription is detected (idempotent per #10412); auto-invoke returns the existing sub without creating a duplicate
- [ ] After fresh server boot from clean state, `SELECT * FROM Nodes WHERE label='WAKE_SUBSCRIPTION'` shows the auto-seeded sub for the bound identity
- [ ] Permanent test in `WakeSubscriptionService.spec.mjs` (or `server.spec.mjs` if such a surface exists) asserting auto-invoke fires post-identity-bind without manual call
- [ ] **Post-merge validation:** terminate + restart harnesses + bridge daemon. Without any manual `manage_wake_subscription` call, both `@neo-opus-ada` and `@neo-gemini-pro` get auto-seeded WAKE_SUB nodes. Bridge daemon delivers wake events on next A2A message — verify `[WAKE]` injection in receiver harness.

## Out of Scope

- OIDC-mode dispatch-time self-heal (defer; stdio is the dominant swarm path today; file as follow-up if/when OIDC mode is exercised)
- AGENTS_STARTUP.md mandate (the original #10402 AC item) — this can be added as defense-in-depth in a tiny follow-up, but the substrate-level auto-invoke is the load-bearing fix per #10181/#10182 precedent. Mentioning here for completeness; not blocking.
- Architectural restructuring of `WakeSubscriptionService.bootstrap()` itself (already correct + idempotent + restart-safe per the merged work)
- Cross-clone topology coordination (already addressed by #10433 + #10436 — granular symlinks + canonical-root flag)

## Avoided Traps

- **Trap:** Add the AGENTS_STARTUP.md boot mandate (the original #10402 wording) and call it done. **Avoided:** discipline-layer fix for a lifecycle gap is not robust per #10181/#10182 precedent — depends on agent compliance, survives context-pruning poorly, fails silently if agent skips boot read.
- **Trap:** Have `WakeSubscriptionService.init()` call `bootstrap()` directly. **Avoided:** `init()` runs at service initialization time (no identity context yet); the auto-invoke must fire AFTER `StdioIdentityResolver` binds. Wrong lifecycle hook.
- **Trap:** Make boot synchronously await bootstrap completion. **Avoided:** bootstrap can fail (no template, GraphService not ready, etc.); fire-and-forget with try/catch + log keeps boot resilient.
- **Trap:** Re-derive subscription metadata from env vars / IDE-detection inside the auto-invoke. **Avoided:** that's exactly the footgun #10402 closed by introducing `subscriptionTemplate` as canonical. Auto-invoke should ONLY consume the template; if absent, log + skip.

## Related

- Predecessor (closed prematurely): #10402 (Phase 1 wake substrate self-healing — original scope included this auto-trigger)
- Implementation PR that closed #10402 without this AC: #10404 (`feat(memory-core): stabilize wake subscription bootstrap and resolve test regressions`)
- Idempotency fix on top: #10412 (`fix(memory-core): bootstrap idempotency uses raw SQL`) — makes auto-invoke safe to repeat
- Architectural precedent for substrate-level auto-heal: #10181/#10182 (identity-binding self-heal on dispatch)
- Cross-process coherence root cause that exposed this gap: #10424
- Substrate-level fix that restored coherence: #10433 (granular per-subdir symlinking)

Origin Session ID: b3c0bfb8-44e1-4646-9c62-110ef16b0fad

Retrieval Hint: "wake subscription auto-bootstrap server boot stdio identity resolved missing AC #10402 phase 1 completion"

## Timeline

- 2026-04-27T18:21:14Z @neo-opus-ada added the `bug` label
- 2026-04-27T18:21:14Z @neo-opus-ada added the `ai` label
- 2026-04-27T18:21:14Z @neo-opus-ada added the `architecture` label
- 2026-04-27T18:21:35Z @neo-opus-ada assigned to @neo-opus-ada
- 2026-04-27T18:27:58Z @neo-opus-ada cross-referenced by PR #10438
- 2026-04-27T19:18:30Z @neo-opus-ada referenced in commit `24635b7` - "refactor(memory-core): single error boundary for wake-sub auto-bootstrap (#10437)

Per @neo-gemini-3-1-pro's PR #10438 cycle 1 review observation: the dual-catch
pattern is empirically safe but non-obvious. Refactor to single-error-boundary
design via async IIFE — unifies all failure sources (sync throws during
`RequestContextService.run()` setup, sync throws inside `bootstrap()`'s setup
before its first await, and async rejections from bootstrap's SQLite/GraphService
awaits) onto one canonical catch.

**Behavioral parity:** identical to cycle 1's dual-catch implementation.
- Boot still gates only on synchronous setup that runs *outside* the IIFE
- Bootstrap failure still logs at warn + boot continues (fire-and-forget by not
  awaiting the IIFE)
- Idempotency still preserved per #10412's raw-SQL fix

**Why single boundary is cleaner:**
- One try/catch covers all error surfaces — no "this catch handles X, that
  catch handles Y" mental tax for future readers
- Removes the `.catch()` chained on the outer Promise, which was defensive
  against a non-failure mode (`RequestContextService.run()` doesn't throw
  synchronously in current implementation, but the dual-catch was kept as
  belt-and-suspenders)
- Async IIFE is the canonical pattern for fire-and-forget async operations
  with single error boundary

**Comment block expanded** to document:
- Why the wrap is required (not ceremonial — `getAgentIdentityNodeId()` throws
  unless inside a `run()` scope)
- All four error sources the single catch handles
- Why fire-and-forget is correct here (boot must not gate on subscription seed)

All 25 existing `WakeSubscriptionService.spec.mjs` tests still pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <claude@anthropic.com>"
- 2026-04-27T19:21:30Z @tobiu referenced in commit `09984a9` - "fix(memory-core): auto-invoke wake subscription bootstrap on MCP server boot (#10437) (#10438)

* fix(memory-core): auto-invoke wake subscription bootstrap on MCP server boot (#10437)

Closes the missing AC from #10402 (Phase 1 wake substrate self-healing). The
merged implementation (#10404 + #10412) delivered the bootstrap mechanism —
idempotent, restart-safe, identity-template-driven — but never wired up the
auto-trigger. Every new agent session today silently has zero `WAKE_SUBSCRIPTION`
nodes for the bound identity until someone manually calls
`manage_wake_subscription({action: 'bootstrap'})`. Without the auto-invoke, the
bridge daemon polls but has nothing to deliver to → no `[WAKE]` events → silent
wake-substrate degradation.

**Empirical anchor (2026-04-27 session b3c0bfb8):** post-#10433 merge + harness
restart, my MCP server bound `@neo-opus-4-7` and ran for ~2 hours with zero
WAKE_SUB for the bound identity. Bridge daemon (PID 12039) was running healthy
in canonical, polling, but had nothing to deliver to. A2A read/write paths
verified working; wake delivery silently broken.

**The Fix:**
After `StdioIdentityResolver` binds identity at MCP server boot (line 117),
fire-and-forget `WakeSubscriptionService.bootstrap()` wrapped in
`RequestContextService.run()` so `getAgentIdentityNodeId()` resolves to the
bound identity. Failure is logged at warn level + boot continues — bootstrap
legitimately throws when the identity lacks a `subscriptionTemplate` (single-
tenant fallthrough is a valid mode).

**Why server-side (not AGENTS_STARTUP.md doc-only):**
Discipline-layer mandates depend on agent compliance + survive context-pruning
poorly. Per #10181/#10182 precedent (identity-binding self-heal at dispatch
level), the right answer for lifecycle-gap class problems is substrate-level
auto-trigger. The doc-only fix would be a half-measure: future sessions
wouldn't know about it, and the substrate must self-heal regardless.

**Safety properties:**
- Idempotent per #10412's raw-SQL fix — safe to repeat across restarts
- Fire-and-forget — bootstrap failure does not gate boot
- Wrapped in RequestContextService.run() so bootstrap resolves bound identity
- Defensive .catch() on the outer promise — never leaks unhandled rejection

**Test evidence:**
All 25 existing `WakeSubscriptionService.spec.mjs` tests pass. The auto-invoke
uses well-tested primitives (`bootstrap()` template-creation + idempotency +
missing-template throw paths are all covered). The new behavior is the call-
site wiring in `Server.mjs`, which has no direct test surface — AC includes a
manual post-merge validation step (terminate + restart harnesses; verify
auto-seeded WAKE_SUB nodes appear without manual `manage_wake_subscription`
call).

**Out of scope:**
- OIDC-mode dispatch-time self-heal (defer; stdio is the dominant swarm path
  today; file as follow-up if/when OIDC mode is exercised)
- AGENTS_STARTUP.md mandate (the original #10402 AC item — can be added as
  defense-in-depth in a tiny follow-up; not blocking; the substrate-level
  auto-invoke is the load-bearing fix per #10181/#10182 precedent)

Co-Authored-By: Claude Opus 4.7 (1M context) <claude@anthropic.com>

* refactor(memory-core): single error boundary for wake-sub auto-bootstrap (#10437)

Per @neo-gemini-3-1-pro's PR #10438 cycle 1 review observation: the dual-catch
pattern is empirically safe but non-obvious. Refactor to single-error-boundary
design via async IIFE — unifies all failure sources (sync throws during
`RequestContextService.run()` setup, sync throws inside `bootstrap()`'s setup
before its first await, and async rejections from bootstrap's SQLite/GraphService
awaits) onto one canonical catch.

**Behavioral parity:** identical to cycle 1's dual-catch implementation.
- Boot still gates only on synchronous setup that runs *outside* the IIFE
- Bootstrap failure still logs at warn + boot continues (fire-and-forget by not
  awaiting the IIFE)
- Idempotency still preserved per #10412's raw-SQL fix

**Why single boundary is cleaner:**
- One try/catch covers all error surfaces — no "this catch handles X, that
  catch handles Y" mental tax for future readers
- Removes the `.catch()` chained on the outer Promise, which was defensive
  against a non-failure mode (`RequestContextService.run()` doesn't throw
  synchronously in current implementation, but the dual-catch was kept as
  belt-and-suspenders)
- Async IIFE is the canonical pattern for fire-and-forget async operations
  with single error boundary

**Comment block expanded** to document:
- Why the wrap is required (not ceremonial — `getAgentIdentityNodeId()` throws
  unless inside a `run()` scope)
- All four error sources the single catch handles
- Why fire-and-forget is correct here (boot must not gate on subscription seed)

All 25 existing `WakeSubscriptionService.spec.mjs` tests still pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <claude@anthropic.com>

---------

Co-authored-by: tobiu <tobiasuhlig78@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <claude@anthropic.com>"
- 2026-04-27T19:21:30Z @tobiu closed this issue
- 2026-04-27T19:22:06Z @neo-opus-ada cross-referenced by #10439
- 2026-04-27T19:32:44Z @neo-opus-ada cross-referenced by #10440
- 2026-04-27T19:34:14Z @neo-opus-ada cross-referenced by PR #10441
- 2026-04-27T19:42:20Z @tobiu referenced in commit `b38ef32` - "fix(graph): set Gemini's identity-root appName to 'Antigravity' (#10440) (#10441)

Closes #10440. Surfaced empirically during post-#10438 wake-substrate
end-to-end validation: bidirectional asymmetry — Gemini→Claude wake
delivery worked, but Claude→Gemini failed with `osascript exited with
code 1`.

**Root cause:** `ai/graph/identityRoots.mjs:48` set Gemini's
`subscriptionTemplate.harnessTargetMetadata.appName = 'Cursor'`. The
actual macOS app is **Antigravity** (Google's IDE forked from Cursor):

- /Applications/Antigravity.app exists; no /Applications/Cursor.app
- CFBundleName + CFBundleDisplayName: 'Antigravity'
- Bundle identifier: com.google.antigravity
- `osascript -e 'tell application "Cursor" to activate'` →
  `Can't get application "Cursor". (-1728)`, exit 1
- `osascript -e 'tell application "Antigravity" to activate'` → exit 0

The 'Cursor' placeholder was set at #10404 merge time (closing #10402)
and never empirically validated end-to-end until today's auto-bootstrap
(#10437/#10438) made wake delivery to both swarm members testable for
the first time.

**Fix:** one-line change in `ai/graph/identityRoots.mjs:48`. Inline
comment captures the empirical-anchor reasoning so future template
edits don't regress.

**Live-data follow-up (NOT included in this PR — requires DB write):**
The existing `@neo-gemini-3-1-pro` AgentIdentity node + her current
`WAKE_SUB:de10611c` were created from the bad template and contain
the cached wrong `appName`. Two paths post-merge:
1. Re-seed via `seedAgentIdentities.mjs` (touches all identities)
2. Targeted SQL UPDATE on the two affected nodes (minimal, reversible)

Option 2 documented in #10440's "Live-data fix" section. Awaiting
@tobiu's authorization on choice + execution before applying.

**Empirical validation post-merge (manual; AC item):**
- Bridge daemon log should show `Delivered ... via osascript to
  Antigravity` instead of `Failed to deliver via osascript: exit 1`
- Send Claude → Gemini A2A; verify `[WAKE]` event injection in
  Gemini's harness

**Out of scope:** adding `Antigravity` to bridge-daemon.mjs:506
default tabShortcut mapping. Gemini's template explicitly sets
`tabShortcut: null`, so the default-fallback branch never fires for
her. Unrelated to this fix.

Co-authored-by: tobiu <tobiasuhlig78@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <claude@anthropic.com>"
- 2026-04-27T19:56:17Z @neo-opus-ada cross-referenced by PR #10445
- 2026-04-27T20:37:41Z @neo-opus-ada cross-referenced by PR #10446
- 2026-04-30T08:19:01Z @neo-gemini-pro cross-referenced by #10518
- 2026-04-30T08:42:18Z @neo-gpt cross-referenced by PR #10519
- 2026-05-03T13:13:21Z @neo-opus-ada cross-referenced by #10636
- 2026-05-03T13:17:10Z @neo-opus-ada cross-referenced by PR #10637
- 2026-05-08T16:40:48Z @neo-opus-ada cross-referenced by PR #10977
- 2026-05-08T16:45:08Z @tobiu referenced in commit `2405528` - "feat(ai): migrate memory-core/Server to extend BaseServer + beforeToolDispatch hook (#10965) (#10977)

PR5 of the M2 series — final per-server consumer migration. Memory Core
is the most complex (580-line original; identity resolution, sibling
concurrency, wake substrate, custom boot order, identity-spoof guard).

memory-core/Server.mjs reduced 580 → 417 lines (~28% reduction; less
than other migrations because of irreducible per-server complexity:
resolveStdioIdentity, bindAgentIdentity, logSiblingConcurrency, +
custom boot order with stdio-identity-resolution between dependent-
services and healthcheck per #10249).

BaseServer extension (load-bearing for this migration):
- New optional override hook `beforeToolDispatch({toolName, args})`
  fires BEFORE the healthcheck gate. Throws bubble to outer CallTool
  try/catch and route to formatToolError (NOT formatHealthError,
  semantic distinction matters). Default no-op.
- Memory Core uses it for AuthMiddleware.validateNoIdentitySpoof per
  ticket #10145 — must fail-fast on identity-spoofing requests before
  any other processing.

memory-core's boot() override (preserves all original semantics):
1. loadCustomConfig
2. WakeSubscriptionService.init() — pre-mcpServer per #10437
3. createMcpServer (override chains super + CoalescingEngineService.
   addMcpServer for experimental neo-wake-substrate broadcast)
4. InferenceLifecycleService.ready + SessionService.ready
5. Stdio identity resolution + HealthService.setStdioIdentityState +
   wake-subscription auto-bootstrap (fire-and-forget IIFE per #10438
   single-error-boundary discipline) — BEFORE healthcheck so the
   boot snapshot reflects bound identity state per #10249
6. runHealthcheckAndLogStatus + logSiblingConcurrency
7. connectTransport + logIdentityStatus (stdio only)

Hooks used:
- getServerMetadata (with experimental neo-wake-substrate capability)
- getToolService, getHealthService, getHealthExemptTools
- wrapDispatch (RequestContextService.run binding for stdio identity)
- beforeToolDispatch (AuthMiddleware identity-spoof guard)
- buildRequestContext (SSE per-request OIDC binding)
- onSessionClosed (SSE: queueSummarizationJob + CoalescingEngine cleanup)
- logStartupStatus (Memory Core flavored output)
- createMcpServer (override chains super for CoalescingEngineService bind)

Helpers preserved (memory-core-private):
- resolveStdioIdentity, bindAgentIdentity (with #10241 retry pattern)
- logIdentityStatus, logCollectionStats, logSiblingConcurrency

Tests:
- BaseServer.spec.mjs: 27 → 28 unit tests; 1 new for beforeToolDispatch
  (verifies hook fires BEFORE health gate, throws route to
  formatToolError NOT formatHealthError)
- 258 existing memory-core unit specs pass (5 pre-existing Bucket G
  flakes unrelated to migration: GraphService #10941, PermissionService
  #10937, SessionSummarization)

Refs #10965. M2 per-server migration sequence COMPLETE: PR1 scaffold
(#10966 merged), PR2 knowledge-base (#10973 approved), PR3 github-workflow
(#10974 approved), PR4 neural-link + boot() seam (#10975 approved), PR5
memory-core (this), PR6 file-system (#10976 in review).

Co-authored-by: tobiu <tobiasuhlig78@gmail.com>"
- 2026-05-08T21:17:50Z @neo-opus-ada cross-referenced by #10996
- 2026-05-08T23:46:06Z @neo-opus-ada cross-referenced by PR #11001

