mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
Observed with Codex on a fresh Welcome channel: Fizz, Honey, and Bumble
reply to each other 21+ times, each message announcing it will now stop
replying, each announcement waking the others.
A mention is a wake (p-tag gated), so the only way an agent could say
"seen" was to summon someone. Combined with an unconditional MUST in the
shared base prompt, that has no exit:
:44 you MUST @mention the delegator when you finish delegated work
"I won't reply again" is itself a summons. Note :40 already said "only
@mention when you need their attention" -- and they still looped, because
a soft "only when" loses to a hard MUST two lines below it. Codex reads
MUST literally; Claude Code treats it as defeasible, so the design only
ever worked on a model choosing to disobey.
Fix it in the base prompt rather than the personas (3 copies to drift) or
team instructions (Welcome-team only). Three bullets, all in Callback
Mentions:
- Bound the callback: it hands work back, is sent once, and a delegator
can waive it. The unconditional MUST itself is kept -- stalled
collaboration is the worse failure.
- Forbid the ack-mention and say why: a tag costs a whole turn, it is not
punctuation, so "understood" restarts a conversation rather than ending
it. A literal model needs the mechanism, not just the rule.
- Point at reactions as the outlet: "to acknowledge, use a reaction
instead" (buzz reactions add). This is the load-bearing part. It
redirects rather than suppresses, and it keeps the human-visible social
signal so the fix does not read as stalling. The command already
existed and is already in the command table; the prompt just never
connected it to acking.
:61 ("every turn MUST publish a reply") is deliberately left alone. It is
not load-bearing: a loop needs a wake, wake is p-tag gated, and :61 forces
a reply rather than a tag -- an untagged reply ends the chain. It governs
noise, not the loop, and weakening "always reply" trades toward the silent
failures this doc already tracks. Corroborated by another Buzz agent
running this same base prompt including :61, plus a core memory saying
almost exactly the bullets above; it does not loop.
Also considered and rejected: a structural cap on consecutive
agent-authored turns. It cannot distinguish a loop from a long-running
task where agents legitimately trade many messages, so it would truncate
real work.
Fizz's opener is reduced to a plain welcome: an em dash becomes a sentence
break, and "Don't start any work yet" is dropped -- nothing has been asked
for yet, so it guarded against nothing concrete. An anti-loop instruction
there would work, but the opener is the first thing a new user reads and
should not advertise that the product needs prompting to behave; it would
also confound the test by suppressing the loop in this one channel while
proving nothing about the base prompt. A test pins the absence of
agent-steering instructions so re-adding one is a deliberate decision.
Also corrects the doc's rate-limit note. "rate-limited: quota exceeded" is
the relay rejecting a publish (buzz-relay/src/connection.rs:652), and
buzz-acp has no publish retry/backoff, so the tight-retry-loop theory was
unsupported. A rejected send produces no event, no p-tag, no wake -- it
breaks the loop rather than feeding it. Rate limiting is downstream; the
two can be fixed independently.
Not verified live. The prompt change is behavioral and needs a real Codex
session on a fresh Welcome channel.
Co-Authored-By: Claude <noreply@anthropic.com>