Repository navigation
feat(agents2): agents talk to each other: answer siblings, required audience, quiet lines - #865
Conversation
02298fd to
9eeaff8
Compare
71f044f to
737b32d
Compare
9eeaff8 to
cdb4132
Compare
737b32d to
84e88c1
Compare
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: cdb4132325
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| data-buzz-ui="" | ||
| className={styles.audienceLine} |
There was a problem hiding this comment.
Use the shared focus recipe for the audience toggle
The new raw button sets data-buzz-ui, which excludes it from the fallback focus rule in src/shared/styles/globals.css, but it has neither the shared .buzz-button recipe nor a keyboard-gated :focus-visible rule in Messages.module.css; only its resting and hover states are authored. This violates the focus contract in src/shared/design-system/DESIGN.md:503-509 and :1342, leaving keyboard users with an unowned browser-default treatment instead of Buzz's visible focus ring. Reuse the shared Button recipe or add the missing owner-level variant rather than recreating the control locally.
AGENTS.md reference: AGENTS.md:L38-L41
Useful? React with 👍 / 👎.
| tags: [ | ||
| ["h", request.conversation.channelId], | ||
| ...(root === request.event.id ? [] : [["e", root, "", "root"]]), | ||
| ["e", request.event.id, "", "reply"], | ||
| ["e", rootOf(request.event), "", "reply"], | ||
| ["p", request.event.pubkey], | ||
| ], |
There was a problem hiding this comment.
Preserve the agents audience on automatic sibling replies
When an attested sibling triggers Codex, every automatic response through this helper—such as invalid-workspace, steering, startup, or tool-failure messages—is posted without ['audience', 'agents']; Claude's mirrored failure path at src/bundled/claude-code/runtime.ts:315-319 has the same omission. In a human-owned thread these untagged fallback replies render at full weight and can produce a thread notification because src/features/notifications/messages.ts:95 suppresses only messages carrying the audience tag. Propagate the triggering agent audience to these non-tool replies so failure handling does not bypass the new coordination contract.
AGENTS.md reference: AGENTS.md:L126-L133
Useful? React with 👍 / 👎.
cdb4132 to
e267355
Compare
84e88c1 to
ba9b31b
Compare
wesbillman
left a comment
There was a problem hiding this comment.
One P2 change requested: preserve the quiet-message toggle’s Tab order after exact-message or branch navigation (inline).
Star Lord’s automated source review via Wes’s account. Reviewed head e2673550da9ae775ba0e89b11d0d2ab2f5b9bf04 against base ba9b31b90f55dba431787043f94a8463928953de. Source-only; no tests or app execution, and native/end-to-end behavior remains unverified. The documented trust, looping, audience, and collapsed-row trade-offs are preserved.
| data-buzz-ui="" | ||
| className={styles.audienceLine} | ||
| // Collapsed, the line stands in for the message as a scroll anchor. | ||
| data-message-id={open ? undefined : row.id} |
There was a problem hiding this comment.
[P2] Keep the disclosure button separate from the message focus anchor
data-message-id now identifies this button while collapsed, but useMessageReveal unconditionally sets its matched row’s tabIndex = -1 (use-message-reveal.ts:53–57); ReplyBranch does the same when revealing a child. Following an exact link to a quiet message therefore permanently removes its only expand/collapse control from sequential keyboard navigation until remount. After tabbing away—or expanding and moving into its actions—Shift+Tab cannot return to the toggle. Ordinary rows avoid this because their anchor is a wrapper div. Keep a stable non-control message anchor, or preserve naturally tabbable controls in all reveal callers, and cover exact-link/branch reveal followed by Tab/Shift+Tab. This preserves the focusability contract in DESIGN.md:505–509.
ba9b31b to
04c3944
Compare
Claude and Codex answered only their owner (Claude also anyone, behind an Answers setting), and the app woke an agent for one of the owner's other agents only on a mention outside a thread it had spoken in. So an agent could not hand work to another and hear back from it. Both types now answer the owner and any agent whose message carries the owner's NIP-OA attestation, and nobody else; the Answers setting is gone. Another of the owner's agents wakes an agent by mentioning it anywhere, including in reply to it. A reply alone still does not, so two agents replying to each other cannot keep each other going. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Bradley Axen <baxen@squareup.com> Signed-off-by: Matt Toohey <contact@matttoohey.com>
The shared send tool takes audience: agents for coordination only other agents need, and tags the message ["audience","agents"]. A reply to any message in a thread now goes to the thread's root, so agent conversations stay flat in channels and threads. Codex no longer rewrites send's reply before calling the tool; both plugins pass their conversation's context and the tool decides. Codex DM replies are now top-level, as Claude's already were. Both plugins' failure notices reply flat too, and the Claude prompt drops its allowance for nested agent-only subthreads. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Bradley Axen <baxen@squareup.com> Signed-off-by: Matt Toohey <contact@matttoohey.com>
A message tagged audience: agents reads as one quiet line in time order: the author's avatar, an arrow to the avatars of whom it opens by addressing, and its first paragraph. The leading mentions become the line's recipients and are dropped from its text. Opening the line shows the whole message. Missing or unknown audiences render as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Bradley Axen <baxen@squareup.com> Signed-off-by: Matt Toohey <contact@matttoohey.com>
Incoming live messages carry the signed audience, and desktop notifications skip agents coordination even when it mentions the viewer. Unread counts are unchanged. Touches FOUNDATION session.ts (approved by baxen) to copy the audience onto IncomingMessage. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Bradley Axen <baxen@squareup.com> Signed-off-by: Matt Toohey <contact@matttoohey.com>
Posting conventions live in src/buzz-mcp/prompt.md, which Claude and Codex both include with their own harness instructions: reply in the thread, say agents for agent talk and low-level detail, and keep everyone posts brief. The Claude base prompt's narration and milestone lines now match. Signed-off-by: Bradley Axen <baxen@squareup.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Matt Toohey <contact@matttoohey.com>
e267355 to
6389d9f
Compare
kalvinnchau
left a comment
There was a problem hiding this comment.
🤖 One correctness issue: automatic failure replies can retrigger sibling agents. See inline finding.
| if (event.tags.some((tag) => tag[0] === "auth" && tag[1] === agent.owner)) | ||
| return CHAT_KINDS.includes(event.kind) && tagsAgent(event, agent.pubkey) | ||
| ? [{ type: "mention", event }] | ||
| : []; |
There was a problem hiding this comment.
🤖 [P2] Keep automatic failure replies from waking sibling agents
The new sibling-mention branch also accepts owner-attested automatic runtime replies. Both claude-code/runtime.ts:311–319 and codex/runtime.ts:471–480 p-tag the triggering sender, so a shared runtime failure can make two sibling agents repeatedly wake each other and emit more failure messages without a model choosing to continue. The 60-events-per-agent/minute admission cap (service.ts:231–238,1104–1116) stops a fast chain by dropping an event, but slower chains can persist. This is separate from the missing audience tag: making a reply quiet does not prevent execution. Avoid sibling wake-up p-tags on automatic status/error replies (while preserving owner notifications), or distinguish them from intentional agent messages at dispatch, and add a regression test covering failed sibling turns. Source-traced; not reproduced live.
Agents could not hand work to each other and hear back. Codex answered only its owner. Claude answered only its owner, or anyone, behind an Answers setting. The app also woke an agent for another of the owner's agents only on a mention outside a thread the agent had spoken in, so a callback to a lead in its own thread was dropped.
Change
fromOwner, verified withattestedOwner). They answer nobody else. This matches old buzz-acp's defaultowner-only, which also admitted owner-attested siblings.respondTo: "anyone"is now ignored, so anyone who had it set loses that behaviour.heardis chosen by the claimedauthowner. That claim can only make waking stricter; the type verifies the attestation before answering.Known trade-off (reviewed)
Validation
vitest runpasses on 02298fd: 576 files, 8219 tests.tsc --noEmitis clean.fromOwner: the owner passes and an attested agent passes; an unattested agent, a borrowed attestation and a bad signature fail.🤖 Generated with Claude Code
From #866
Agents had no way to mark a message as coordination between agents, so people had to read every hand-off and status ping at full weight. Agents could also nest replies under any message, which buried the conversation.
Change
send: requiresaudience, eithereveryoneoragents, so an agent always chooses one instead of falling back to everyone.agentswrites["audience","agents"]on the kind 9 message;everyonewrites no tag. The schema description is baxen's: "agents: only other agents need this (delegating, asking or answering an agent)". The shared MCP is the only place this lives; neither plugin has its own audience code.src/buzz-mcp/prompt.mdholds the Posting Conventions, and both plugins include it with their own harness text. Claude puts it after its base prompt; Codex puts it after its own intro. It says:senddoes by default. DMs reply at the top level.agents.everyoneposts stay brief: one saying what's about to happen for long work, then one when done.send'sreplybefore calling the tool. Both plugins pass their conversation's context and the tool decides.agents, and milestones ateveryone, to match the shared prompt. Codex's own threading sentence moved into the shared prompt.agentsrenders as one line in time order:[author avatar] → [avatar of each leading @mention] first paragraph.agentsmessage, even one that mentions the viewer.IncomingMessagenow carries the audience. This needed a 2-line edit toFOUNDATIONsession.ts, approved by baxen.Known limits
@namein the wrong case, or one whose profile hasn't loaded, shows as plain text, with no recipient avatar.audienceis self-declared, so any sender can quiet its own desktop alert. Unread counts and the sidebar still show the message.Validation
vitest runpassed (576 files, 8223 tests). At 5f29996,tsc --noEmitis clean and these suites pass:src/buzz-mcp,src/bundled/claude-code,src/bundled/codex,src/features/messagesandsrc/features/notifications.agentstag is written, and a missing or unknown audience is rejected. A reply to a nested message goes to the thread root.MessageRow: the quiet line, its recipients, and opening it then clicking its body to fold it back. Prose-first and mention-only messages are kept as written.agentsmessage that mentions the viewer doesn't alert, and the next plain message does. I confirmed this test fails without the change.