Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
7ad7d79
fix(acp): wake agents for mentions added by edits
Aug 4, 2026
914418d
fix(acp): route edit-triggered replies through originals
Aug 10, 2026
fa5a74e
test(acp): cover setup edit nudge routing
Aug 10, 2026
e72d70b
fix(acp): resolve edit routing before native steer
Aug 10, 2026
a0d4991
fix(acp): prepare edit steers off relay loop
Aug 10, 2026
531a206
fix(acp): reserve edits during steer preparation
Aug 10, 2026
5e005f6
fix(acp): discard stale prepared edit steers
Aug 10, 2026
ced3224
fix(acp): invalidate prepared edits across membership changes
Aug 10, 2026
d29968c
test(acp): cover prepared edit membership lifecycle
Aug 10, 2026
80d9ec6
fix(acp): flush prepared native steer fallback
Aug 10, 2026
f471814
test(acp): satisfy queue capacity lint
Aug 10, 2026
122acdd
fix(acp): discard recovered edit preparations
Aug 11, 2026
63bbdf6
fix(acp): gate edit steer preparation
Aug 11, 2026
70ccecd
fix(acp): release prepared edits before fallback
Aug 11, 2026
2620958
fix(acp): preserve edit routing and steer delivery
Aug 11, 2026
35edff0
fix(acp): fence native steer acknowledgement
Aug 12, 2026
80b8ddb
fix(acp): settle removed steer fences
Aug 12, 2026
75ef80b
fix(acp): invalidate steers across re-add
Aug 12, 2026
782899e
fix(acp): clear dead ownership invalidations
Aug 12, 2026
acd0a06
fix(acp): align edit target validation
Aug 12, 2026
9811b89
fix(acp): preserve edit routing after cancellation
Aug 12, 2026
2e470fd
fix(acp): fence tasks by membership generation
Aug 12, 2026
663bfd9
fix(acp): scope fallback signals to membership
Aug 12, 2026
ab8411f
fix(acp): route edit reactions to originals
Aug 13, 2026
5c7f5ac
fix(acp): preserve edit turn and failure routing
Aug 13, 2026
bfa6469
fix(acp): settle native steer lifecycle
Aug 13, 2026
5e03c2c
fix(acp): close edit steering lifecycle gaps
Aug 13, 2026
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
6 changes: 3 additions & 3 deletions crates/buzz-acp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -222,16 +222,16 @@ Start with **N=2** for most deployments. Increase if queue depth grows under loa

## Forum Channels

By default, the ACP harness subscribes to stream message kinds (9, 46010, 40007). To receive forum events, opt in with `--kinds` and disable the mention filter (forum posts don't @mention agents):
By default, the ACP harness subscribes to actionable stream kinds (9 messages, 40003 edits that add a mention, 46010 workflow approvals, and 40007 reminders). To receive forum events, opt in with `--kinds` and disable the mention filter (forum posts don't @mention agents):

**CLI flags:**
```bash
buzz-acp --kinds 9,46010,40007,45001,45002,45003 --no-mention-filter
buzz-acp --kinds 9,40003,46010,40007,45001,45002,45003 --no-mention-filter
```

**Or with `--subscribe all`:**
```bash
buzz-acp --subscribe all --kinds 9,46010,40007,45001,45002,45003
buzz-acp --subscribe all --kinds 9,40003,46010,40007,45001,45002,45003
```

**Per-channel config:**
Expand Down
67 changes: 45 additions & 22 deletions crates/buzz-acp/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
use std::collections::{HashMap, HashSet};
use std::path::PathBuf;

use buzz_core::kind::{
KIND_STREAM_MESSAGE, KIND_STREAM_MESSAGE_EDIT, KIND_STREAM_REMINDER,
KIND_WORKFLOW_APPROVAL_REQUESTED,
};
use clap::Parser;
use clap::ValueEnum;
use nostr::Keys;
Expand Down Expand Up @@ -1249,16 +1253,26 @@ pub fn load_rules(path: &std::path::Path) -> Result<Vec<SubscriptionRule>, Confi
Ok(config.rules)
}

/// Event kinds that carry actionable direct mentions by default.
///
/// Message edits are included because Desktop emits `p` tags only for
/// recipients newly added by an edit. Receiving kind 40003 therefore wakes an
/// agent once for a newly added mention without re-waking it for ordinary edits.
pub(crate) fn default_mention_kinds() -> Vec<u32> {
vec![
KIND_STREAM_MESSAGE,
KIND_STREAM_MESSAGE_EDIT,
Comment thread
loganj marked this conversation as resolved.
Comment thread
loganj marked this conversation as resolved.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Route edit-triggered typing to the original thread

When this newly subscribed edit targets a threaded reply and starts a normal turn, dispatch_pending still derives typing_scope by calling parse_thread_tags on the raw kind:40003 event. Its bare e target is intentionally ignored there, so build_typing_event emits an h-only indicator and Desktop records it at channel scope rather than in the original thread. Resolve the edit target for the typing scope, or update the scope after edit enrichment, so users viewing the thread can see that the agent is responding.

Useful? React with 👍 / 👎.

KIND_WORKFLOW_APPROVAL_REQUESTED,
KIND_STREAM_REMINDER,
]
}

/// Resolve per-channel NIP-01 filters from config + discovered channels.
pub fn resolve_channel_filters(
config: &Config,
discovered_channels: &[Uuid],
rules: &[SubscriptionRule],
) -> HashMap<Uuid, ChannelFilter> {
use buzz_core::kind::{
KIND_STREAM_MESSAGE, KIND_STREAM_REMINDER, KIND_WORKFLOW_APPROVAL_REQUESTED,
};

let target_channels: Vec<Uuid> = if let Some(ref overrides) = config.channels_override {
overrides
.iter()
Expand All @@ -1273,13 +1287,10 @@ pub fn resolve_channel_filters(

match config.subscribe_mode {
SubscribeMode::Mentions => {
let kinds = config.kinds_override.clone().unwrap_or_else(|| {
vec![
KIND_STREAM_MESSAGE,
KIND_WORKFLOW_APPROVAL_REQUESTED,
KIND_STREAM_REMINDER,
]
});
let kinds = config
.kinds_override
.clone()
.unwrap_or_else(default_mention_kinds);
let require_mention = !config.no_mention_filter;
for ch in &target_channels {
result.insert(
Expand Down Expand Up @@ -1357,10 +1368,6 @@ pub fn resolve_dynamic_channel_filter(
channel_id: Uuid,
rules: &[crate::filter::SubscriptionRule],
) -> Option<ChannelFilter> {
use buzz_core::kind::{
KIND_STREAM_MESSAGE, KIND_STREAM_REMINDER, KIND_WORKFLOW_APPROVAL_REQUESTED,
};

// In Mentions/All mode, if the operator explicitly constrained channels
// with --channels, only allow dynamic subscription to channels in that
// allowlist. Config mode ignores --channels (per CLI contract) and uses
Expand All @@ -1378,13 +1385,12 @@ pub fn resolve_dynamic_channel_filter(

match config.subscribe_mode {
SubscribeMode::Mentions => Some(ChannelFilter {
kinds: Some(config.kinds_override.clone().unwrap_or_else(|| {
vec![
KIND_STREAM_MESSAGE,
KIND_WORKFLOW_APPROVAL_REQUESTED,
KIND_STREAM_REMINDER,
]
})),
kinds: Some(
config
.kinds_override
.clone()
.unwrap_or_else(default_mention_kinds),
),
require_mention: !config.no_mention_filter,
}),
SubscribeMode::All => Some(ChannelFilter {
Expand Down Expand Up @@ -1529,11 +1535,28 @@ mod tests {
assert!(f.require_mention, "mentions mode requires mention");
let kinds = f.kinds.as_ref().expect("should have kinds");
assert!(kinds.contains(&buzz_core::kind::KIND_STREAM_MESSAGE));
assert!(kinds.contains(&buzz_core::kind::KIND_STREAM_MESSAGE_EDIT));
assert!(kinds.contains(&buzz_core::kind::KIND_WORKFLOW_APPROVAL_REQUESTED));
assert!(kinds.contains(&buzz_core::kind::KIND_STREAM_REMINDER));
}
}

#[test]
fn test_dynamic_mentions_mode_includes_message_edits() {
let config = test_config(SubscribeMode::Mentions);
let filter = resolve_dynamic_channel_filter(&config, Uuid::new_v4(), &[])
.expect("dynamic channel should be subscribed");

assert!(filter.require_mention);
assert!(
filter
.kinds
.expect("mentions mode should constrain kinds")
.contains(&KIND_STREAM_MESSAGE_EDIT),
"newly mentioned agents must receive message edits on dynamic channels"
);
}

#[test]
fn test_mentions_mode_custom_kinds() {
let mut config = test_config(SubscribeMode::Mentions);
Expand Down
Loading
Loading