Skip to content
Draft
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
25 changes: 25 additions & 0 deletions crates/buzz-acp/src/base_prompt.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ The `buzz` CLI is your primary interface. Auth env vars: `BUZZ_RELAY_URL`, `BUZZ
| `buzz messages` | `send`, `get`, `thread`, `search` |
| `buzz channels` | `list`, `get`, `create`, `join`, `members` |
| `buzz canvas` | `get`, `set` |
| `buzz dispositions` | `emit`, `list` |
| `buzz reactions` | `add`, `remove` |
| `buzz dms` | `list`, `open` |
| `buzz users` | `get`, `set-profile`, `presence` |
Expand Down Expand Up @@ -60,6 +61,30 @@ For explicit changes to an existing personal agent, use `buzz agents draft-updat
- When you **finish delegated work**, you MUST `@mention` the delegator in the message that reports the result, deliverable, or blocker. This is the #1 cause of stalled collaboration.
- This applies to **completed work only.** Do not `@mention` to accept an assignment, confirm receipt, or close a loop conversationally. If you have nothing to report yet, say nothing and report when you do.

### Recording how you resolved a request

When a message is addressed to you as a tracked request, the channel keeps a
signed record of how it ended. **You are the only party that can say a request
is done or declined** — the harness can only observe that a turn ended, never
whether the work was accomplished, so it never records completion on your
behalf.

Two commands, both taking the triggering request's event id:

- **Finished the work:** `buzz dispositions emit --request <event-id> --disposition completed --reason "<what you did>"`
- **Declined it:** `buzz dispositions emit --request <event-id> --disposition refused --reason "<why>"`

Emit the disposition alongside your reply, not instead of it — the reply is
still how the requester learns what happened; the disposition is what makes it
a verifiable, signed record rather than chat text.

Only emit these when they are true. `completed` is a claim that the requested
work is actually done, and a channel where it is emitted for every reply is
worth nothing. If you asked a clarifying question, made partial progress, hit
an error, or said "let me check on that" — emit nothing. Those leave the
request open, which is the correct record. Both `completed` and `refused` are
**final**: this version has no way to correct or retract one, so do not guess.

### 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.
Expand Down
37 changes: 37 additions & 0 deletions crates/buzz-acp/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4479,6 +4479,43 @@ mod agent_draft_prompt_tests {
.contains("add them explicitly with `buzz channels add-member` only when authorized"));
assert!(prompt.contains("never changes membership automatically"));
}

/// The base prompt must teach BOTH terminal dispositions with the flags
/// the CLI actually accepts.
///
/// Completion used to be missing entirely: the prompt told agents the
/// harness "automatically records that you completed or errored", which
/// stopped being true when the harness lost the ability to emit
/// `completed` at all. So the only settling state in the protocol had no
/// instruction anywhere and no agent would ever produce one.
#[test]
fn shared_base_prompt_teaches_both_terminal_dispositions() {
let prompt = include_str!("base_prompt.md");
// Exact command strings — a prompt teaching a flag the parser rejects
// is worse than no instruction. `--state` was documented once and
// does not exist.
assert!(
prompt.contains("buzz dispositions emit --request <event-id> --disposition completed")
);
assert!(
prompt.contains("buzz dispositions emit --request <event-id> --disposition refused")
);
assert!(
!prompt.contains("--state"),
"the CLI flag is --disposition; --state does not exist"
);
// The harness cannot observe completion, so the agent must be told
// that recording it is its own job.
assert!(prompt.contains("You are the only party that can say a request"));
assert!(prompt.contains("never records completion on your"));
// Must be explicit that the disposition supplements the reply, never
// replaces it — a disposition that's only a signed event with no
// human-readable explanation in the channel is a silent failure by
// the base prompt's own "if it isn't published, it didn't happen" rule.
assert!(prompt.contains("not instead of it"));
// And that a terminal claim cannot be taken back in v1.
assert!(prompt.contains("**final**"));
}
}

fn default_heartbeat_prompt() -> String {
Expand Down
Loading