Skip to content

fix(acp): answer owner DMs without an explicit @mention - #2777

Open
webdevtodayjason wants to merge 2 commits into
block:mainfrom
webdevtodayjason:fix/acp-dm-mention-gate
Open

fix(acp): answer owner DMs without an explicit @mention#2777
webdevtodayjason wants to merge 2 commits into
block:mainfrom
webdevtodayjason:fix/acp-dm-mention-gate

Conversation

@webdevtodayjason

Copy link
Copy Markdown

Closes #2747.

Problem

In a 1:1 DM with a managed agent, the agent ignored every message unless it was explicitly @-mentioned — even though a DM has exactly two participants and is addressed to the counterparty by definition. The result: agents look unresponsive to their own owner in DMs.

The mention gate (require_mention) was applied uniformly to every channel, DM included, at two levels:

  • Subscription filter (config.rs): the relay-side #p filter dropped untagged events before they were ever delivered to the harness.
  • match_event (filter.rs): re-checked the agent's p tag; no match → dropped.

Dropping only the subscription filter is insufficient — match_event would still drop the delivered-but-untagged DM. Both levels have to be exempted together.

Fix

Exempt the mention gate for owner-authored DM events:

  • The caller computes mention_exempt = is_dm && author == owner and threads it into match_event, which skips require_mention when set.
  • DM channels' subscription filter drops the #p requirement so the events are delivered in the first place.
  • setup_mode opts out (keeps its own mention requirement).

Why owner-scoped, not a blanket DM exemption: exempting all DM events would let two agents in a DM trigger each other with no mention, re-creating the thread-deafness loop that #2270 is about. Scoping to the owner means the human's DM messages auto-trigger the agent while agent-to-agent DM still requires an explicit mention — preserving the anti-loop invariant. Group channels are unchanged.

Safety: DM turns are already restricted to the owner and verified siblings (#2591), so this does not open agents to third-party prompting.

Tests

  • filter.rs: an owner-authored DM event with no p tag now matches (agent responds); a group-channel event with no p tag still does NOT match (gate preserved); a non-owner DM event still requires a mention.
  • config.rs: the DM subscription filter drops #p; non-DM channels keep it.
  • Full gate green: cargo test -p buzz-acp → 611 passed, cargo clippy -p buzz-acp --all-targets -- -D warnings → 0 warnings.

Sibling PR: the DM reply-anchor fix (#2748) is up alongside this. Related open issue: #2270 (thread deafness in group channels), a distinct but adjacent case.

Checklist

  • cargo test -p buzz-acp + clippy green
  • Bug fix includes regression tests
  • No new unwrap() in production paths
  • No new unsafe

In a 1:1 DM the agent required an @mention on every message: the mention
gate (require_mention) was applied uniformly to every channel, DM included,
at both the relay subscription filter (drops untagged events before delivery)
and match_event (re-checks the p-tag). A DM is addressed to the counterparty
by definition, so this made agents look unresponsive to their own owner.

Exempt the gate for owner-authored DM events — computed as is_dm && author ==
owner and threaded to match_event as mention_exempt, with the DM channels'
subscription filter dropping the #p requirement. Scoped to the owner so
agent-to-agent DM messages still require a mention, preserving the block#2270
anti-loop invariant. Group channels are unchanged. DM turns are already
restricted to owner + verified siblings (block#2591), so this does not expose
agents to third-party prompting.

Closes block#2747.

Signed-off-by: webdevtodayjason <jason@webdevtoday.com>
@svndco

svndco commented Jul 26, 2026

Copy link
Copy Markdown

Independent review — preferred ACP direction, one blocking edge case

I checked this out at 5250b55056bbee5f184e08cb91525eb3ccaebfcd and verified:

  • cargo test -p buzz-acp: 602 library + 9 integration tests passed
  • cargo clippy -p buzz-acp --all-targets -- -D warnings: passed
  • cargo fmt --all -- --check: passed

This is safer than #2763 because the in-process mention exemption is owner-only, preserving the sibling-agent anti-loop boundary.

There is one blocking inconsistency in the dynamic-channel path: is_dm_channel() deliberately returns true when metadata resolution fails (fail-closed for the author gate), but lines 1987–1988 reuse that value to remove the relay #p filter, and line 2197 reuses it to exempt an owner event from require_mention. A transient metadata failure for a newly joined non-DM channel therefore makes that channel behave like a DM for mention matching, contradicting the stated “unknown types keep the mention filter” behavior used at startup.

Please split the two verdicts: keep unknown => DM for author authorization, but require positive channel_type == "dm" metadata for mention-filter bypass. Add a regression test proving unresolved channel type retains require_mention. With that adjustment, this is the ACP PR I would keep; #2763 can close as the duplicate.

The dynamic-channel path reused the fail-closed `is_dm_channel` verdict for
two unrelated decisions. `is_dm_channel` deliberately returns true when
metadata resolution fails (fail-closed for the author authorization gate), but
that same value was reused to (a) drop the relay `#p` filter when subscribing
to a newly-joined channel and (b) exempt an owner event from `require_mention`.
A transient metadata failure on a newly-joined NON-DM channel therefore made
that channel behave like a DM for mention matching — contradicting the
startup path's "unknown types keep the mention filter" behavior.

Split the two verdicts:

- `is_dm_channel` (fail-closed, unknown => DM) is kept unchanged for the author
  authorization gate, so an unresolved channel still restricts to the owner.
- `is_dm_channel_confirmed` (fail-open, requires a positive `channel_type ==
  "dm"`) now gates the mention-filter bypass at both sites — the membership-
  notification `#p`/relay-filter removal and the `require_mention` exemption.
  Unknown/unresolved types keep the relay `#p` filter AND `require_mention`.

Both verdicts derive from a single shared `resolve_dm_verdict` resolution, so
the per-event path still resolves channel metadata only once. This mirrors the
startup subscription path, which already required a positive DM type.

Adds regression tests proving an unresolved/unknown channel type retains
`require_mention` (no mention-filter bypass) while still failing closed at the
author gate, plus a test that a positively-resolved DM still bypasses.

Addresses review on block#2777.

Signed-off-by: webdevtodayjason <jason@webdevtoday.com>
@webdevtodayjason

Copy link
Copy Markdown
Author

Good catch, thanks for taking the time to actually check it out and run the gates. You're right about the dynamic path reusing the fail-closed value. Fixed in b4292e6 (new commit, didn't force push).

I split it into two checks. is_dm_channel() stays exactly as it was, fail-closed, and only gates author auth. Added is_dm_channel_confirmed() which needs channel_type == "dm" to actually resolve, and that's what gates the #p filter removal and the require_mention exemption now. Both come from one resolve_dm_verdict() call per event so they can't drift apart. The dynamic path now matches what startup was already doing with unknown types.

Added the regression test you asked for (unresolved type keeps require_mention and stays fail-closed on auth) plus coverage for the positive DM case and a resolved non-DM.

cargo test -p buzz-acp is at 614 passing, clippy -D warnings and fmt clean.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

buzz-acp: agents ignore DMs unless explicitly @mentioned — mention gate has no DM exemption

2 participants