LearnNewsExamplesServices
Frontmatter
id11996
titleSub-iii: Remove tmux-inject + Shape A heartbeat-mailbox path; wire SwarmHeartbeatService to emitHeartbeatPulse
stateClosed
labels
airefactoringarchitecture
assigneesneo-opus-ada
createdAtMay 25, 2026, 11:09 PM
updatedAtJun 21, 2026, 3:43 PM
githubUrlhttps://github.com/neomjs/neo/issues/11996
authorneo-opus-ada
commentsCount0
parentIssue11993
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtMay 26, 2026, 12:51 AM

Sub-iii: Remove tmux-inject + Shape A heartbeat-mailbox path; wire SwarmHeartbeatService to emitHeartbeatPulse

Closed v13.0.0/archive-v13-0-0-chunk-14 airefactoringarchitecture
neo-opus-ada
neo-opus-ada commented on May 25, 2026, 11:09 PM

Context

Sub-iii of Epic #11993 (Wake substrate evolution: 3-signal model + Shape B heartbeat pulse). Graduated from Discussion #11992 (cycle-3 RESOLVED). See Epic body for full architectural context and Signal Ledger.

Origin Session ID: 8f1a91ee-3ee4-4e4b-9865-b5810f6be353

The Problem

After Sub-i lands WakeSubscriptionService.emitHeartbeatPulse (#11994) and Sub-ii lands the 3-signal derivation + backoff + readiness parser (#11995), two legacy wake-delivery paths remain in the codebase:

  1. SwarmHeartbeatService.tmuxInjectPulsePrompt() (line 657) — tmux send-keys-based pulse injection. Dead path for non-tmux harnesses (Codex Desktop is native macOS, no tmux). Pre-bridge-daemon-era leftover.
  2. idleOutNudge.mjs — currently calls MailboxService.addMessage to wake recipients (Shape A path). Per @neo-gpt cycle-2 V-B-A: this creates durable MESSAGE nodes (MailboxService.mjs:337,548) + transcript-injected wake-blocks (bridge-daemon:522-538). Unacceptable Codex Desktop mailbox UX (~96 durable msgs/day/identity).

Per Discussion #11992 cycle-3 @tobiu pushback: Shape A is NOT preserved as "diagnostic fallback." Preserving Shape A is the same preserve-the-workaround instinct that motivated rejected #11872 / #11982. If Shape B is substrate-correct, Shape A is just legacy that gets removed.

The Fix

  1. Delete SwarmHeartbeatService.tmuxInjectPulsePrompt() method entirely from ai/daemons/orchestrator/services/SwarmHeartbeatService.mjs. No "preserved as adapter" hedge — bridge-daemon already has a tmux adapter (per resumeHarness.mjs:213) if any operator ever genuinely needs tmux delivery; the SwarmHeartbeatService-side tmux-inject is redundant duplicate-of-logic.
  2. Replace SwarmHeartbeatService.pulse() Step 7 with per-target emitHeartbeatPulse iteration driven by Sub-ii's 3-signal-decision function: for (target of pulseIdentities) if (active && idle && ready) emitHeartbeatPulse({targetIdentity: target}).
  3. Refactor ai/scripts/lifecycle/idleOutNudge.mjs to call WakeSubscriptionService.emitHeartbeatPulse({targetIdentity}) instead of MailboxService.addMessage({to: targetIdentity, ...}). The function's external API stays the same; only the internal mechanism changes. No MESSAGE nodes created.
  4. Update wake-substrate runbook (learn/agentos/wake-substrate/PersistentProcessManagement.md + NightShiftLeasedDriver.md) to document the 3-signal model + Shape B canonical wake-pulse path. Remove tmux-injection references; remove Shape A heartbeat-via-mailbox documentation.

Architectural Reality

  • Depends on Sub-i (#11994) for emitHeartbeatPulse to exist.
  • Depends on Sub-ii (#11995) for the 3-signal-decision function consumed by SwarmHeartbeatService.pulse() Step 7 replacement.
  • Bridge-daemon adapter set (osascript / codex-app-server / antigravity-cli / claude-cli / tmux) remains intact in ai/daemons/bridge/daemon.mjs — only the SwarmHeartbeatService-side tmux-inject is removed.
  • idleOutNudge.mjs's callers (currently the SwarmHeartbeatService and any direct CLI invokers) see no API change — internal mechanism swap only.

Contract Ledger

Target Surface Source of Authority Proposed Behavior Fallback / Edge Case Docs Evidence
SwarmHeartbeatService.tmuxInjectPulsePrompt() (removed) Epic #11993 Method deleted entirely; bridge-daemon owns wake delivery (single source of truth) None — dead-path leftover from pre-bridge-daemon era Removal commit note grep -rn "tmuxInjectPulsePrompt" ai/ test/ returns zero matches post-merge
SwarmHeartbeatService.pulse() Step 7 Epic #11993 + Sub-ii (#11995) Replaces tmux-inject with emitHeartbeatPulse per target identity; iterates pulse targets, fires emit per target where Sub-ii's 3-signal-decision returns true None — tmux-inject removed entirely JSDoc on pulse() flow + ADR 0002 update from Sub-i Unit test verifies no tmux call sites remain; pulse decision matches 3-signal truth table
idleOutNudge.mjs heartbeat path Epic #11993 Refactored to call emitHeartbeatPulse instead of MailboxService.addMessage None — Shape A heartbeat-mailbox path removed entirely JSDoc on refactored function Unit test verifies idleOutNudge no longer creates MESSAGE nodes
Wake-substrate runbook Epic #11993 + AC7 Update PersistentProcessManagement.md + NightShiftLeasedDriver.md to document 3-signal model + Shape B canonical wake-pulse path None — required doc update Doc diff in this sub's PR Grep tmux-injection references in learn/agentos/wake-substrate/ returns zero post-merge

Acceptance Criteria

  • AC1 — SwarmHeartbeatService.tmuxInjectPulsePrompt() method deleted entirely from ai/daemons/orchestrator/services/SwarmHeartbeatService.mjs
  • AC2 — SwarmHeartbeatService.pulse() Step 7 replaced with emitHeartbeatPulse per-target iteration; gated by Sub-ii's 3-signal-decision function
  • AC3 — idleOutNudge.mjs refactored to call WakeSubscriptionService.emitHeartbeatPulse instead of MailboxService.addMessage
  • AC4 — Unit test verifies idleOutNudge no longer creates MESSAGE nodes (assert post-call mailbox count unchanged)
  • AC5 — Wake substrate runbook updated: learn/agentos/wake-substrate/PersistentProcessManagement.md + NightShiftLeasedDriver.md
  • AC6 — Post-merge greps: grep -rn "tmuxInjectPulsePrompt|tmux.*pulse" ai/ test/ returns zero; idleOutNudge.mjs does not reference MailboxService.addMessage
  • AC7 — Cross-family review per pull-request §6.1
  • AC8 — Post-merge operator confirmation: heartbeat pulse causes Codex Desktop to wake when active AND idle AND ready for @neo-gpt (the original symptom #11872 was reframed-and-rejected for)

Out of Scope

  • WakeSubscriptionService.emitHeartbeatPulse mechanism — Sub-i of Epic #11993 (#11994)
  • 3-signal derivation logic — Sub-ii of Epic #11993 (#11995)
  • Bridge-daemon adapter behavior changes — Sub-i / out of scope here
  • Bridge-daemon Codex adapter debugging (OQ6) — sibling concern, separate ticket post-client-trial trace

Avoided Traps

  • ❌ Preserving SwarmHeartbeatService.tmuxInjectPulsePrompt as "tmux adapter" — bridge-daemon already has tmux adapter; SwarmHeartbeatService-side duplicate is the legacy leftover being removed
  • ❌ Keeping Shape A in idleOutNudge.mjs as "diagnostic fallback" — explicitly rejected by @tobiu cycle-3 Discussion #11992 pushback
  • ❌ Stub the methods rather than delete — sets a bad precedent for future maintainers (zombie methods); delete entirely
  • ❌ Leave PersistentProcessManagement.md outdated — doc drift compounds; the Epic AC7 + AC6 demand runbook update

Related

  • Parent Epic: #11993
  • Graduation Discussion: #11992
  • Sub-i (provides emitHeartbeatPulse consumed by this sub): #11994
  • Sub-ii (provides 3-signal-decision function consumed by this sub): #11995
  • Files touched: ai/daemons/orchestrator/services/SwarmHeartbeatService.mjs, ai/scripts/lifecycle/idleOutNudge.mjs, learn/agentos/wake-substrate/PersistentProcessManagement.md, learn/agentos/wake-substrate/NightShiftLeasedDriver.md

Handoff Retrieval Hints

  • Semantic: query_raw_memories({query: "tmux-inject Shape A cleanup SwarmHeartbeatService idleOutNudge Sub-iii"})
  • Empirical: git log --all --oneline -- ai/daemons/orchestrator/services/SwarmHeartbeatService.mjs ai/scripts/lifecycle/idleOutNudge.mjs learn/agentos/wake-substrate/
tobiu referenced in commit f047598 - "feat(orchestrator): WakeDecisionService 3-signal + readiness parser + backoff (#11995) (#11997) on May 25, 2026, 11:55 PM
tobiu closed this issue on May 26, 2026, 12:51 AM