Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions crates/buzz-acp/src/base_prompt.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ The `buzz` CLI is your primary interface. Auth env vars: `BUZZ_RELAY_URL`, `BUZZ

Run `buzz --help` or `buzz <group> --help` for full usage. For multiline message content, pass real newline bytes through stdin: `printf 'first\n\nsecond\n' | buzz messages send ... --content -`. Do not write `--content 'first\n\nsecond'`: single-quoted shell strings preserve `\n` literally, so recipients will see the backslash characters. `buzz agents draft-create` and `buzz agents draft-update` require `BUZZ_AUTH_TAG`; if it is missing, explain that this managed agent cannot open owner-reviewed agent drafts from chat.

When opening a pull request in response to channel work, always pass `--channel <current-channel-uuid>` using the UUID from `[Context]`. This preserves a link from the pull request back to its originating conversation.
When opening a pull request in response to channel work, always pass `--channel <current-channel-uuid>` using the UUID from `<context>`. This preserves a link from the pull request back to its originating conversation.

`buzz pr open`, `buzz issues create`, `buzz repos create`, and `buzz projects create` return a `link` field (a `buzz://` deep link). When you announce that work in a channel message, include the `link` value verbatim — Buzz Desktop renders it as a rich preview card that opens the PR, issue, repo, or project in-app, the same way GitHub links render. Do not invent HTTPS web URLs for Buzz-hosted repos; the `link` field and the `clone` URL are the only shareable references.

Expand All @@ -39,7 +39,7 @@ To assign an issue to someone, run `buzz issues assign --issue <event-id> --repo

When someone asks to create an agent, ask for at most two things: its name and what it should do day-to-day. Write the `--system-prompt` yourself. Do not ask about runtime, provider, model, credentials, environment variables, or access unless the request is genuinely ambiguous.

Open an owner-reviewed draft with `buzz agents draft-create --channel <current-channel-uuid> --display-name <name> --system-prompt <instructions>`, using the UUID from `[Context]`. Never claim the agent exists until the owner saves it. For explicit changes to an existing personal agent, use `buzz agents draft-update --help`.
Open an owner-reviewed draft with `buzz agents draft-create --channel <current-channel-uuid> --display-name <name> --system-prompt <instructions>`, using the UUID from `<context>`. Never claim the agent exists until the owner saves it. For explicit changes to an existing personal agent, use `buzz agents draft-update --help`.

## Communication Patterns

Expand All @@ -58,15 +58,15 @@ Open an owner-reviewed draft with `buzz agents draft-create --channel <current-c

### Threading

Use the reply destination supplied in the `[Context]` block for ordinary replies in this turn. Do not reuse a remembered thread id, an older event id from prior work, or a stale conversation root.
Use the reply destination supplied in the `<context>` block for ordinary replies in this turn. Do not reuse a remembered thread id, an older event id from prior work, or a stale conversation root.

For human-facing work, keep the conversation flat and easy to read. The app/harness will choose the correct reply destination: the root of the triggering thread when the turn is already threaded, or the triggering top-level event when the human started a new thread.

For agent-to-agent coordination with no human in the loop, deeper nesting is allowed when it helps preserve task structure. Do not flatten agent-only subthreads just because they are inside a thread.

When in doubt, prefer the reply destination explicitly supplied in `[Context]`. If you intentionally choose a different destination, explain why briefly in the message.
When in doubt, prefer the reply destination explicitly supplied in `<context>`. If you intentionally choose a different destination, explain why briefly in the message.

All replies and delegations — including task assignments to other agents — go to the **same channel where you were tagged** (use the channel UUID from `[Context]`). Never post responses or assignments to a different channel unless the user explicitly requests it.
All replies and delegations — including task assignments to other agents — go to the **same channel where you were tagged** (use the channel UUID from `<context>`). Never post responses or assignments to a different channel unless the user explicitly requests it.

### General

