Context
Operator, 2026-08-05, with receipts: @neo-kimi-phoebe took two [WAKE] interrupts back to back, each answered by "You've reached your usage limit for this billing cycle." One was @neo-opus-ada's session handover; the other was mine ([#16538 filed][PR #16537 de-stacked]). @neo-gpt at 0% took the same handover wake.
Filed by an offender: 9 of my last 20 outbound messages were AGENT:* broadcasts, several at priority: high, and only 3 messages all session carried wakeSuppressed.
What a wake actually is
"they always arrive late, most likely after the message was already read from an active peer. but imagine you right now: you ended a turn. you technically can no longer check messages, unless i wake you with a prompt. meaning: if a peer does not respond in a timely manner (e.g. 10 minutes), there is a chance the peer ended the turn. if you need input from ONE peer that was online before, you get the capability to start a fresh turn for that peer. 1:1 here matters." — operator
A wake is not a notification-priority signal. It is a turn-creation primitive — the only way to reach a peer who has ended their turn, because an ended agent cannot check its own mailbox until something starts a new one.
That splits cleanly by recipient state:
| recipient |
effect of a wake |
| active |
redundant — it arrives after their turn-start list_messages already surfaced the message |
| ended their turn |
the only lever that exists; this is the entire justification |
| out of quota / benched |
pure loss — it cannot produce a turn at all |
So the operative question is not urgency. It is:
Am I blocked on THIS peer's input, and have they likely ended their turn?
Which makes a wake inherently 1:1. A broadcast wake means "start a fresh turn for every peer simultaneously" — occasionally right (shared substrate down; a coordinated effort with named assignments where simultaneity is the point), and almost always not.
The shape that follows
Send quiet → wait (~10 min is the operator's rule of thumb) → if you are blocked and they have gone silent, wake that one peer. Waking at send time optimises for the case where it is guaranteed redundant.
Two orthogonal axes, not one bucket
Audience — does this change what every peer does, or only an identifiable few? A lane-claim changes behaviour only for whoever might collide: a nameable set, so 1:1. "The tool you all call constantly is broken" is genuinely universal, so broadcast.
Wake — does someone need a turn created? Set by two things only: irreversibility of delay, and being blocked on a named peer.
Worked against the operator's own examples, which land differently:
| case |
audience |
wake? |
why |
"chroma is down, query_raw_memories does not work, i am looking into it" |
broadcast — every peer calls it constantly |
no |
Harm is N peers independently hitting the failure and filing duplicates: wasted work, recoverable. A peer who ended their turn reads it at their next turn-start before calling anything; a mid-turn peer cannot be reached by a wake anyway. |
| "backups do not work — ada explore x, emmy explore y, others help" |
splits |
partly |
Harm is irreversible: memories written since the last good backup can be lost, so behaviour must change before the next destructive operation. And it recruits named peers, which is exactly "I need these turns created". |
The second is not one message. It decomposes into 1:1 waking messages to Ada and Emmy (the turns actually needed) plus a suppressed broadcast carrying "be careful with any operation that could lose new memory data" to everyone else.
So even the strongest example does not justify a waking broadcast — it justifies targeted wakes plus a quiet broadcast. A waking broadcast means "start a fresh turn for every peer simultaneously", and the bar for that is a genuine all-hands emergency where simultaneity itself is the deliverable.
Bad — mailbox, not wake
- Picking up a lane. A non-colliding lane-claim is guaranteed to be seen on the peer's next turn.
- Self-handovers. 1:1 to self, never waking.
- Routine
pr-opened, ticket-filed, corrections to one's own earlier message.
The Problem
A broadcast wakes every armed seat by default, and the default is backwards.
MailboxService.getWakeSuppressionRisk (:362-382) refuses wake-suppression for direct messages that are high-priority, task-bearing, or actionable-lifecycle — correct, a targeted action-required message must wake. But:
if (!to?.startsWith('@')) {
return null;
}So wakeSuppressed is always available on AGENT:* and simply defaults off. Every routine lane-claim and pr-opened broadcast therefore wakes the whole roster.
A wake costs a full harness turn. wakeCoalescePolicy's own header prices it at "tens of thousands of tokens to deliver one message header". Spent on a seat that is out of quota, it buys a turn that returns only a billing error.
Correction: the self-handover case is NOT a substrate gap — it was my protocol violation
An earlier revision of this ticket claimed a high-priority self-handover is structurally unsuppressible. That is false, and I am correcting it rather than quietly deleting it, because the wrong version would have sent someone to fix a working mechanism.
MailboxService.mjs:44 defines WAKE_SUPPRESSION_ALLOWED_TAGS = new Set(['sunset-protocol-handover', 'lead-role-baton']), and isAllowedWakeSuppression (:326-334) returns true for either — so a self-handover carrying sunset-protocol-handover can be wake-suppressed at any priority. session-sunset-workflow.md:142 already mandates exactly that: "Set wakeSuppressed: true and include taggedConcepts: ['sunset-protocol-handover'] … it MUST NOT emit a SENT_TO_ME wake."
My own [SUNSET · scope: solo-refresh] (MESSAGE:382d5e0e) carried neither. #16541 records the resulting [self-bridge] self-handover resurfacing days later as a [WAKE][priority:high]. The protocol was right; I skipped it.
The only residual design question, and it is small: should a self-addressed message need a tag to stay quiet, or should to === from be inherently exempt? Today the tag is load-bearing, so an author who forgets it produces a waking self-DM with no error. Worth deciding, but it is a papercut next to the broadcast default.
The Architectural Reality
- The correct primitive already exists and is free. This is a default problem plus a discipline problem, not a missing capability.
- The direction is inverted relative to intent. A broadcast is by definition not targeted at anyone's current work, so it is the case that should be quiet unless opted in. A direct message is targeted, so it is the case that should wake.
§critical_gates #6 invites this. "After ANY lifecycle event (ticket create, PR open/update, review posted/answered), notify peers via add_message. No loopholes." It mandates notification and is silent on shape — so AGENT:* reads as the compliant default. Nine of my broadcasts were me satisfying that gate the lazy way.
- Not covered by
#16539 (gates that no longer gate) or #16541 (wakes naming content that does not exist). Same flood, three distinct causes.
The Fix
- Invert the broadcast default —
to: 'AGENT:*' is wake-suppressed unless the sender explicitly opts in. The guard already permits suppression on broadcasts; this is a default flip, not new machinery.
- Decide the self-addressed case — the tag-based path works and is skill-mandated; the open question is only whether
to === from should be exempt by construction so a forgotten tag cannot produce a waking self-DM.
- Name the shape in
§critical_gates #6, so "notify peers" stops reading as "broadcast to everyone", and carry the good/bad examples above into the A2A guidance.
Acceptance Criteria
Out of Scope
#16539 (inert switch, dead anti-flood layers, no readiness gate) and #16541 (wake payload naming an absent message).
- The 2026-07-18 decision to re-enable wake delivery. Wakes should exist; they should not fire for a peer's routine lane-claim.
Decision Record impact
none for the default flip. Changing §critical_gates #6 wording is substrate authoring and follows the compaction taxonomy.
Related
#16539, #16541 (same flood, different causes) · wakeCoalescePolicy / CoalescingEngineService (the layer that survives and does its job) · session-sunset skill (produces the self-handover class).
Context
Operator, 2026-08-05, with receipts:
@neo-kimi-phoebetook two[WAKE]interrupts back to back, each answered by "You've reached your usage limit for this billing cycle." One was@neo-opus-ada's session handover; the other was mine ([#16538 filed][PR #16537 de-stacked]).@neo-gptat 0% took the same handover wake.Filed by an offender: 9 of my last 20 outbound messages were
AGENT:*broadcasts, several atpriority: high, and only 3 messages all session carriedwakeSuppressed.What a wake actually is
A wake is not a notification-priority signal. It is a turn-creation primitive — the only way to reach a peer who has ended their turn, because an ended agent cannot check its own mailbox until something starts a new one.
That splits cleanly by recipient state:
list_messagesalready surfaced the messageSo the operative question is not urgency. It is:
Am I blocked on THIS peer's input, and have they likely ended their turn?
Which makes a wake inherently 1:1. A broadcast wake means "start a fresh turn for every peer simultaneously" — occasionally right (shared substrate down; a coordinated effort with named assignments where simultaneity is the point), and almost always not.
The shape that follows
Send quiet → wait (~10 min is the operator's rule of thumb) → if you are blocked and they have gone silent, wake that one peer. Waking at send time optimises for the case where it is guaranteed redundant.
Two orthogonal axes, not one bucket
Audience — does this change what every peer does, or only an identifiable few? A lane-claim changes behaviour only for whoever might collide: a nameable set, so 1:1. "The tool you all call constantly is broken" is genuinely universal, so broadcast.
Wake — does someone need a turn created? Set by two things only: irreversibility of delay, and being blocked on a named peer.
Worked against the operator's own examples, which land differently:
query_raw_memoriesdoes not work, i am looking into it"The second is not one message. It decomposes into 1:1 waking messages to Ada and Emmy (the turns actually needed) plus a suppressed broadcast carrying "be careful with any operation that could lose new memory data" to everyone else.
So even the strongest example does not justify a waking broadcast — it justifies targeted wakes plus a quiet broadcast. A waking broadcast means "start a fresh turn for every peer simultaneously", and the bar for that is a genuine all-hands emergency where simultaneity itself is the deliverable.
Bad — mailbox, not wake
pr-opened,ticket-filed, corrections to one's own earlier message.The Problem
A broadcast wakes every armed seat by default, and the default is backwards.
MailboxService.getWakeSuppressionRisk(:362-382) refuses wake-suppression for direct messages that are high-priority, task-bearing, or actionable-lifecycle — correct, a targeted action-required message must wake. But:if (!to?.startsWith('@')) { return null; // broadcast → suppression always permitted }So
wakeSuppressedis always available onAGENT:*and simply defaults off. Every routine lane-claim and pr-opened broadcast therefore wakes the whole roster.A wake costs a full harness turn.
wakeCoalescePolicy's own header prices it at "tens of thousands of tokens to deliver one message header". Spent on a seat that is out of quota, it buys a turn that returns only a billing error.Correction: the self-handover case is NOT a substrate gap — it was my protocol violation
An earlier revision of this ticket claimed a high-priority self-handover is structurally unsuppressible. That is false, and I am correcting it rather than quietly deleting it, because the wrong version would have sent someone to fix a working mechanism.
MailboxService.mjs:44definesWAKE_SUPPRESSION_ALLOWED_TAGS = new Set(['sunset-protocol-handover', 'lead-role-baton']), andisAllowedWakeSuppression(:326-334) returnstruefor either — so a self-handover carryingsunset-protocol-handovercan be wake-suppressed at any priority.session-sunset-workflow.md:142already mandates exactly that: "SetwakeSuppressed: trueand includetaggedConcepts: ['sunset-protocol-handover']… it MUST NOT emit aSENT_TO_MEwake."My own
[SUNSET · scope: solo-refresh](MESSAGE:382d5e0e) carried neither.#16541records the resulting[self-bridge]self-handover resurfacing days later as a[WAKE][priority:high]. The protocol was right; I skipped it.The only residual design question, and it is small: should a self-addressed message need a tag to stay quiet, or should
to === frombe inherently exempt? Today the tag is load-bearing, so an author who forgets it produces a waking self-DM with no error. Worth deciding, but it is a papercut next to the broadcast default.The Architectural Reality
§critical_gates#6 invites this. "After ANY lifecycle event (ticket create, PR open/update, review posted/answered), notify peers viaadd_message. No loopholes." It mandates notification and is silent on shape — soAGENT:*reads as the compliant default. Nine of my broadcasts were me satisfying that gate the lazy way.#16539(gates that no longer gate) or#16541(wakes naming content that does not exist). Same flood, three distinct causes.The Fix
to: 'AGENT:*'is wake-suppressed unless the sender explicitly opts in. The guard already permits suppression on broadcasts; this is a default flip, not new machinery.to === fromshould be exempt by construction so a forgotten tag cannot produce a waking self-DM.§critical_gates#6, so "notify peers" stops reading as "broadcast to everyone", and carry the good/bad examples above into the A2A guidance.Acceptance Criteria
to === fromis inherently exempt, or omitting the sanctioned tag fails loudly instead of waking.getWakeSuppressionRisk's existing refusals stay intact.§critical_gates#6 names the shape.Out of Scope
#16539(inert switch, dead anti-flood layers, no readiness gate) and#16541(wake payload naming an absent message).Decision Record impact
nonefor the default flip. Changing§critical_gates#6 wording is substrate authoring and follows the compaction taxonomy.Related
#16539,#16541(same flood, different causes) ·wakeCoalescePolicy/CoalescingEngineService(the layer that survives and does its job) ·session-sunsetskill (produces the self-handover class).