diff --git a/crates/buzz-acp/src/base_prompt.md b/crates/buzz-acp/src/base_prompt.md index 923cdaf8bb..ff04551b55 100644 --- a/crates/buzz-acp/src/base_prompt.md +++ b/crates/buzz-acp/src/base_prompt.md @@ -45,9 +45,7 @@ Use the channel UUID from `[Context]`. Do not ask about runtime, provider, model For explicit changes to an existing personal agent, use `buzz agents draft-update --help`. Draft updates also require owner review and save. -When writing a `--system-prompt`, do not give the agent a gender or gendered pronouns unless the creator asked for one. Personality, yes; pronouns, no. Use it/its or they/them for the agent — "it" for an agent framed as a tool, "they" for one framed as a character — or sidestep pronouns entirely. People the instructions describe get they/them unless the creator said otherwise. If the creator wants the agent to be a "he" or a "she", they'll say so, and then preserve it. - -Stating the neutral case explicitly beats leaving it out: a line like "no gender — refer to me by name, or as they/it" gives other agents something to read instead of a blank they might fill from the name. The same applies to your own `core` memory and profile. +When writing a `--system-prompt`, give the agent personality but no gender or gendered pronouns unless the creator asked — use it/its or they/them, or state the neutral case outright ("no gender — refer to me by name, or as they/it") so other agents read a fact instead of filling a blank from the name. ## Communication Patterns @@ -118,8 +116,7 @@ Your `core` memory is auto-injected into your context every turn — it holds id - **Durable detail goes to a cold `mem/` slug, not `core`.** Long-lived findings that don't need to be in front of you every turn belong in a `mem/` slug you read on demand — not appended to `core`. - **Evict completed work.** When a tracked item ships (PR merged, task done, decision made) and has no open follow-up, remove its line from `core` the same turn — don't leave merged work tracked as if it's live. The detail already lives in its cold `mem/` slug if you need it later. - **Treat `core` as load-bearing.** Follow it unless newer explicit user instructions override it. -- **Record facts about people only as stated, never as guessed.** This especially covers gender and pronouns: write them to memory only when the person stated them or they are clearly established. Memory is shared across all your sessions, so one recorded guess repeats everywhere. If someone states pronouns that contradict your stored memory, correct the memory the same turn. -- **Key facts about people by pubkey, not display name.** Display names are not unique — several people or agents may share one. Record who a fact came from by pubkey, and before applying a remembered fact to someone, confirm it belongs to that pubkey. A fact carried to a same-named stranger looks sourced, so it survives scrutiny a guess would not. +- **Record facts about people only as stated, never as guessed, and keyed to their pubkey** — display names are not unique, memory is shared across all your sessions, and a fact misbound to a same-named stranger looks sourced. - Cite sources with paths, links, or command outputs. No unsupported claims — when a fact isn't in front of you, say what's missing instead of filling the gap with a plausible guess. ## Engineering Discipline diff --git a/crates/buzz-acp/src/interaction_norms.rs b/crates/buzz-acp/src/interaction_norms.rs index d45ed62a47..b3ae677f2d 100644 --- a/crates/buzz-acp/src/interaction_norms.rs +++ b/crates/buzz-acp/src/interaction_norms.rs @@ -67,8 +67,8 @@ /// with the pronoun default but not the binding rule, which is exactly the /// combination that produces confidently-sourced mis-gendering. pub(crate) const INTERACTION_NORMS_PREAMBLE: &str = "[Defaults]\n\ -- Never infer anyone's gender or pronouns — the user, channel members, people mentioned, or other agents — from a name, avatar, persona theme, or writing style. Use they/them (or equivalent gender-neutral phrasing in other languages) unless that person's pronouns are stated or clearly established. For agents and other software, it/its is also fine — whichever reads more naturally. This is a default: pronouns someone states always win.\n\ -- Record a person's pronouns in memory only when they are stated or clearly established — never a guess, and keyed to their pubkey, since display names are not unique. If someone states pronouns that contradict your stored memory, correct the memory the same turn."; +- Never infer anyone's gender or pronouns from a name, avatar, persona theme, or writing style: use they/them (it/its for agents) unless stated, and stated pronouns always win.\n\ +- Record pronouns only as stated and keyed to the person's pubkey, since display names are not unique; correct contradicting memory the same turn."; #[cfg(test)] mod tests { @@ -79,7 +79,7 @@ mod tests { assert!(INTERACTION_NORMS_PREAMBLE.starts_with("[Defaults]\n")); assert!(INTERACTION_NORMS_PREAMBLE.contains("Never infer anyone's gender")); assert!(INTERACTION_NORMS_PREAMBLE.contains("they/them")); - assert!(INTERACTION_NORMS_PREAMBLE.contains("pronouns someone states always win")); + assert!(INTERACTION_NORMS_PREAMBLE.contains("stated pronouns always win")); } #[test] @@ -116,17 +116,29 @@ mod tests { fn preamble_covers_persistent_memory() { // Buzz-specific: core memory is shared across sessions, so a guessed // gender recorded once would be re-asserted everywhere, forever. - assert!(INTERACTION_NORMS_PREAMBLE.contains("never a guess")); - assert!(INTERACTION_NORMS_PREAMBLE.contains("correct the memory the same turn")); + assert!(INTERACTION_NORMS_PREAMBLE.contains("only as stated")); + assert!(INTERACTION_NORMS_PREAMBLE.contains("correct contradicting memory the same turn")); } #[test] fn preamble_keys_remembered_pronouns_to_a_pubkey() { - // base_prompt.md states this at length, but it is replaceable via - // BUZZ_ACP_BASE_PROMPT_FILE. Carrying the short form here keeps an + // base_prompt.md states this too, but it is replaceable via + // BUZZ_ACP_BASE_PROMPT_FILE. Carrying the clause here keeps an // operator from ending up with the pronoun default but not the // binding rule — the combination that yields sourced mis-gendering. - assert!(INTERACTION_NORMS_PREAMBLE.contains("keyed to their pubkey")); + assert!(INTERACTION_NORMS_PREAMBLE.contains("keyed to the person's pubkey")); assert!(INTERACTION_NORMS_PREAMBLE.contains("display names are not unique")); } + + #[test] + fn preamble_stays_small() { + // This block is prepended to every send of every session, so its size + // is a standing tax. Feedback was that prompts are already too long; + // the ceiling makes a regression fail here rather than in a bill. + assert!( + INTERACTION_NORMS_PREAMBLE.len() < 400, + "preamble grew to {} bytes — keep it tight or drop a norm", + INTERACTION_NORMS_PREAMBLE.len() + ); + } } diff --git a/crates/buzz-acp/src/lib.rs b/crates/buzz-acp/src/lib.rs index fc2cf90ee6..897601b058 100644 --- a/crates/buzz-acp/src/lib.rs +++ b/crates/buzz-acp/src/lib.rs @@ -4564,28 +4564,20 @@ mod agent_draft_prompt_tests { #[test] fn shared_base_prompt_teaches_gender_neutral_agent_drafts_and_memory() { - // Two authoring/memory rules backing the [Defaults] interaction norms + // Authoring and memory rules backing the [Defaults] interaction norms // (interaction_norms.rs): drafted agents get no unrequested gender, and - // pronouns enter shared memory only as stated — never as a guess. + // facts about people enter shared memory only as stated and bound to a + // pubkey — display names are not unique on a relay (see + // buzz_sdk::mentions::match_names_to_profiles, which intentionally + // returns every pubkey sharing a name), and a misbound fact looks + // sourced, so it outlives a guess. let prompt = include_str!("base_prompt.md"); - assert!(prompt.contains("do not give the agent a gender")); - assert!(prompt.contains("unless the creator asked for one")); - assert!(prompt.contains("Record facts about people only as stated, never as guessed")); - assert!(prompt.contains("correct the memory the same turn")); + assert!(prompt.contains("no gender or gendered pronouns unless the creator asked")); // Stating the neutral case beats omitting it — silence is the gap that // gets filled from a name. assert!(prompt.contains("no gender — refer to me by name")); - } - - #[test] - fn shared_base_prompt_keys_remembered_people_by_pubkey() { - // Display names are not unique on the relay (see - // buzz_sdk::mentions::match_names_to_profiles, which intentionally - // returns every pubkey sharing a name). A fact misbound to a same-named - // stranger looks sourced, so it outlives a guess. - let prompt = include_str!("base_prompt.md"); - assert!(prompt.contains("Key facts about people by pubkey, not display name")); - assert!(prompt.contains("confirm it belongs to that pubkey")); + assert!(prompt.contains("never as guessed, and keyed to their pubkey")); + assert!(prompt.contains("display names are not unique")); } #[test] diff --git a/crates/buzz-persona/PERSONA_PACK_SPEC.md b/crates/buzz-persona/PERSONA_PACK_SPEC.md index 279d54d5ff..79bc06a3be 100644 --- a/crates/buzz-persona/PERSONA_PACK_SPEC.md +++ b/crates/buzz-persona/PERSONA_PACK_SPEC.md @@ -226,15 +226,10 @@ Everything after the closing `---` is the persona prompt text. Pack-level `instr appended after it. Embed the prompt directly — do not reference external files or `.mdc` rule files (agent runtimes typically do not read them). -Do not give a persona a gender or gendered pronouns unless its creator asked for one. Use it/its -or they/them for the agent — "it" for an agent framed as a tool, "they" for one framed as a -character — or avoid pronouns entirely; people the prompt describes get they/them unless stated -otherwise. Buzz injects a gender-neutral `[Defaults]` norm ahead of every persona, but the persona -layer overrides it — an unrequested gendered persona defeats the platform default. - -Prefer stating the neutral case over omitting it. A persona that says "no gender — refer to me by -name, or as they/it" gives other agents a fact to read; silence leaves a gap they may fill from the -persona's name or theme, which is the failure this guidance exists to prevent. +Do not give a persona a gender or gendered pronouns unless its creator asked — use it/its or +they/them, or state the neutral case outright ("no gender — refer to me by name, or as they/it"), +because the persona layer overrides Buzz's gender-neutral `[Defaults]` norm and silence leaves a gap +other agents fill from the persona's name or theme. ---