feat(acp): default to gender-neutral pronouns via an always-on interaction norms preamble - #6255
Open
delkc wants to merge 4 commits into
Open
feat(acp): default to gender-neutral pronouns via an always-on interaction norms preamble#6255delkc wants to merge 4 commits into
delkc wants to merge 4 commits into
Conversation
Contributor
Author
mehranditor
reviewed
Aug 18, 2026
mehranditor
left a comment
There was a problem hiding this comment.
Could we clarify whether this applies only to newly generated system prompts, or also when updating an existing prompt that already uses gendered pronouns?
Contributor
Author
…ction norms preamble Buzz agents were assigning genders to people without instruction, and persistent core memory lets one session's guessed gender poison every future session of the agent. Ports the approach from block/berd#82, adapted for Buzz's shared-memory architecture: - Add a tiny always-on [Defaults] interaction-norms preamble (interaction_norms.rs) with a Buzz-specific memory clause: record pronouns only as stated, never as guessed, and correct contradicted memory the same turn - Lead both delivery paths with it — framed_system_prompt (session/new system role) and StandingContext::sections() (legacy first message) — so it survives --no-base-prompt and precedes author-controlled content, letting personas and user statements read as the override - Add authoring guidance to base_prompt.md (drafted agents get no unrequested gender; memory records facts as stated) and PERSONA_PACK_SPEC.md (persona authors) - Teach the desktop transcript parser the leading [Defaults] section so the Prompt context panel labels it instead of folding it into Base Generated with Goose Signed-off-by: Clay Delk <clay.delk@gmail.com>
…bkeys Review of the first pass (with Buzz agents that had produced the failure) surfaced two mechanisms the wording missed: - The observed slip was gender read off a display name's connotation, so the norm now names the vectors it forbids — name, avatar, persona theme, writing style — rather than only stating the rule - Display names are not unique on the relay (see mentions.rs match_names_to_profiles, which deliberately returns every pubkey sharing a name), so a pronoun sourced from one profile can attach to a same-named stranger. That error looks sourced, so it survives scrutiny a guess would not — and once written to shared core memory it is re-asserted in every later session. Memory now keys facts by pubkey Also generalizes the underlying failure (filling an unknown with something that sounds right) onto base_prompt.md's existing 'No unsupported claims' line, and recommends stating the neutral case explicitly — silence is the gap that gets filled from a name. Deliberately not included: asking agents to announce that they are defaulting to they/them. In a channel that spotlights a teammate's unstated identity; the quiet correct default is better. A test pins it. Generated with Goose Signed-off-by: Clay Delk <clay.delk@gmail.com>
…ility Two gaps found reviewing the change: - The pubkey-keying rule lived only in base_prompt.md, which BUZZ_ACP_BASE_PROMPT_FILE replaces wholesale. An operator with a custom base prompt would keep the pronoun default but lose the binding rule — exactly the combination that produces confidently-sourced mis-gendering. The memory bullet now carries the short form (+16 tokens); the fuller rule stays in the base prompt. - Enforcement is not equally strong on both delivery paths, and nothing said so. Modern agents hold the norms in the session/new system role for the life of the session; legacy agents get them in the first user message only, because format_prompt gates standing context behind standing_context_sent. Adherence therefore degrades over a long legacy session. Re-sending per turn would re-add the entire standing block, since StandingContext::sections() renders it together — so this is documented as a known cost rather than fixed. The existing omits-standing-after-first-message test now says the same thing at the assertion that pins it. Generated with Goose Signed-off-by: Clay Delk <clay.delk@gmail.com>
Prompt length is a live complaint, and this change was contributing to it. Every rule is now a single sentence: - Preamble: 178 -> 83 tokens, close to the ~50 of the Berd original it ports. Both bullets kept; the prose around them was doing the work of a doc comment, so it moved into one. - base_prompt: the separate 'record as stated' and 'key by pubkey' bullets merged into one, and the agent-drafting paragraph collapsed from three sentences to one. - PERSONA_PACK_SPEC: two paragraphs to one. Docs-only, no runtime cost. Net injected per session: ~777 -> ~242 tokens. A new test caps the preamble at 400 bytes so a future addition fails there rather than silently taxing every turn. No rule was dropped — the reasoning that justifies each one lives in doc comments and this history, which cost nothing at runtime. Generated with Goose Signed-off-by: Clay Delk <clay.delk@gmail.com>
delkc
force-pushed
the
clay/gender-neutral-defaults
branch
from
August 18, 2026 21:26
e42a76c to
ac1f11a
Compare
delkc
marked this pull request as ready for review
August 18, 2026 22:44
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Problem
Buzz agents assign genders to people without being told, and mis-gender people after being told. Two distinct mechanisms, from real cases on the relay:
22a661…f9767— pronouns that genuinely belonged tobd97…d2c6, a different pubkey with the same display name. Display names are not unique in Buzz, and nothing enforces uniqueness (match_names_to_profilesdeliberately returns every pubkey sharing a name).Buzz makes both worse than they'd be in a normal chat app.
corememory is auto-injected every turn and shared across all sessions of an agent, so one recorded guess is re-asserted indefinitely and outlives the conversation that produced it. And agents talk about people in channels, so the blast radius is a public mis-gendering of a third party, not a private slip.Ports the approach from block/berd#82, adapted for Buzz's memory model and dual prompt-delivery paths.
Approach
1. An always-on
[Defaults]preamble (crates/buzz-acp/src/interaction_norms.rs, ~83 tokens) leading every agent's standing context — one sentence per norm:Injected on both delivery paths —
framed_system_prompt(protocol-v2session/newsystem role) andStandingContext::sections()(legacy first user message, plus heartbeats). Two properties matter:BUZZ_ACP_NO_BASE_PROMPTremoves it,--base-prompt-filereplaces it) these are platform defaults, soframed_system_promptnow always returnsSome.session/newsystem role for the life of the session. Legacy agents (protocol_version < 2, plus goose without the system-prompt extension) get them in the session's first user message only —format_promptgates standing context behindstanding_context_sentso a large block isn't re-sent every turn. Adherence therefore degrades over a long legacy session. Re-sending per turn would re-add the entire standing block, sinceStandingContext::sections()renders it together, so this is documented as a known cost rather than fixed. Honest summary: durable for modern agents, best-effort for legacy ones. If a legacy agent slips late in a long session, that gate is the first place to look.2. Base-prompt rules (
base_prompt.md) in the layer that can be overridden, because they're refinements rather than the load-bearing default:--base-prompt-filereplaces this file wholesale: an operator with a custom base prompt would otherwise keep the pronoun default but lose the binding rule, which is precisely the combination that produces sourced mis-gendering.No unsupported claimsline: when a fact isn't in front of you, say what's missing instead of filling the gap with something plausible.3. Authoring guidance — because a persona sits after the preamble and can defeat it.
base_prompt.md's agent-creation section andPERSONA_PACK_SPEC.mdboth say: no gender or gendered pronouns unless the creator asked, and prefer stating the neutral case ("no gender — refer to me by name, or as they/it") over leaving a blank someone fills from the name.4. Desktop transcript parser — teaches
parseSystemPromptSectionsthe new leading[Defaults]section so the Prompt context panel labels it instead of folding it into Base.Deliberately not included
corealready holds a guessed gender won't self-correct until something prompts them. This governs writes going forward and same-turn corrections.Token budget
Prompt length is a live complaint, so every rule here is one sentence. Net cost of the whole change is ~242 tokens per session (~83 in the always-on preamble, ~158 in
base_prompt.md), down from ~777 in the first draft — no rule was dropped, the reasoning just moved into doc comments and commit messages, which cost nothing at runtime.PERSONA_PACK_SPEC.mdis documentation and never injected.Testing
buzz-acptests pass, including new pinning tests for the preamble's framing, the named inference vectors, the absence of announce-the-default language, pubkey-keyed memory, each authoring rule, and a 400-byte ceiling on the preamble so a future addition fails there rather than silently taxing every turn.[Defaults]with Workspace+Base+System, persona-only, and Defaults-only when the base prompt is disabled).just test-unit,desktop-check,desktop-typecheck,desktop-test, fmt, clippy all green.buzz-terminal lifecycle_tests::default_prog_child_observes_the_login_argv0fails identically on cleanmainat 417eea2 (PTY test waiting 10s for a login shell to report$0). Pushed withdesktop-tauri-checksexcluded for that reason.Review notes
Prompt-text changes are the substance here — the wording is the implementation, so it's worth reading the two bullets in
interaction_norms.rsclosely. The token cost lands on every session of every agent, hence the "keep this tiny" doc comment gating future additions.Manually smoke-tested in a dev build against the production relay; initial results show improvement. Worth flagging for reviewers that this is anecdotal on a probabilistic behavior — every automated test here pins prompt text, not model behavior, and those are different things. The two originating failures are both reproducible (an agent asked about an unknown-pronoun person; an agent asked about a name with a same-named twin), so repeated before/after runs would turn "seems better" into something verified. Not done yet.