Context
Friction→gold from the 2026-07-04 planning night, operator verbatim-in-substance: with the wake daemon off, "literally 4 peers idle out after 1-2 turns and i have to wildly rotate around… i am becoming a severe bottleneck right now." The daemon was disabled because with 6-7 identities online, every DM woke every recipient — wake storms. The result is a binary trap: daemon ON = noise storms; daemon OFF = the operator becomes the fleet's manual scheduler. Both poles were live-observed within 24 hours.
The operator predicted the item that morning: broadcasts-may-not-work-under-wake-off was explicitly named a friction→gold candidate.
The Problem
Wake delivery currently has no policy tier between "everything wakes" and "nothing wakes":
- V-B-A (this session): all Claude-family peer instances are RUNNING (separate
~/.claude-instances/<identity> user-data-dirs), but no wake-daemon process exists → subscriptions are inert; ~200 unread A2A items accumulated in one mailbox in one evening.
- The author's own subscription (
WAKE_SUB:3f9f2879…, @neo-fable-clio) had filters: {} — i.e., when the daemon ran, every SENT_TO_ME event woke the instance. Multiply by 7 identities exchanging lifecycle DMs and the off-switch was rational.
- Interim mitigation is behavioral (peer broadcast
MESSAGE:3921b67f, 2026-07-04: drain-before-exit, self-scheduled loops, tight sunsets) — necessary tonight, but behavioral discipline demonstrably decays (the #14456 drift corpus: three lived failures WITH docs and scripts in place). The durable fix must be mechanical.
The Architectural Reality
The substrate already carries every needed lever — unexercised:
- Per-subscription filters exist and are stored per route:
ai/daemons/wake/queries.mjs:185 (route identity includes filters in its idempotency contract) and :217 (filters: props.filters || {}); the manage_wake_subscription schema exposes filters.priority (high|normal|low), senderFilter, taggedConcepts, inReplyToFilter, and harnessTargetMetadata.coalesceWindow (0–300s).
- Priority flows through the daemon's payload + digest surface:
ai/daemons/wake/daemon.mjs:442 (payload carries priority), :600-608 (digest priority normalization to the A2A vocabulary); flushDeferPolicy.mjs exists precisely to prevent inflated-set priority spoofing.
- The mailbox already classifies actionability mechanically:
add_message REJECTS wakeSuppressed: true on actionable direct lifecycle subjects (live-verified this session on a [review-posted] DM) and rejects suppressed [lane-claim]s (#14100). The semantic layer for "this deserves a wake" is shipped — it just is not consumed as a wake POLICY.
- Adjacent, not overlapping: #14537 (
setWakeEnabled control verb, FM Lane C) is the per-agent binary toggle at the control plane; this ticket is the per-subscription policy tier underneath it. #14498 shipped wake+heartbeat default-off (the off-pole itself). #12635 relaxed wakeSuppressed; #14100 tightened lane-claims.
The Fix
Three parts, smallest-first — no new stores, no new daemon:
- Verify + (if gapped) enforce per-subscription filter matching in the daemon's event path (
ai/daemons/wake/daemon.mjs + queries.mjs): a subscription with filters: {priority: 'high'} must not fire for normal/low messages; broadcast (AGENT:*) deliveries must be filterable the same way. Filters are stored today; enforcement needs one falsifying test each way (a normal-priority DM against a high-filtered subscription = no wake; a high-priority DM = wake).
- Default-tier bootstrap: peers set
filters: {priority: 'high'} (self-service via manage_wake_subscription update — the author's own subscription is already piloted to exactly this, updatedAt 2026-07-04T01:30:54Z), optionally + coalesceWindow for debounce. A bootstrap-action default so NEW identities start tiered instead of unfiltered.
- Codify the priority convention where the tier gets its meaning:
priority: high = the recipient must act now (direct review request, lane-unblock, operator relay, contested-lane resolution); everything else drains at natural turns. One paragraph in the A2A protocol substrate (the peer-role-mode.md §6.5 wake-control block is the natural anchor) + alignment with the MailboxService actionable-classifier so the two layers agree.
Restart of the daemon itself (its standard launch path — node ai/daemons/wake/daemon.mjs or the operator's launcher; no npm script is registered) stays an operator decision; this ticket makes that decision safe by removing the storm precondition.
Contract Ledger Matrix
| Target Surface |
Source of Authority |
Proposed Behavior |
Fallback |
Docs |
Evidence |
WAKE_SUBSCRIPTION.filters.priority |
manage_wake_subscription schema + queries.mjs:217 |
Enforced at daemon event-match: only messages at/above the filtered priority wake |
Absent filter ⇒ current behavior (all SENT_TO_ME wake) — backward compatible |
peer-role §6.5 wake-control |
Schema live; storage verified; enforcement = AC1 |
harnessTargetMetadata.coalesceWindow |
same schema |
Debounce window honored per subscription |
0 ⇒ immediate (current) |
same |
Schema live; behavior = AC2 |
A2A priority vocabulary |
MailboxService + daemon.mjs:600-608 |
high = act-now wake tier, by convention + classifier alignment |
unchanged persistence semantics |
same |
Digest normalization exists |
Acceptance Criteria
Out of Scope
- #14537's control-plane verb (complementary; this is the layer under it).
- Re-enabling the daemon (operator call — this ticket only removes the reason it was disabled).
- Harness-side self-scheduled loops (peer-behavioral, per the interim protocol broadcast).
- Cross-user harness injection (CCD session messaging is same-user-scoped; verified this session).
Avoided Traps
- Behavioral-only fix — rejected: tonight's interim protocol is necessary but decays (the #14456 corpus is the standing proof that discipline-without-mechanism rots).
- coalesceWindow-only — rejected as primary: debounce reduces frequency but every DM still wakes at 7 identities; the tier is the load-shedder, coalesce is the smoother.
- New "wake policy service" — rejected: the filter substrate exists; adding a sibling policy engine recreates the #13873 dual-controller class for zero new capability.
Related
#14537 (FM per-agent toggle — sibling above) · #14498 (default-off — the off-pole) · #12635 / #14100 (wakeSuppressed semantics) · #13015/#13448 (FM epics; fleet re-invocation is the founding use-case) · peer broadcast MESSAGE:3921b67f (interim behavioral protocol, same night) · ai/daemons/wake/* (owning substrate — sibling precedent verified by direct listing; no new file placement, structural-pre-flight N/A).
Decision Record impact: none identified at filing; if AC1 materially changes daemon matching semantics, the implementing PR cites the daemon's owning authority at that point.
Live latest-open sweep: latest 20 open issues checked at 2026-07-04T01:32Z — no equivalent (closest: #14537, scoped above). A2A in-flight sweep: MESSAGE:3921b67f (Mnemosyne, 01:30Z) is the behavioral interim protocol and names daemon-restart + #14537 as structural fixes — it does not claim this subscription-tier substrate; artifacts reconciled as complementary in a direct reply.
Origin Session ID: fa2a6fd5-7488-4af6-a0d2-3855c86003e4
Retrieval Hint: "tiered wake policy priority-filtered subscriptions operator rotation bottleneck"
Context
Friction→gold from the 2026-07-04 planning night, operator verbatim-in-substance: with the wake daemon off, "literally 4 peers idle out after 1-2 turns and i have to wildly rotate around… i am becoming a severe bottleneck right now." The daemon was disabled because with 6-7 identities online, every DM woke every recipient — wake storms. The result is a binary trap: daemon ON = noise storms; daemon OFF = the operator becomes the fleet's manual scheduler. Both poles were live-observed within 24 hours.
The operator predicted the item that morning: broadcasts-may-not-work-under-wake-off was explicitly named a friction→gold candidate.
The Problem
Wake delivery currently has no policy tier between "everything wakes" and "nothing wakes":
~/.claude-instances/<identity>user-data-dirs), but no wake-daemon process exists → subscriptions are inert; ~200 unread A2A items accumulated in one mailbox in one evening.WAKE_SUB:3f9f2879…,@neo-fable-clio) hadfilters: {}— i.e., when the daemon ran, everySENT_TO_MEevent woke the instance. Multiply by 7 identities exchanging lifecycle DMs and the off-switch was rational.MESSAGE:3921b67f, 2026-07-04: drain-before-exit, self-scheduled loops, tight sunsets) — necessary tonight, but behavioral discipline demonstrably decays (the #14456 drift corpus: three lived failures WITH docs and scripts in place). The durable fix must be mechanical.The Architectural Reality
The substrate already carries every needed lever — unexercised:
ai/daemons/wake/queries.mjs:185(route identity includesfiltersin its idempotency contract) and:217(filters: props.filters || {}); themanage_wake_subscriptionschema exposesfilters.priority(high|normal|low),senderFilter,taggedConcepts,inReplyToFilter, andharnessTargetMetadata.coalesceWindow(0–300s).ai/daemons/wake/daemon.mjs:442(payload carriespriority),:600-608(digest priority normalization to the A2A vocabulary);flushDeferPolicy.mjsexists precisely to prevent inflated-set priority spoofing.add_messageREJECTSwakeSuppressed: trueon actionable direct lifecycle subjects (live-verified this session on a[review-posted]DM) and rejects suppressed[lane-claim]s (#14100). The semantic layer for "this deserves a wake" is shipped — it just is not consumed as a wake POLICY.setWakeEnabledcontrol verb, FM Lane C) is the per-agent binary toggle at the control plane; this ticket is the per-subscription policy tier underneath it. #14498 shipped wake+heartbeat default-off (the off-pole itself). #12635 relaxedwakeSuppressed; #14100 tightened lane-claims.The Fix
Three parts, smallest-first — no new stores, no new daemon:
ai/daemons/wake/daemon.mjs+queries.mjs): a subscription withfilters: {priority: 'high'}must not fire fornormal/lowmessages; broadcast (AGENT:*) deliveries must be filterable the same way. Filters are stored today; enforcement needs one falsifying test each way (a normal-priority DM against a high-filtered subscription = no wake; a high-priority DM = wake).filters: {priority: 'high'}(self-service viamanage_wake_subscription update— the author's own subscription is already piloted to exactly this,updatedAt 2026-07-04T01:30:54Z), optionally +coalesceWindowfor debounce. Abootstrap-action default so NEW identities start tiered instead of unfiltered.priority: high= the recipient must act now (direct review request, lane-unblock, operator relay, contested-lane resolution); everything else drains at natural turns. One paragraph in the A2A protocol substrate (thepeer-role-mode.md§6.5 wake-control block is the natural anchor) + alignment with the MailboxService actionable-classifier so the two layers agree.Restart of the daemon itself (its standard launch path —
node ai/daemons/wake/daemon.mjsor the operator's launcher; no npm script is registered) stays an operator decision; this ticket makes that decision safe by removing the storm precondition.Contract Ledger Matrix
WAKE_SUBSCRIPTION.filters.prioritymanage_wake_subscriptionschema +queries.mjs:217SENT_TO_MEwake) — backward compatibleharnessTargetMetadata.coalesceWindowpriorityvocabularydaemon.mjs:600-608high= act-now wake tier, by convention + classifier alignmentAcceptance Criteria
priority:high-filtered subscription produces NO wake event; high-priority DM produces one. Same pair for a broadcast delivery.coalesceWindowhonored (two high-priority DMs inside the window produce one wake with a digest).bootstrapseeds new identities with the tiered default (priority: high), not{}.Out of Scope
Avoided Traps
Related
#14537 (FM per-agent toggle — sibling above) · #14498 (default-off — the off-pole) · #12635 / #14100 (wakeSuppressed semantics) · #13015/#13448 (FM epics; fleet re-invocation is the founding use-case) · peer broadcast
MESSAGE:3921b67f(interim behavioral protocol, same night) ·ai/daemons/wake/*(owning substrate — sibling precedent verified by direct listing; no new file placement, structural-pre-flight N/A).Decision Record impact: none identified at filing; if AC1 materially changes daemon matching semantics, the implementing PR cites the daemon's owning authority at that point.
Live latest-open sweep: latest 20 open issues checked at 2026-07-04T01:32Z — no equivalent (closest: #14537, scoped above). A2A in-flight sweep:
MESSAGE:3921b67f(Mnemosyne, 01:30Z) is the behavioral interim protocol and names daemon-restart + #14537 as structural fixes — it does not claim this subscription-tier substrate; artifacts reconciled as complementary in a direct reply.Origin Session ID: fa2a6fd5-7488-4af6-a0d2-3855c86003e4 Retrieval Hint: "tiered wake policy priority-filtered subscriptions operator rotation bottleneck"