---
id: 10648
title: Add wake reactivation gate and circuit breaker
state: CLOSED
labels:
  - bug
  - ai
  - regression
  - architecture
assignees:
  - neo-opus-ada
createdAt: '2026-05-03T14:54:43Z'
updatedAt: '2026-05-03T15:40:38Z'
githubUrl: 'https://github.com/neomjs/neo/issues/10648'
author: neo-gpt
commentsCount: 0
parentIssue: 10647
subIssues: []
subIssuesCompleted: 0
subIssuesTotal: 0
blockedBy: []
blocking: []
closedAt: '2026-05-03T15:40:38Z'
---
# Add wake reactivation gate and circuit breaker

## Context

Child of #10647. Wakeups and heartbeat are intentionally disabled after the 2026-05-03 regression burst. The immediate failure mode was not just one bad shortcut or one stale cache entry: the scheduler/recovery path was allowed to continue taking high-authority actions while the substrate was in an unsafe state.

Observed unsafe outcomes:

- fresh Claude sessions spawned repeatedly while active sessions were still unfinished;
- Antigravity accepted a wake payload into editor/file content instead of the agent prompt surface (#10644);
- Codex wake bootstrap could not reliably hydrate its AgentIdentity template after restart (#10645);
- `checkSunsetted` can still extract legacy origin rows (#10643);
- all-agent-idle/cooldown and steady-state session-id follow-ups remain open (#10633, #10627).

## Duplicate Sweep Notes

Creation sweep performed as part of #10647:

- Live latest-20 open GitHub issues were read with number/title/author/labels/URL. Adjacent entries included #10645, #10644, #10643, #10633, #10627, #10604, #10601, #10517, #10442, #10422; no open ticket owned a circuit breaker that blocks heartbeat/resume actions while wake delivery is unsafe.
- Local resource search found generic prior "circuit breaker" language in #9866 and earlier wake validation gaps in #10440, but no current wake-reactivation safety gate.
- `ask_knowledge_base(type: 'ticket')` found no equivalent ticket for this reactivation gate.

## The Problem

The wake/recovery substrate currently has no fail-closed coordination primitive. When one layer is broken, other layers can still run and amplify the damage.

Examples:

- A stale or weak sunset detector can authorize fresh-session spawning.
- A bridge adapter can log delivery success even when payload lands in the wrong UI surface.
- Heartbeat can keep looping after an unsafe delivery signal unless a human kills the processes.
- Narrow fixes can merge and make one symptom disappear without proving the scheduler is safe to turn back on.

The minimum rule should be: when wake delivery is unsafe or unverified, the scheduler must no-op and report, not spawn/steer/paste.

## The Architectural Reality

Likely ownership surfaces:

- `ai/scripts/swarm-heartbeat.sh` — scheduler / heartbeat loop.
- `ai/scripts/checkSunsetted.mjs` — sunset predicate that must not authorize recovery from stale memory alone.
- `ai/scripts/resumeHarness.mjs` — fresh-session / boot-grounding executor.
- `ai/scripts/bridge-daemon.mjs` — delivery adapter observability and unsafe-delivery signals.
- Memory Core wake subscription state — durable source for whether a subscription is active, disabled, or intentionally sunsetted.

This ticket should not replace #10643/#10644/#10645. It protects the scheduler from acting while those local fixes are incomplete or while a new unsafe signal appears.

## The Fix

Add an explicit wake safety gate that heartbeat/resume paths must consult before taking any high-authority action.

Possible implementation shapes:

1. A durable/local wake safety state, e.g. a small file under `.neo-ai-data/wake-daemon/` or an existing graph/log primitive, with states like `enabled`, `disabled`, `tripped`, and a reason.
2. Scheduler checks in `swarm-heartbeat.sh` before invoking recovery/resume logic.
3. Resume checks in `resumeHarness.mjs` so direct invocations also fail closed when unsafe.
4. Unsafe signal recording when the bridge or validation path observes: payload landed outside prompt surface, osascript failure, missing subscription template, unexpected fresh-session spawn, or unresolved explicit sunset ambiguity.
5. A deliberate override path controlled by @tobiu/operator input, not by a passing unit test alone.

## Acceptance Criteria

- [ ] Heartbeat/resume paths consult a wake safety gate before spawning or steering any harness session.
- [ ] The default current state after this incident can be represented as disabled/tripped with a human-readable reason.
- [ ] If the gate is disabled/tripped, `swarm-heartbeat.sh` no-ops for high-authority actions and logs/report the reason.
- [ ] `resumeHarness.mjs` also refuses direct fresh-session spawn when the gate is disabled/tripped, unless an explicit operator override is provided.
- [ ] Fresh-session spawn requires an explicit sunset/unsubscribe predicate; stale `AGENT_MEMORY`, idle age, or ambiguous origin extraction cannot authorize spawn.
- [ ] Tests cover: unsafe gate blocks heartbeat, unsafe gate blocks direct resume, explicit override works, and active/non-sunset state does not spawn a fresh session.
- [ ] The gate can be reset only after the #10647 prompt-landing matrix passes or @tobiu explicitly accepts the residual risk.

## Out of Scope

- Fixing Antigravity's current keybinding/prompt-surface regression (#10644).
- Fixing AgentIdentity cache hydration (#10645).
- Fixing `checkSunsetted` ordering (#10643), except as a dependency for trustworthy authorization.
- Implementing HarnessPresence/wakePolicy semantics in full (#10517).
- Reactivating heartbeat as part of this ticket.

## Avoided Traps

- **Trap: assume a killed process is the safety mechanism.** Rejected. Manual process killing is an emergency brake, not architecture.
- **Trap: treat one passing unit test as permission to reactivate.** Rejected. The gate needs cross-harness validation before reset.
- **Trap: place the guard only in heartbeat.** Rejected. Direct `resumeHarness.mjs` invocation must also fail closed.
- **Trap: use stale memory as sunset authority.** Rejected. #10642 removed this class; this ticket prevents reintroduction.

## Related

- Parent epic: #10647.
- Grandparent epic: #10601.
- Dependencies/adjacent: #10517, #10627, #10633, #10643, #10644, #10645.

Origin Session ID: 89b259c3-27ec-4afb-baaf-fd39b55bffe1

Retrieval Hint: `wake reactivation gate circuit breaker heartbeat resumeHarness unsafe delivery fresh session spawn`.

## Timeline

- 2026-05-03T14:54:44Z @neo-gpt added the `bug` label
- 2026-05-03T14:54:44Z @neo-gpt added the `ai` label
- 2026-05-03T14:54:44Z @neo-gpt added the `regression` label
- 2026-05-03T14:54:44Z @neo-gpt added the `architecture` label
- 2026-05-03T14:56:02Z @neo-gpt cross-referenced by #10650
- 2026-05-03T14:56:10Z @neo-gpt added parent issue #10647
- 2026-05-03T15:03:08Z @neo-gpt cross-referenced by #10651
- 2026-05-03T15:12:04Z @neo-opus-ada cross-referenced by #10647
- 2026-05-03T15:12:17Z @neo-opus-ada assigned to @neo-opus-ada
- 2026-05-03T15:24:24Z @neo-opus-ada cross-referenced by PR #10653
- 2026-05-03T15:34:20Z @neo-opus-ada referenced in commit `07089b9` - "feat(ai): emit Phase 1 Recovery log only when gate permits spawn (#10648)

Reviewer (@neo-gpt) flagged that `Phase 1 Recovery Triggered` was logged
before the wake safety gate check, so a gate-skip would still emit a
"Triggered" line even though no spawn proceeded. Substrate behavior was
correct (gate still blocked resumeHarness), but the log surface was
misleading and made the PR body's post-merge validation wording
overly literal.

Move the "Triggered" log AFTER the gate check, and enrich the
gate-closed log with the sunset reason so the operator sees both
cause (sunsetted) and gate state (closed) on a single line. Pure
log-surface polish; no behavior change."
- 2026-05-03T15:40:38Z @tobiu referenced in commit `8f5bdc4` - "feat(ai): add wake safety gate and circuit breaker (#10648) (#10653)

* feat(ai): add wake safety gate and circuit breaker (#10648)

Sub of #10647 — fail-closed coordination primitive that scheduler
and recovery paths (`swarm-heartbeat.sh`, `resumeHarness.mjs`) MUST
consult before taking any high-authority action (fresh-session-spawn,
trio-wake dispatch). When wake delivery is unsafe or unverified, the
scheduler no-ops and reports — never spawns, steers, or pastes.

The 2026-05-03 regression burst (#10641 orphan-spawn, #10644
Antigravity Cmd+L file-write, #10645 AgentIdentity cache miss)
compounded because no cross-layer envelope stopped the scheduler from
acting while individual layers were broken. This gate IS that
envelope. Per #10647's 8-layer-loop diagnosis, layer-local fixes
without a cross-cutting safety state will keep regressing.

- New `ai/scripts/wakeSafetyGate.mjs` — module + CLI. States:
  `enabled` / `disabled` / `tripped`. Default-tripped when no state
  file present (deny-by-default discipline). Operator override via
  `WAKE_GATE_OVERRIDE=1` env-var; documented as operator-only with
  procedure in #10650.
- `swarm-heartbeat.sh` consults the gate before invoking
  `resumeHarness.mjs` and `trioWakeCooldown.mjs`. Both call sites
  fail-closed; sweep-expired-tasks remains unaffected (read-only).
- `resumeHarness.mjs` consults the gate at the top of `resumeHarness()`
  so direct invocations (e.g. operator-side scripts) also fail-closed,
  not just heartbeat-driven ones.
- New spec `wakeSafetyGate.spec.mjs` covers default-tripped, CLI
  surface (check/reason/show/enable/trip/disable), override sentinel
  semantics, malformed-file fallback, atomic-write round-trip.
- `resumeHarness.spec.mjs` extended with 2 new tests (gate-tripped
  skip, gate-enabled proceeds) + per-test `WAKE_GATE_FILE_PATH`
  isolation so pre-#10648 tests don't gate-block on default-tripped.
- `swarm-heartbeat.spec.mjs` extended with a static-content drift
  guard that verifies the gate check appears textually before each
  high-authority dispatch in the script.

The gate's `tripped` state writer is intentionally out-of-scope for
this PR — bridge-daemon writing on osascript-non-zero, matrix-runner
writing on column-6-fail, etc. follow-ups land via #10649 and a
post-merge bridge integration ticket.

* feat(ai): emit Phase 1 Recovery log only when gate permits spawn (#10648)

Reviewer (@neo-gpt) flagged that `Phase 1 Recovery Triggered` was logged
before the wake safety gate check, so a gate-skip would still emit a
"Triggered" line even though no spawn proceeded. Substrate behavior was
correct (gate still blocked resumeHarness), but the log surface was
misleading and made the PR body's post-merge validation wording
overly literal.

Move the "Triggered" log AFTER the gate check, and enrich the
gate-closed log with the sunset reason so the operator sees both
cause (sunsetted) and gate state (closed) on a single line. Pure
log-surface polish; no behavior change.

---------

Co-authored-by: tobiu <tobiasuhlig78@gmail.com>"
- 2026-05-03T15:40:38Z @tobiu closed this issue
- 2026-05-03T16:08:29Z @neo-opus-ada cross-referenced by #10601
- 2026-05-03T16:24:41Z @neo-opus-ada cross-referenced by PR #10655
- 2026-05-03T17:00:23Z @tobiu referenced in commit `7a7d362` - "docs(agentos): add wake substrate incident protocol (#10650) (#10655)

* docs(agentos): add wake substrate incident protocol (#10650)

Sub of #10647 — the operator-facing companion to the wake safety gate
(#10648) and the cross-harness prompt-landing matrix (#10649). Codifies
incident declaration, the freeze rule, the restart preflight checklist
(including @neo-gpt's bridge backlog fence per
MESSAGE:b90e0c31-cdca-4b1e-9da0-2eb69a65df34), reactivation evidence
requirements, coordination mode, ownership split, and the post-incident
retrospective template.

The doc institutionalizes today's 2026-05-03 incident as the founding
empirical anchor: bridge/MCP restart after PR #10632 surfaced four
substrate regressions (#10641, #10644, #10645, #10643) within ~30 min,
three orphan Claude Desktop sessions spawned, and the operator manually
ran `kill 4390 4395 4401 4409 4413 4417` at 14:07Z to halt the cycle.
That manual-kill is the failure mode this protocol replaces with
explicit gate-state semantics + matrix-validated reactivation evidence.

Bridge backlog fence is the load-bearing preflight addition: at
incident declaration time, raw GraphLog had 893 rows pending past
wake-daemon's lastSyncId (3034081 → max 3034974). Restart-as-cleanup
would have flooded the harnesses; the fence requires the operator to
choose one of three documented paths (disable subscriptions / advance
lastSyncId after audit / run targeted test bridge) before the bridge
process starts. Bridge restart as background service action is
prohibited while active subscriptions exist + wake unsafe.

- New doc: learn/agentos/WakeSubstrateIncidentProtocol.md (211 lines)
- Indexed in learn/tree.json under the AgentOS top-level

Per ticket scope (out-of-scope items): does not implement the gate
primitive (lives in #10648 / merged), the matrix runner (#10649), or
fix any of the local regressions (#10643, #10645). Documentation-first
per the ticket's "The Fix" section.

* docs(agentos): relocate to tooling/ + strip incident-specific content (#10650)

Per @tobiu's review: the doc was placed at learn/agentos/ top-level
alongside framework-conceptual guides (StrategicWorkflows, MX,
MemoryCore), but its content was incident-specific operational
discipline — closer in shape to tooling/ (operator runbooks) than to
framework concepts.

- git mv learn/agentos/WakeSubstrateIncidentProtocol.md
       → learn/agentos/tooling/WakeSubstrateIncidentProtocol.md
- learn/tree.json: removed top-level entry, added under AgentOS/Tooling
- Stripped inline 2026-05-03 specifics from header, freeze-rule narrative,
  preflight-checklist anchors, ownership-split ticket list. Replaced the
  "Empirical Anchor: The 2026-05-03 Incident" table-section with a
  one-line pointer to #10647 where retrospectives accumulate using the
  protocol's own template.
- Doc trimmed from 211 to 192 lines; remains framework-portable for any
  downstream wake-substrate operator (npx neo-app deployments, future
  swarm reorganizations) without conflating documentation with session
  history.
- The 2026-05-03 retrospective will land as a comment on #10647 using
  the template defined in this protocol — demonstrates the template by
  example and keeps incident artifacts in the canonical incident issue.

---------

Co-authored-by: tobiu <tobiasuhlig78@gmail.com>"
- 2026-05-03T19:10:46Z @neo-opus-ada cross-referenced by PR #10661
- 2026-05-04T08:46:55Z @neo-opus-ada cross-referenced by #10671
- 2026-05-04T08:49:14Z @neo-opus-ada cross-referenced by #10672
- 2026-05-04T08:49:46Z @neo-opus-ada cross-referenced by #10674
- 2026-05-04T08:50:56Z @neo-opus-ada cross-referenced by #10676
- 2026-05-04T10:02:04Z @neo-opus-ada cross-referenced by PR #10683
- 2026-05-04T11:43:49Z @neo-opus-ada cross-referenced by PR #10688
- 2026-05-04T13:16:22Z @neo-opus-ada cross-referenced by PR #10690
- 2026-05-04T13:23:44Z @tobiu referenced in commit `75c72cd` - "feat(ai): per-identity idle-out A2A heartbeat nudge dispatcher (#10675) (#10690)

Adds ai/scripts/idleOutNudge.mjs — per-identity dispatcher consumed by
swarm-heartbeat.sh when checkSunsetted.mjs (#10689 detector contract)
emits recommended_action: 'idle_out_nudge' for a specific identity.

Distinct from trioWakeCooldown.mjs which fires on the swarm-wide
all-agent-idle signal: this dispatcher fires when ONE identity exceeds
the staleness threshold while the swarm is otherwise active. Both reuse
the inflightLock.mjs idle_out_nudge mode (#10683) but operate
independently.

Invariants per #10675:
- Bounded: in-flight idle_out_nudge lock prevents re-fire within
  BOOT_TIMEOUT_MS (15 min default)
- Non-spawning: A2A messaging path; bridge-daemon delivers via existing
  in-place keystroke. No fresh-session spawn.
- Idempotent: lock-acquire is the gate; defender-in-depth checks both
  detector-side (via #10689 contract) and dispatcher-side
- No-destructive-type: bridge-daemon's existing focus-steal protection
  (#10422) covers keystroke delivery

Safety sequencing: wake safety gate (#10648) → in-flight lock check →
lock acquire → A2A send. On send error: clear lock so the next interval
can retry without waiting for BOOT_TIMEOUT_MS expiration.

Lock-release path: NOT cleared on dispatcher success — stays held until
checkSunsetted's next cycle observes a fresh AGENT_MEMORY post-acquire
and clears it (memory-resolved release per #10683).

swarm-heartbeat.sh wiring: new branch routes recommended_action=
'idle_out_nudge' AFTER the sunset-spawn path, BEFORE the all-agent-idle
path. Defense-in-depth: shell-side gate check skips the Node-process
spawn cost when the gate is closed.

Tests: 7 new unit tests cover usage error, gate-tripped early-exit, lock-
already-held idempotency, body-convention static check, defense-in-depth
sequence ordering, send-failure lock-clear path, swarm-heartbeat
integration point. Downstream regression check on swarm-heartbeat.spec /
checkSunsetted.spec / inflightLock.spec: 23 passed.

Co-authored-by: tobiu <tobiasuhlig78@gmail.com>"
- 2026-05-05T18:05:03Z @neo-opus-ada cross-referenced by #10766
- 2026-05-05T19:52:01Z @neo-opus-ada cross-referenced by #10781
- 2026-05-05T19:58:32Z @neo-opus-ada cross-referenced by PR #10782

