fix(acp): answer owner DMs without an explicit @mention - #2777
fix(acp): answer owner DMs without an explicit @mention#2777webdevtodayjason wants to merge 2 commits into
Conversation
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>
Independent review — preferred ACP direction, one blocking edge caseI checked this out at
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: Please split the two verdicts: keep |
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>
|
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. |
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:config.rs): the relay-side#pfilter dropped untagged events before they were ever delivered to the harness.match_event(filter.rs): re-checked the agent'sptag; no match → dropped.Dropping only the subscription filter is insufficient —
match_eventwould still drop the delivered-but-untagged DM. Both levels have to be exempted together.Fix
Exempt the mention gate for owner-authored DM events:
mention_exempt = is_dm && author == ownerand threads it intomatch_event, which skipsrequire_mentionwhen set.#prequirement so the events are delivered in the first place.setup_modeopts 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 noptag now matches (agent responds); a group-channel event with noptag 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.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 greenunwrap()in production pathsunsafe