Skip to content

feat(agents2): agents talk to each other: answer siblings, required audience, quiet lines - #865

Merged
matt2e merged 5 commits into
mainfrom
baxen/agent-siblings
Oct 12, 2026
Merged

matt2e merged 5 commits into
mainfrom
baxen/agent-siblings

Conversation

@baxen

@baxen baxen commented Oct 11, 2026 •

Copy link
Copy Markdown
Collaborator

Merge stack, bottom to top: #838 → #855 → #856 → #858 → #859 → #860 → #865 → #867 → #871. Each PR targets the one before it. Earlier stack notes here are superseded.

Folded in: #866 (closed). Their descriptions are below.

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

  • Claude and Codex answer the owner, plus any agent whose message carries the owner's NIP-OA attestation (fromOwner, verified with attestedOwner). They answer nobody else. This matches old buzz-acp's default owner-only, which also admitted owner-attested siblings.
  • Claude's Answers setting is removed. A saved respondTo: "anyone" is now ignored, so anyone who had it set loses that behaviour.
  • Another of the owner's agents now wakes an agent by mentioning it anywhere, including in reply to the agent's own message.
    • A reply without a mention still does not wake it, so two agents replying to each other can't keep each other going.
    • Their events still never match the agent's watches.
    • The sibling branch in heard is chosen by the claimed auth owner. That claim can only make waking stricter; the type verifies the attestation before answering.
  • Unchanged: model and effort picks (owner only), timers, DMs.

Known trade-off (reviewed)

  • Any agent the owner has attested qualifies, including agents hosted elsewhere. Agent attestations carry no conditions, so they don't expire, and nothing revokes them when an agent is removed. A removed agent's key, or an owner agent that has been prompt-injected, can still ask Claude to run commands. This is the same trust model buzz-acp used.
  • Two agents that keep mentioning each other can still loop. The backstop is the existing 60-runs-per-minute limit per agent.

Validation

  • The full vitest run passes on 02298fd: 576 files, 8219 tests. tsc --noEmit is clean.
  • New tests:
    • service: a sibling mention in reply to the agent wakes it; a bare reply or a watch match does not.
    • fromOwner: the owner passes and an attested agent passes; an unattested agent, a borrowed attestation and a bad signature fail.
    • Claude runtime: a stranger is ignored, with or without the attestation; an attested sibling is answered.
    • Codex runtime: an attested sibling is answered.
  • Deferred: the live app check of one agent mentioning another, and the human test.

🤖 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

  • MCP send: requires audience, either everyone or agents, so an agent always chooses one instead of falling back to everyone. agents writes ["audience","agents"] on the kind 9 message; everyone writes 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.
  • Shared Buzz prompt: src/buzz-mcp/prompt.md holds 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:
    • Reply in the thread at its top level, which is what send does by default. DMs reply at the top level.
    • Talking to agents and low-level detail always go to agents.
    • everyone posts stay brief: one saying what's about to happen for long work, then one when done.
  • Flat replies: a reply to any message in a thread goes to the thread's root, so agents post top-level or in a thread, never deeper.
  • Plugin drift removed: Codex no longer rewrites send's reply before calling the tool. Both plugins pass their conversation's context and the tool decides.
    • Behaviour change: Codex replies in DMs are now top-level, as Claude's already were, instead of starting a thread under the DM message.
    • Both plugins' failure notices reply flat to the thread root.
    • The Claude base prompt drops its allowance for nested agent-only subthreads. Its "narrate as you go" line now points narration at agents, and milestones at everyone, to match the shared prompt. Codex's own threading sentence moved into the shared prompt.
  • Quiet rows: a message tagged agents renders as one line in time order: [author avatar] → [avatar of each leading @mention] first paragraph.
    • The leading mentions become the line's recipients and are removed from its text.
    • Clicking the line opens the whole message in place, at the line's caption size and colour, starting where the preview was and without the leading mentions. The whole entry stays highlighted while open. A click anywhere in it folds it back, except on links and controls or when selecting text.
    • A message that opens with prose, or is nothing but a mention, shows as written. A missing or unknown audience renders as before.
  • Notifications: desktop never alerts for an agents message, even one that mentions the viewer. IncomingMessage now carries the audience. This needed a 2-line edit to FOUNDATION session.ts, approved by baxen.
  • Not changed: unread counts (server-side work is in flight), mobile push, relay enforcement.

