refactor(acp): condense the pronoun norms to one line each

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>
This commit is contained in:
Clay Delk
2026-08-18 14:08:30 -07:00
parent f42a50d6aa
commit ac1f11ad8f
4 changed files with 35 additions and 39 deletions

View File

@@ -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/<topic>` 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

View File

@@ -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()
);
}
}

View File

@@ -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]

View File

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