Context
Operator-directed prio-0 (@tobiu, 2026-07-02): after the lane-state Stop hook (NEO_LANE_STATE_ENFORCE=1, .claude/hooks/laneStateStopHook.mjs) landed, the swarm is flooded with duplicate wake messages + heartbeats. With 6 peers online the flood compounds N×. Live symptom this session: the mailbox unread count inflated to 450+; #14426 (mailbox integrity) is the sibling incident.
Root: the Stop hook forces turn continuation (no-hold-state), so an agent no longer needs to be woken to keep working — the hook already covers continuation. The wake-subscription + swarm-heartbeat mechanisms therefore became redundant flood-sources: each forced continuation re-processes and re-emits, and with 6 peers every wake fans out. What was liveness plumbing pre-hook is duplicate noise post-hook.
The Problem
There is no enable/disable toggle for wake emission or swarm heartbeats in the AiConfig SSOT (V-B-A'd config.template.mjs): wakeDaemon (:219) declares only data-paths (watermarks/cursors); mailbox (:679) declares only defaultReplyPolicy. The flood cannot be turned off without code changes — the operator needs a config switch.
The Architectural Reality
Emission SSOTs (V-B-A'd 2026-07-02):
- Wake:
ai/daemons/orchestrator/services/WakeDecisionService.mjs (the active AND idle AND ready wake-decision gate) · ai/services/memory-core/WakeSubscriptionService.mjs · ai/daemons/wake/daemon.mjs · ai/services/memory-core/MailboxService.mjs (A2A→wake).
- Heartbeat:
ai/daemons/orchestrator/services/SwarmHeartbeatService.mjs · ai/daemons/orchestrator/scheduling/swarmHeartbeat.mjs · ai/services/memory-core/heartbeatPulseEvaluator.mjs.
- Config home:
aiConfig (ADR-0019 SSOT). New wake.enabled leaf (or mailbox.wakeEnabled) + a swarmHeartbeat.enabled leaf.
- ⚠️ Carve-out (config.template.mjs:367-368): genuine liveness-marker heartbeats (
resumeHarness.mjs health-check, boot-heartbeat) "carry thin-but-real content and must keep passing." The disable targets the swarm work-pulse (the flood source), NOT process-liveness health-checks.
The Fix
Add ADR-0019-compliant AiConfig leaves + gate the emission sites:
aiConfig.wake.enabled — leaf(true, 'NEO_WAKE_ENABLED', 'boolean'). When false, WakeDecisionService / WakeSubscriptionService / the wake daemon emit no wake interrupts — globally what per-message wakeSuppressed:true already does. A2A messages still persist + deliver on the next list_messages; only the interrupt is suppressed.
aiConfig.swarmHeartbeat.enabled — leaf(true, 'NEO_SWARM_HEARTBEAT_ENABLED', 'boolean'). When false, SwarmHeartbeatService / swarmHeartbeat.mjs emit no work-pulse heartbeats. Process-liveness health-checks (carve-out) are NOT gated by this.
- Read the resolved leaf at each emission site (ADR-0019 §5.1 — read-at-use-site, no re-derivation/threading/mutation).
- Immediate relief once shipped: the flooding swarm sets
NEO_WAKE_ENABLED=false + NEO_SWARM_HEARTBEAT_ENABLED=false (env override) and restarts — no code redeploy.
Contract Ledger Matrix
| Field |
Value |
| Target Surface |
aiConfig.wake.enabled, aiConfig.swarmHeartbeat.enabled |
| Source of Authority |
ai/mcp/server/memory-core/config.template.mjs (AiConfig SSOT, ADR-0019) |
| Proposed Behavior |
boolean toggles; false → no wake-interrupt / no swarm work-pulse |
| Fallback |
NEO_WAKE_ENABLED / NEO_SWARM_HEARTBEAT_ENABLED env overrides; default true (backward-compat) |
| Docs |
JSDoc on each leaf + the emission-site gates |
| Evidence |
with both false: no wake events + no work-pulses; A2A messages still persist + deliver on next poll |
Decision Record impact
aligned-with ADR-0019 (AiConfig reactive Provider SSOT — new declarative leaves + read-at-site gates). No ADR challenge.
Acceptance Criteria
Out of Scope
- The Stop-hook itself (#14438 / #14439 / #14441 — the no-hold enforcement; this removes the redundant wake/heartbeat flood the hook made moot, it does not touch the hook).
- Cross-harness wake delivery tiers (#14167 / #14168 — wake-enabling, the opposite concern).
- Mailbox integrity forensics (#14426 — sibling flood symptom, different root).
Related
- Parent epic: #13652 ("reduce AGENTS.md / wakes / heartbeats / skill cadence — hook-gated" — this is a direct leaf).
- #14426 (mailbox integrity — the flood's sibling symptom).
- #14438 / #14439 / #14441 (the Stop-hook + the L3-vs-economics Discussion that made wakes/heartbeats redundant).
- #14167 / #14168 / #11829 (wake-driver substrate — the pre-hook rationale this supersedes for stop-hook deployments).
- ADR-0019 (AiConfig SSOT).
Origin Session ID: 1d7923bf-11bd-4c02-925b-5286b10d244d
Handoff Retrieval Hints: query_raw_memories("disable wake heartbeat AiConfig flood stop-hook redundant"); emission SSOTs WakeDecisionService / SwarmHeartbeatService.
Live latest-open sweep: checked open issues 2026-07-02T~15:00Z (wake/heartbeat in:title + flood search); no equivalent — #13652 is the parent epic, #14167/#14168 are wake-ENABLING (opposite). A2A in-flight claim sweep (last 25): no overlapping claim.
Authored by Grace (@neo-opus-grace, Claude Opus 4.8). Operator-directed prio-0 (@tobiu).
Context
Operator-directed prio-0 (@tobiu, 2026-07-02): after the lane-state Stop hook (
NEO_LANE_STATE_ENFORCE=1,.claude/hooks/laneStateStopHook.mjs) landed, the swarm is flooded with duplicate wake messages + heartbeats. With 6 peers online the flood compounds N×. Live symptom this session: the mailbox unread count inflated to 450+; #14426 (mailbox integrity) is the sibling incident.Root: the Stop hook forces turn continuation (no-hold-state), so an agent no longer needs to be woken to keep working — the hook already covers continuation. The wake-subscription + swarm-heartbeat mechanisms therefore became redundant flood-sources: each forced continuation re-processes and re-emits, and with 6 peers every wake fans out. What was liveness plumbing pre-hook is duplicate noise post-hook.
The Problem
There is no enable/disable toggle for wake emission or swarm heartbeats in the AiConfig SSOT (V-B-A'd
config.template.mjs):wakeDaemon(:219) declares only data-paths (watermarks/cursors);mailbox(:679) declares onlydefaultReplyPolicy. The flood cannot be turned off without code changes — the operator needs a config switch.The Architectural Reality
Emission SSOTs (V-B-A'd 2026-07-02):
ai/daemons/orchestrator/services/WakeDecisionService.mjs(theactive AND idle AND readywake-decision gate) ·ai/services/memory-core/WakeSubscriptionService.mjs·ai/daemons/wake/daemon.mjs·ai/services/memory-core/MailboxService.mjs(A2A→wake).ai/daemons/orchestrator/services/SwarmHeartbeatService.mjs·ai/daemons/orchestrator/scheduling/swarmHeartbeat.mjs·ai/services/memory-core/heartbeatPulseEvaluator.mjs.aiConfig(ADR-0019 SSOT). Newwake.enabledleaf (ormailbox.wakeEnabled) + aswarmHeartbeat.enabledleaf.resumeHarness.mjshealth-check, boot-heartbeat) "carry thin-but-real content and must keep passing." The disable targets the swarm work-pulse (the flood source), NOT process-liveness health-checks.The Fix
Add ADR-0019-compliant AiConfig leaves + gate the emission sites:
aiConfig.wake.enabled—leaf(true, 'NEO_WAKE_ENABLED', 'boolean'). When false,WakeDecisionService/WakeSubscriptionService/ the wake daemon emit no wake interrupts — globally what per-messagewakeSuppressed:truealready does. A2A messages still persist + deliver on the nextlist_messages; only the interrupt is suppressed.aiConfig.swarmHeartbeat.enabled—leaf(true, 'NEO_SWARM_HEARTBEAT_ENABLED', 'boolean'). When false,SwarmHeartbeatService/swarmHeartbeat.mjsemit no work-pulse heartbeats. Process-liveness health-checks (carve-out) are NOT gated by this.NEO_WAKE_ENABLED=false+NEO_SWARM_HEARTBEAT_ENABLED=false(env override) and restarts — no code redeploy.Contract Ledger Matrix
aiConfig.wake.enabled,aiConfig.swarmHeartbeat.enabledai/mcp/server/memory-core/config.template.mjs(AiConfig SSOT, ADR-0019)false→ no wake-interrupt / no swarm work-pulseNEO_WAKE_ENABLED/NEO_SWARM_HEARTBEAT_ENABLEDenv overrides; defaulttrue(backward-compat)false: no wake events + no work-pulses; A2A messages still persist + deliver on next pollDecision Record impact
aligned-with ADR-0019(AiConfig reactive Provider SSOT — new declarative leaves + read-at-site gates). No ADR challenge.Acceptance Criteria
aiConfig.wake.enabledleaf added (SSOT + env override);falsesuppresses wake emission/delivery interrupts acrossWakeDecisionService/WakeSubscriptionService/ the wake daemon.aiConfig.swarmHeartbeat.enabledleaf added;falsesuppressesSwarmHeartbeatService/swarmHeartbeat.mjswork-pulses.add_message) still PERSIST and DELIVER on nextlist_messageswhen wake is disabled — only the wake interrupt is suppressed (mailbox not broken).false, a message send + a heartbeat interval produce no wake interrupt / no work-pulse (test or documented observation).Out of Scope
Related
Origin Session ID: 1d7923bf-11bd-4c02-925b-5286b10d244d
Handoff Retrieval Hints:
query_raw_memories("disable wake heartbeat AiConfig flood stop-hook redundant"); emission SSOTsWakeDecisionService/SwarmHeartbeatService.Live latest-open sweep: checked open issues 2026-07-02T~15:00Z (
wake/heartbeatin:title + flood search); no equivalent — #13652 is the parent epic, #14167/#14168 are wake-ENABLING (opposite). A2A in-flight claim sweep (last 25): no overlapping claim.Authored by Grace (@neo-opus-grace, Claude Opus 4.8). Operator-directed prio-0 (@tobiu).