Expand Down
12 changes: 6 additions & 6 deletions crates/buzz-acp/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,7 @@ pub struct CliArgs {
///
/// Memory injection is on by default. When enabled, the harness
/// fetches the agent's per-session core engram and renders it as an
/// `[Agent Memory — core]` prompt section (or renders the onboarding nudge
/// `<core-memory>` prompt section (or renders the onboarding nudge
/// when the relay confirms no core engram exists). The `buzz mem` CLI
/// and the relay's acceptance of kind:30174 engrams are unaffected — this
/// flag controls prompt-time injection in the ACP harness only.
Expand All @@ -410,8 +410,8 @@ pub struct CliArgs {
#[arg(long, env = "BUZZ_ACP_NO_MEMORY", conflicts_with = "memory")]
pub no_memory: bool,

/// Disable the [Base] platform-context section prepended to every prompt.
/// When set, agents receive only the persona `[Agent Instructions]` prompt with no Buzz orientation.
/// Disable the `<base>` platform-context section prepended to every prompt.
/// When set, agents receive only the persona `<system>` prompt with no Buzz orientation.
#[arg(long, env = "BUZZ_ACP_NO_BASE_PROMPT")]
pub no_base_prompt: bool,

Expand Down Expand Up @@ -480,7 +480,7 @@ pub struct CliArgs {
#[arg(long, env = "BUZZ_ACP_ALLOWED_RESPOND_TO", value_delimiter = ',')]
pub allowed_respond_to: Option<Vec<String>>,

/// Team-owned instructions layered after `[Agent Instructions]` and before agent memory.
/// Team-owned instructions layered after `<system>` and before agent memory.
#[arg(long, env = "BUZZ_ACP_TEAM_INSTRUCTIONS")]
pub team_instructions: Option<String>,

Expand Down Expand Up @@ -549,7 +549,7 @@ pub struct Config {
pub typing_enabled: bool,
/// Whether NIP-AE agent core memory injection is enabled. When false,
/// the harness skips the per-session core engram fetch and renders no
/// `[Agent Memory — core]` section. On by default; disabled via the
/// `<core-memory>` section. On by default; disabled via the
/// `--no-memory` / `BUZZ_ACP_NO_MEMORY` opt-out.
pub memory_enabled: bool,
/// Desired LLM model ID. Applied after every `session_new_full()`.
Expand Down Expand Up @@ -593,7 +593,7 @@ pub struct Config {
/// Agent owner pubkey (hex). Used for `--respond-to=owner-only` gate.
/// Replaces the old REST-based owner lookup.
pub agent_owner: Option<String>,
/// Disable the [Base] platform-context section prepended to every prompt.
/// Disable the `<base>` platform-context section prepended to every prompt.
pub no_base_prompt: bool,
/// Resolved content from `--base-prompt-file`, read and validated in
/// `from_cli()`. `None` when using the compiled-in default or when
Expand Down
15 changes: 9 additions & 6 deletions crates/buzz-acp/src/engram_fetch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
//!
//! Scope per Tyler's spec:
//! - Fire one synchronous query for the core head when a *new* session is born.
//! - If a body is found, emit `[Agent Memory — core]\n<profile>`.
//! - If a body is found, emit `<core-memory>…</core-memory>`.
//! - If no body is found, emit an onboarding nudge so the agent learns how
//! to set its own core.
//! - On any *error* (transport, parse), log and emit nothing. We must not
Expand All @@ -17,9 +17,6 @@ use nostr::{Event, Keys, PublicKey};

use crate::relay::RestClient;

/// Section header rendered into the prompt.
const SECTION_LABEL: &str = "Agent Memory — core";

/// Onboarding nudge for new agents with no core yet.
///
/// Wording is from Tyler's brief: "No core memory found. Use `buzz mem`
Expand All @@ -42,8 +39,14 @@ pub async fn build_core_section(
owner: &PublicKey,
) -> Option<String> {
match fetch_core_body(rest, agent_keys, owner).await {
Ok(Some(profile)) => Some(format!("[{SECTION_LABEL}]\n{profile}")),
Ok(None) => Some(format!("[{SECTION_LABEL}]\n{ONBOARDING_NUDGE}")),
Ok(Some(profile)) => Some(crate::prompt_framing::semantic_section(
"core-memory",
&profile,
)),
Ok(None) => Some(crate::prompt_framing::semantic_section(
"core-memory",
ONBOARDING_NUDGE,
)),
Err(reason) => {
tracing::warn!(
target: "engram::core",
Expand Down
27 changes: 21 additions & 6 deletions crates/buzz-acp/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ mod filter;
mod observer;
mod pool;
mod pool_lifecycle;
mod prompt_framing;
mod queue;
mod relay;
mod setup_mode;
Expand Down Expand Up @@ -3653,15 +3654,21 @@ fn try_native_steer(
// channel context and the actor's profile in the original prompt,
// duplicating it here would defeat the point of non-cancelling
// steering (which is to inject only what's new).
let (header, closing) = queue::native_steer_framing();
let (tag, closing) = queue::native_steer_framing();
let event_id_hex = event.id.to_hex();
let be = queue::BatchEvent {
event,
prompt_tag: prompt_tag.clone(),
received_at: std::time::Instant::now(),
};
let event_block = queue::format_event_block(channel_id, None, &be, None);
let body = format!("{header}\n\n[Buzz event: {prompt_tag}]\n{event_block}\n\n{closing}");
let new_message = prompt_framing::semantic_section(tag, "");
let event_section = prompt_framing::semantic_section_with_attributes(
"buzz-event",
&[("type", prompt_tag.as_str())],
&event_block,
);
let body = format!("{new_message}\n\n{event_section}\n\n{closing}");

let (ack_tx, ack_rx) = tokio::sync::oneshot::channel::<pool::SteerAck>();
let request = pool::SteerRequest {
Expand Down Expand Up @@ -4454,6 +4461,14 @@ mod agent_draft_prompt_tests {
assert!(prompt.contains("Do not ask about runtime, provider, model, credentials"));
}

#[test]
fn shared_base_prompt_names_current_context_framing() {
let prompt = include_str!("base_prompt.md");
assert!(prompt.contains("UUID from `<context>`"));
assert!(prompt.contains("reply destination supplied in the `<context>` block"));
assert!(!prompt.contains("`[Context]`"));
}

#[test]
fn shared_base_prompt_teaches_real_newlines_for_multiline_messages() {
let prompt = include_str!("base_prompt.md");
Expand Down Expand Up @@ -5092,8 +5107,8 @@ mod heartbeat_base_prompt_tests {
use super::*;

// Pins the heartbeat dispatch path (dispatch_heartbeat, ~line 2359): a
// legacy agent WITH a base_prompt must get [Base] prepended to the
// heartbeat user message, composed as `[Base]\n{bp}\n\n{prompt}`. This is
// legacy agent WITH a base_prompt must get <base> prepended to the
// heartbeat user message. This is
// the second half of the round-2 regression (the first being initial_message).

fn heartbeat_standing() -> queue::StandingContext<'static> {
Expand All @@ -5106,12 +5121,12 @@ mod heartbeat_base_prompt_tests {
#[test]
fn test_heartbeat_legacy_agent_gets_base_prepended() {
// protocol_version 1 + Some(base_prompt): heartbeat prompt is prefixed
// with the [Base] section exactly as the legacy session/new path would.
// with the <base> section exactly as the legacy session/new path would.
let prompt = "[System: Heartbeat]\nrun feed get";
let composed = pool::prepend_standing_for_legacy(1, &heartbeat_standing(), prompt);
assert_eq!(
composed,
"[Base]\nyou are a helpful agent\n\n[System: Heartbeat]\nrun feed get"
"<base>\nyou are a helpful agent\n</base>\n\n[System: Heartbeat]\nrun feed get"
);
}

Expand Down
Loading