Known limits

  • Recipient avatars come from the same bound mentions the message body uses: exact profile or agent-library names. A leading @name in the wrong case, or one whose profile hasn't loaded, shows as plain text, with no recipient avatar.
  • The collapsed line doesn't show reply counts, reactions or retry. Those appear once it's opened.
  • The preview is the first paragraph as raw text, the same as link previews.
  • audience is self-declared, so any sender can quiet its own desktop alert. Unread counts and the sidebar still show the message.

Validation

  • At f6a1dd5 the full vitest run passed (576 files, 8223 tests). At 5f29996, tsc --noEmit is clean and these suites pass: src/buzz-mcp, src/bundled/claude-code, src/bundled/codex, src/features/messages and src/features/notifications.
  • New tests:
    • MCP: the agents tag is written, and a missing or unknown audience is rejected. A reply to a nested message goes to the thread root.
    • Both plugins' prompts include the shared Buzz prompt.
    • 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.
    • Address helper: seam shifting, and line breaks kept after a leading mention.
    • Notifications: an agents message that mentions the viewer doesn't alert, and the next plain message does. I confirmed this test fails without the change.
  • I checked rendering in headless Chromium against the prototype's sample conversation, collapsed and open, in light and dark at wide and narrow widths. There I checked that a click on the open body folds it and that a drag-selection doesn't.
  • Pre-push hooks pass on 5f29996: types and unit tests, design system, rust-clippy, org check.
  • An independent agent review found a timestamp regression, now fixed. Its other findings are listed under known limits.
  • Deferred: the native app run, the live agent-to-agent flow, and the human test.

@baxen
baxen changed the base branch from baxen/codex-final-ends-turn to baxen/agent-working-reactions October 11, 2026 22:57
@baxen
baxen force-pushed the baxen/agent-siblings branch from 02298fd to 9eeaff8 Compare October 11, 2026 23:11
@baxen
baxen force-pushed the baxen/agent-working-reactions branch from 71f044f to 737b32d Compare October 11, 2026 23:11
@baxen baxen changed the title feat(agents2): answer the owner's other agents, wherever they mention feat(agents2): agents talk to each other: answer siblings, required audience, quiet lines Oct 11, 2026
@baxen
baxen force-pushed the baxen/agent-siblings branch from 9eeaff8 to cdb4132 Compare October 11, 2026 23:55
@baxen
baxen force-pushed the baxen/agent-working-reactions branch from 737b32d to 84e88c1 Compare October 11, 2026 23:55
@baxen
baxen marked this pull request as ready for review October 11, 2026 23:56
@baxen
baxen requested a review from a team as a code owner October 11, 2026 23:56
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 11, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-12T00:03:28.602795Z cdb4132 Draft marked ready
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment on lines +988 to +989
data-buzz-ui=""
className={styles.audienceLine}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Comment on lines 475 to 479
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],
],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

@baxen
baxen force-pushed the baxen/agent-siblings branch from cdb4132 to e267355 Compare October 12, 2026 00:12
@baxen
baxen force-pushed the baxen/agent-working-reactions branch from 84e88c1 to ba9b31b Compare October 12, 2026 00:12

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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.

@matt2e
matt2e force-pushed the baxen/agent-working-reactions branch from ba9b31b to 04c3944 Compare October 12, 2026 05:51
@matt2e
matt2e requested a review from a team as a code owner October 12, 2026 05:51
Base automatically changed from baxen/agent-working-reactions to main October 12, 2026 07:21
baxen and others added 4 commits October 12, 2026 18:23
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>
@matt2e
matt2e force-pushed the baxen/agent-siblings branch from e267355 to 6389d9f Compare October 12, 2026 07:25

@kalvinnchau kalvinnchau left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 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 }]
: [];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 [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.

@matt2e
matt2e merged commit 7400d68 into main Oct 12, 2026
77 of 83 checks passed
@matt2e
matt2e deleted the baxen/agent-siblings branch October 12, 2026 10:04
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.

4 participants