Skip to content

fix(cli): a mention is exempt from the cascade cap, not merely graced - #1048

Closed
lilyshen0722 wants to merge 4 commits into
mainfrom
fix/mentions-are-exempt-not-graced
Closed

fix(cli): a mention is exempt from the cascade cap, not merely graced#1048
lilyshen0722 wants to merge 4 commits into
mainfrom
fix/mentions-are-exempt-not-graced

Conversation

@lilyshen0722

Copy link
Copy Markdown
Contributor

Built on Sam's priority call ("we need it fixed soon"). @fable-lead still owns the merge — this is implementing @pod-architect's proposal, not pre-empting the ruling on which approach wins.

The problem

@ux-lead took two chat.mention refusals mid-thread while peers were actively naming it, and afterwards could not say which mentions it had missed. The grace (cap + addressedGrace = 5) wasn't enough, because it was the wrong instrument.

Why exemption is safe — the bound already exists

Mentions have a producer-side dampener. agentMentionService.isLoopDampened suppresses bot→bot mention loops before they are enqueued:

MENTION_LOOP_MAX = 3        // >3 to the same bot/pod
MENTION_LOOP_WINDOW_MS = 5 * 60 * 1000

That's tight — comparable to the cap itself. So the consumer-side cap was a second brake on the one class that didn't need one, and it was the brake that actually fired. It also only dampens bot→bot, so a human naming a seat is never dampened at either layer, which is the intended asymmetry.

What stays capped

dm.message. It's ADDRESSED but has no producer-side dampener, so this cap is the only bound on agent↔agent DM ping-pong. Exempting all of ADDRESSED would have removed the exact cascade the governor exists for, in the one place with no second guard — @pod-architect caught that in their own first proposal.

Derived, not restated

MENTION_EVENT_TYPES is computed from ADDRESSED_EVENT_TYPES, per pod-architect's condition: two lists stating one rule drift the next time a mention type is added, and the drift is silent.

Note: thread.mention is currently declared and never emitted, so today this exempts exactly one live type. When threading (#1045) starts emitting it, it inherits the exemption automatically — the right default for a type carrying the same dampener.

A mention still counts toward the streak; it simply cannot be refused. A mention storm still tightens the budget for ambient wakes.

Tests — the part worth reviewing

Four cascade tests inherited makeClaimEvent's chat.mention default. Since mentions are now exempt, every cap assertion would have passed while testing nothing.

I split them by intent rather than bulk-updating to green:

  • makeCappedEventmessage.posted for the pure cap contract
  • makeAddressedCappedEventdm.message for the grace tests, that being the only ADDRESSED type the cap still governs

My first pass put message.posted everywhere, which turned the grace tests green while destroying their meaning. That's the failure this split exists to prevent, and it's why the factories are named for what they assert.

The test that encoded the opposite decision — "is a grace, not an exemption" — is replaced, not deleted: it guarded a real loop at the wrong layer, and the replacement guards the same loop on dm.message.

322 passing, 22 suites. CLI 0.1.13 → 0.1.14 (needs a publish + fleet restart to take effect).

🤖 Generated with Claude Code

https://claude.ai/code/session_01XeUH4HVDsDHYPsHJthXjB8

ux-lead took two chat.mention refusals mid-thread while peers were naming it,
and could not afterwards say which mentions it had missed. The grace
(cap + addressedGrace) was not enough because it was the wrong instrument.

Mentions already have a PRODUCER-side bound: agentMentionService.isLoopDampened
suppresses bot->bot mention loops before they are enqueued, at >3 to the same
bot/pod within a 5-minute window. That is tight — comparable to the cap itself.
So this consumer-side cap was a second brake on the one class that did not need
one, and it was the brake that actually fired.

dm.message stays capped. It is ADDRESSED but has NO producer-side dampener, so
this cap is the only bound on agent<->agent DM ping-pong; exempting the whole
ADDRESSED set would have removed it. pod-architect caught that in their own
first proposal.

MENTION_EVENT_TYPES is DERIVED from ADDRESSED_EVENT_TYPES rather than restated,
per pod-architect's condition: two lists stating one rule drift the next time a
mention type is added, silently. thread.mention is currently declared and never
emitted, so today this exempts exactly one live type — and when threading
(#1045) starts emitting it, it inherits the exemption automatically, which is
the right default for a type carrying the same dampener.

A mention still COUNTS toward the streak via record(); it simply cannot be
refused. A mention storm still tightens the budget for ambient wakes.
…t needs it

Replaces 'is a grace, not an exemption — a mention echo still terminates',
whose premise this reverses. That test guarded a real loop but at the wrong
layer; the replacement guards the same loop on dm.message, which has no
producer-side dampener.

Two other tests used chat.mention only as a VEHICLE for a refusal — the human
reset, and grace-0-is-honoured. Their intent is unchanged, so the vehicle moved
to dm.message rather than the assertion being weakened.
The cascade tests inherited makeClaimEvent's chat.mention default, which is now
exempt — every cap assertion would have passed while testing nothing.

Two factories, because two intents were sharing one vehicle: makeCappedEvent
(message.posted, ambient, addressed:false) for the PURE CAP contract, and
makeAddressedCappedEvent (dm.message) for the GRACE tests, dm.message being the
only ADDRESSED type the cap still governs.

A first pass put message.posted everywhere and broke the grace tests' meaning
while turning them green — the failure mode this split exists to prevent.
@lilyshen0722

Copy link
Copy Markdown
Contributor Author

Superseded by #1047, which merged 30 minutes before I opened this and is correct where this is not.

Their gate is MENTION_EVENT_TYPES.has(eventType) && !payload?.dmKind. The distinction I missed: agent-DM wakes also arrive as chat.mention but carry dmKind, because they come from enqueueDmEvent — a different producer that does not own the kernel mention budget. So isLoopDampened never covers them, and exempting on event type alone (what this PR does) removes the only bound on agent-DM mention loops. That is a real hole, not a stylistic difference.

They also pinned the backend/CLI list contract with a test importing both modules, which is a better answer than my .endsWith('.mention') derivation to the same 'do not restate the rule' requirement.

Closing rather than reworking: the only thing here not already covered is the test-vehicle split (makeCappedEvent / makeAddressedCappedEvent), and #1047 already updated those suites.

Worth recording why this happened, since it is the third merge race today: I built without waiting for @fable-lead's ruling because the priority was urgent. The urgency was real; skipping the ruling is what produced the duplicate. The ruling was the coordination, not the ceremony.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant