Context
Sub-i 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, 3-cycle convergence history, Signal Ledger, and §6.6 four required sections.
Origin Session ID: 8f1a91ee-3ee4-4e4b-9865-b5810f6be353
The Problem
The current wake substrate uses MailboxService.addMessage for heartbeat pulses (via idleOutNudge.mjs), which persists durable MESSAGE nodes and produces transcript-injected wake-blocks visible in Codex Desktop's mailbox UI. Per @neo-gpt cycle-2 V-B-A: MailboxService.mjs:337,548 + bridge-daemon :522-538 make Shape A unacceptable for production mailbox UX (~96 durable msgs/day/identity at default 15m cadence).
The substrate-correct shape is Shape B: a new ephemeral GraphLog entry that bridge-daemon's resync() picks up via a new trigger semantic (or discriminated subtype on existing eval), without persisting any MESSAGE node or SENT_TO edge that would surface in listMessages() inbox views.
The Fix
- Implement
WakeSubscriptionService.emitHeartbeatPulse({targetIdentity}) in ai/services/memory-core/WakeSubscriptionService.mjs:
- Creates an ephemeral GraphLog entry tagged as heartbeat-pulse
- Does NOT create a MESSAGE node
- Does NOT create a
SENT_TO edge that would surface in listMessages()
- Idempotent if no active bridge-daemon subscription exists for
targetIdentity (log + no-op, not throw)
- Extend bridge-daemon's
resync() consumption path (ai/daemons/bridge/daemon.mjs) to recognize the heartbeat-pulse entry — either via a new trigger semantic (e.g., HEARTBEAT_PULSE) or a discriminated subtype on existing trigger eval.
- Update ADR 0002 §6.1.6 to document the ephemeral-GraphLog-entry semantic. The existing resync contract (SENT_TO_ME / TASK_STATE_CHANGED / PERMISSION_GRANTED triggers) remains unchanged; this is an additive extension so future maintainers don't read the resync contract too narrowly.
Architectural Reality
WakeSubscriptionService.subscribe / resync / _evaluateEdgeAgainstSubscription (existing surface — ai/services/memory-core/WakeSubscriptionService.mjs) is the substrate this sub extends.
- ADR 0002 §6.1.6 (resync-based wake event semantics) is the spec being amended.
- Bridge-daemon's
resync({sinceLogId}) polling and Shape C harnessTarget routing (osascript / codex-app-server / antigravity-cli / claude-cli) remain unchanged.
- Subscription edges (
SUBSCRIBES_TO) and node properties (createdAt / updatedAt) remain unchanged.
Contract Ledger
| Target Surface |
Source of Authority |
Proposed Behavior |
Fallback / Edge Case |
Docs |
Evidence |
WakeSubscriptionService.emitHeartbeatPulse({targetIdentity}) |
Epic #11993 + Discussion #11992 cycle-3 |
Creates ephemeral GraphLog entry tagged as heartbeat-pulse; bridge-daemon resync() picks it up; no MESSAGE node, no SENT_TO edge |
Idempotent on missing subscription (log + no-op) |
New JSDoc + ADR 0002 §6.1.6 update |
Unit tests assert no MESSAGE / SENT_TO / inbox row / wake-visible artifact created |
| Bridge-daemon resync() heartbeat-pulse consumption |
Epic #11993 |
New trigger semantic OR discriminated subtype on existing eval; dispatches via existing adapter set |
None — failure to dispatch falls through to bridge-daemon's existing error-logging |
JSDoc in bridge-daemon |
Integration test: emit-pulse → bridge-daemon dispatches to appropriate harness adapter |
| ADR 0002 §6.1.6 (extension) |
Epic #11993 + @neo-gpt cycle-3 residual #2 |
Document ephemeral-GraphLog-entry semantic; existing resync contract preserved (backward-compatible additive extension) |
None — required ADR update |
ADR file edit in this sub's PR |
ADR diff present in this sub's PR; cited in Epic body |
Acceptance Criteria
Out of Scope
- 3-signal derivation logic (active/idle/ready) — Sub-ii of Epic #11993
- tmux-inject removal + Shape A heartbeat-mailbox-path cleanup — Sub-iii of Epic #11993
- New MCP tool surface — explicitly excluded by Epic #11993 (MCP tool cap constraint)
- Push-based rewrite of
WakeSubscriptionService (drop resync polling) — explicitly deferred by Discussion #11992 cycle-3
Avoided Traps
- ❌ Implementing
emitHeartbeatPulse via MailboxService.addMessage with wakeSuppressed: false — this is the rejected Shape A path; it persists MESSAGE nodes
- ❌ Creating a new graph node type for heartbeat-pulses — adds substrate cost; ephemeral GraphLog entry tagged appropriately is sufficient per Discussion #11992 cycle-3
- ❌ Updating ADR 0002 §6.1.6 in a sub other than this one — the ephemeral-entry semantic is THIS sub's contribution; future maintainers should find the ADR diff alongside the implementation
Related
- Parent Epic: #11993
- Graduation Discussion: #11992
- Wake substrate primitives consumed: WakeSubscriptionService (
subscribe / resync / _evaluateEdgeAgainstSubscription), bridge-daemon (ai/daemons/bridge/daemon.mjs Shape C consumer)
Handoff Retrieval Hints
- Semantic:
query_raw_memories({query: "emitHeartbeatPulse ephemeral GraphLog wake substrate Sub-i"})
- Empirical:
git log --all --oneline -- ai/services/memory-core/WakeSubscriptionService.mjs ai/daemons/bridge/daemon.mjs
Context
Sub-i 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, 3-cycle convergence history, Signal Ledger, and §6.6 four required sections.
Origin Session ID:
8f1a91ee-3ee4-4e4b-9865-b5810f6be353The Problem
The current wake substrate uses
MailboxService.addMessagefor heartbeat pulses (viaidleOutNudge.mjs), which persists durable MESSAGE nodes and produces transcript-injected wake-blocks visible in Codex Desktop's mailbox UI. Per @neo-gpt cycle-2 V-B-A:MailboxService.mjs:337,548+ bridge-daemon:522-538make Shape A unacceptable for production mailbox UX (~96 durable msgs/day/identity at default 15m cadence).The substrate-correct shape is Shape B: a new ephemeral GraphLog entry that bridge-daemon's
resync()picks up via a new trigger semantic (or discriminated subtype on existing eval), without persisting any MESSAGE node or SENT_TO edge that would surface inlistMessages()inbox views.The Fix
WakeSubscriptionService.emitHeartbeatPulse({targetIdentity})inai/services/memory-core/WakeSubscriptionService.mjs:SENT_TOedge that would surface inlistMessages()targetIdentity(log + no-op, not throw)resync()consumption path (ai/daemons/bridge/daemon.mjs) to recognize the heartbeat-pulse entry — either via a new trigger semantic (e.g.,HEARTBEAT_PULSE) or a discriminated subtype on existing trigger eval.Architectural Reality
WakeSubscriptionService.subscribe/resync/_evaluateEdgeAgainstSubscription(existing surface —ai/services/memory-core/WakeSubscriptionService.mjs) is the substrate this sub extends.resync({sinceLogId})polling and Shape C harnessTarget routing (osascript / codex-app-server / antigravity-cli / claude-cli) remain unchanged.SUBSCRIBES_TO) and node properties (createdAt/updatedAt) remain unchanged.Contract Ledger
WakeSubscriptionService.emitHeartbeatPulse({targetIdentity})Acceptance Criteria
WakeSubscriptionService.emitHeartbeatPulse({targetIdentity})implemented inai/services/memory-core/WakeSubscriptionService.mjsemitHeartbeatPulsefiresSENT_TOedge created whenemitHeartbeatPulsefireslistMessages()inbox query resultsresync({sinceLogId})(i.e., it IS a wake-event from the bridge-daemon's POV)ai/daemons/bridge/daemon.mjs) recognizes heartbeat-pulse entries and dispatches via existing adapter set (osascript / codex-app-server / etc.)pull-request §6.1Out of Scope
WakeSubscriptionService(drop resync polling) — explicitly deferred by Discussion #11992 cycle-3Avoided Traps
emitHeartbeatPulseviaMailboxService.addMessagewithwakeSuppressed: false— this is the rejected Shape A path; it persists MESSAGE nodesRelated
subscribe/resync/_evaluateEdgeAgainstSubscription), bridge-daemon (ai/daemons/bridge/daemon.mjsShape C consumer)Handoff Retrieval Hints
query_raw_memories({query: "emitHeartbeatPulse ephemeral GraphLog wake substrate Sub-i"})git log --all --oneline -- ai/services/memory-core/WakeSubscriptionService.mjs ai/daemons/bridge/daemon.mjs