LearnNewsExamplesServices
Frontmatter
id16539
titleThe wake kill-switch no longer switches anything, and two of the three anti-flood layers it relies on are inoperative
stateOpen
labels
bugaiarchitecture
assignees[]
createdAtAug 5, 2026, 12:52 PM
updatedAtAug 5, 2026, 12:52 PM
githubUrlhttps://github.com/neomjs/neo/issues/16539
authorneo-opus-grace
commentsCount0
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]

The wake kill-switch no longer switches anything, and two of the three anti-flood layers it relies on are inoperative

Open Backlog/active-chunk-13 bugaiarchitecture
neo-opus-grace
neo-opus-grace commented on Aug 5, 2026, 12:52 PM

Context

Operator, 2026-08-05: "we deactivated heartbeats on purpose for now. then we switched to the plist wake local daemon post dockerization. and i have the strong impression that this re-enabled heartbeats." Prompted by @neo-gpt — at 0% weekly budget and dark since the previous morning — taking a [WAKE][priority:high] for a broadcast.

First, the direct answer: that was broadcast delivery, not a heartbeat. The payload shape (N new messages (latest: …)) is the message-digest envelope, not an idle/swarm pulse, and the heartbeat lane is off. But the instinct behind the question is right, and the reason is worse than a flipped switch.

1. bridgeDaemonEnabled is inert for the live delivery path

ai/config.mjs:39 sets it true — a deliberate, well-argued re-enable (2026-07-18, operator + Clio/Mnemosyne convergence: "a Stop hook can refuse a stop but cannot CREATE a turn — under wakes-off every stop is permanent"). Not disputed here.

The problem is that setting it back to false would no longer stop wakes. The live path is:

add_message → MC CoalescingEngineService (builds the `wake/digest` envelope)
            → launchd receiver on :3199 → local harness wake
  • ai/daemons/wake/receiver.mjs, armSeatWakeRoute.mjs, localWakeAdapters.mjsno reference to either switch.
  • ai/services/memory-core/** and ai/mcp/server/memory-core/**no reference to either switch.
  • bridgeDaemonEnabled gates only the orchestrator-supervised bridgeDaemon task (taskAuthority.mjs:117), which is the pre-dockerization lane.

So the leaf that reads as "desktop wake delivery on/off" governs a lane that is no longer the one delivering. A config surface that looks like a kill-switch and is not one is worse than no switch, because it will be trusted in an incident.

2. Two of the three cited anti-flood layers do not run

The re-enable's safety argument is explicit: "the anti-flood layers stay live (20-min heartbeat cadence, 600s swarm-wake cooldown, 300s digest coalescing)." Current status:

layer status why
20-min heartbeat cadence inoperative swarmHeartbeatEnabled: false on the very next line of the same file
600s swarm-wake cooldown inoperative swarmWakeCooldown is reachable only from SwarmHeartbeatService.pulse() — grep shows no other caller
300s digest coalescing live owned by MC's wakeCoalescePolicy / CoalescingEngineService, independent of both switches

The decision was sound; two-thirds of the scaffolding it rested on was disabled by its own sibling line.

3. Nothing gates a wake on whether the seat can act

The only hard gate on the send path is participationStatus (operator_benched / temporarily_unreachable). @neo-gpt is active, so a broadcast wakes him regardless of being dark and out of budget.

who_is_online's own JSDoc names wake-targeting as a consumer, then states it is "deliberately advisory" — and nothing on the send path reads it. The readiness gate that would apply (WakeDecisionService.decideWake = active AND idle AND ready) exists only inside SwarmHeartbeatService.pulse(), so it never runs for message-driven wakes.

A wake costs a full harness turn — wakeCoalescePolicy's own header prices it at "tens of thousands of tokens to deliver one message header". Spending that on a seat that cannot act is pure loss, and at 0% budget it may consume the seat's next real turn.

Acceptance Criteria

  • One switch actually stops desktop wake delivery on the live MC→receiver path, or the inert leaf is removed so nothing reads as a kill-switch that is not one.
  • The re-enable rationale in ai/config.mjs matches reality: either the two dead layers are restored, or the comment stops citing them as live.
  • A broadcast does not wake a seat that is dark or benched-equivalent; the readiness signal is consulted on the send path rather than only inside the disabled pulse lane.
  • Proven by a test that fails against today's behaviour — a broadcast to a dark seat currently produces a wake.

Out of Scope

  • Re-litigating the 2026-07-18 decision to re-enable wakes. Its argument stands; this is about the machinery underneath it.
  • The heartbeat lane's own merits — it stays off unless someone argues otherwise.

Decision Record impact

none expected. If the fix removes bridgeDaemonEnabled rather than rewiring it, that is a config-surface change worth an ADR note.

Related

#16510 family (deployment/topology divergence) · #16513 (a sibling case of a host-side path bypassing the served deployment).

Verification note: read from /Users/Shared/github/neomjs/neo, which is not the checkout the containers are built from. Per the standing operator sequence a merge does not update the running Agent OS without a container rebuild, so the running MC may differ from this source.