fix(acp): follow threads the agent participates in (#2270)#2375
fix(acp): follow threads the agent participates in (#2270)#2375brocoppler wants to merge 2 commits into
Conversation
8173991 to
930c0ef
Compare
|
Blocking evidence against current head Control-flow evidenceThe current head does these three things in sequence:
Once sibling agents A and B have both participated in one thread, an unmentioned reply from B can therefore wake A. A's reply can wake B through the same rule. That is the agent-to-agent loop #2270 explicitly says must remain impossible. This is not covered by the new tests. They prove that a generic unmentioned event in a followed root is admitted, but do not exercise author identity or two participating agents. Required correctionPlease keep explicit agent mentions working, but reject agent-authored, unmentioned events that are admitted only because their root is followed. If the proposed Regression coverage should include:
The PR description also states that this has not been exercised against a live relay. Please add the end-to-end relay case before treating #2270/#2332 as resolved: mention agent → agent replies → human sends an untagged thread reply → harness receives exactly one new turn. Separate timing evidence for successful explicit mentions is now tracked in #2386; this comment is about correctness and loop safety, not model latency. |
Review of block#2375 (BradGroux) identified an agent-to-agent loop the initial thread-following change permitted: ignore_self blocks self-loops and the author gate blocks strangers, but same-owner sibling agents pass the gate by design — so two siblings participating in one thread could wake each other with unmentioned replies indefinitely, each turn refreshing the follow TTL. Followed-thread admissions are now author-aware: - match_event reports the admission basis (admitted_via_follow) so the caller can distinguish follow-bypass admissions from mention matches. - A follow-only admission from an agent author is suppressed under the new --follow-thread-authors policy (BUZZ_ACP_FOLLOW_THREAD_AUTHORS): humans (default) admits human authors only; all opts into agent authors and accepts the auto-continuation risk. Explicit mentions are never affected, and suppression happens before the TTL refresh so agent chatter cannot keep a thread alive. - Agenthood uses the existing NIP-OA heuristic (pool::profile_event_is_agent, now pub(crate)) via a cached kind:0 lookup that runs only on the rare follow-admitted path; unknown identities classify as human (fail open, matching turn_is_human_facing). The security gate remains author_allowed. Regression coverage per review: human unmentioned reply admitted; agent-authored unmentioned reply dropped (covers siblings and external agents under respond-to anyone); mentioned agent-authored replies unaffected; all-policy opt-in; and the two-agent auto-continuation scenario cannot start from either side. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Broc Oppler <brocoppler@gmail.com>
|
The loop was real:
Regression coverage per your list: human unmentioned reply in a followed root admitted; agent-authored unmentioned reply dropped (siblings and external-under- Live-relay run (local relay, this branch, Claude Code behind
Observed turn latency (~15–30s) matches #2386. The PR description's "not yet exercised against a live relay" line is updated. Can add these scenarios as a |
A mention-gated agent (SubscribeMode::Mentions, the default) only ever received events carrying its #p tag, at two independent gates: the relay REQ filter and match_event's re-check. Once the agent replied in a thread, untagged follow-ups in that same thread were never delivered — the agent went deaf mid-conversation unless re-mentioned on every message. This records thread participation at dispatch time and opens both gates for followed threads only: - thread_follow.rs: per-channel set of participated NIP-10 thread roots with a sliding inactivity TTL (default 24h, --thread-follow-ttl-secs), a per-channel cap (64, stalest evicted), and dirty-channel tracking. Participation is recorded when a batch is dispatched — the batch's thread root, or the triggering event id when the reply starts a new thread (mirroring resolve_reply_anchor). - relay.rs: REQ construction extracted into build_req_filters(), which emits a second filter clause (#h + #e limited to followed roots, same kinds/since) when the base clause is mention-gated. The mention gate stays intact for everything else; re-issued REQs replay from last_seen minus skew, so replies landing during the update window are recovered. - filter.rs: match_event takes the followed-root set and admits an unmentioned event whose thread root is followed. Following relaxes only the mention gate — channel scope, kinds, and expression filters still apply. - Followed threads expire after the TTL and the REQ clause is narrowed again; channel unsubscribe clears its follow state. --no-follow-threads restores the previous strict-mention behavior. Closes block#2270. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Broc Oppler <brocoppler@gmail.com>
Review of block#2375 (BradGroux) identified an agent-to-agent loop the initial thread-following change permitted: ignore_self blocks self-loops and the author gate blocks strangers, but same-owner sibling agents pass the gate by design — so two siblings participating in one thread could wake each other with unmentioned replies indefinitely, each turn refreshing the follow TTL. Followed-thread admissions are now author-aware: - match_event reports the admission basis (admitted_via_follow) so the caller can distinguish follow-bypass admissions from mention matches. - A follow-only admission from an agent author is suppressed under the new --follow-thread-authors policy (BUZZ_ACP_FOLLOW_THREAD_AUTHORS): humans (default) admits human authors only; all opts into agent authors and accepts the auto-continuation risk. Explicit mentions are never affected, and suppression happens before the TTL refresh so agent chatter cannot keep a thread alive. - Agenthood uses the existing NIP-OA heuristic (pool::profile_event_is_agent, now pub(crate)) via a cached kind:0 lookup that runs only on the rare follow-admitted path; unknown identities classify as human (fail open, matching turn_is_human_facing). The security gate remains author_allowed. Regression coverage per review: human unmentioned reply admitted; agent-authored unmentioned reply dropped (covers siblings and external agents under respond-to anyone); mentioned agent-authored replies unaffected; all-policy opt-in; and the two-agent auto-continuation scenario cannot start from either side. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Broc Oppler <brocoppler@gmail.com>
6318ba4 to
a72b450
Compare
What this solves
Fixes #2270 (independently re-reported this morning as #2332). A mention-gated agent (
SubscribeMode::Mentions, the default) goes deaf in threads it has joined: once it replies, untagged follow-ups like "yes, go ahead" are never seen unless the human re-tags the agent on every message. The gate exists at two levels — the relay REQ's#pclause never delivers the reply, andmatch_event's mention re-check would drop it anyway.How it works
Participation opens a thread; inactivity closes it.
thread_follow.rs(new): per-channel set of followed NIP-10 thread roots with a sliding inactivity TTL (default 24h,--thread-follow-ttl-secs/BUZZ_ACP_THREAD_FOLLOW_TTL_SECS) and a hard per-channel cap (64, least-recently-active evicted). Participation is recorded at dispatch time: the batch's thread root, or the triggering event id when the agent's reply is about to start a new thread (mirrorsresolve_reply_anchor— this is the exact repro case in [Bug] buzz-acp agents go deaf in threads they've joined unless re-mentioned. #2270). Admitted followed-thread events refresh the TTL, so live conversations keep sliding.build_req_filters(), which emits a second filter clause — same#h/kinds/since, plus#elimited to followed roots — only when the base clause is mention-gated. The mention gate stays intact for everything else; no channel firehose. Subscription updates reuse the existing per-channel sub-id replacement path and replay fromlast_seenminus skew, so replies landing during the update window are recovered. Resubscribes fire only on thread join/expiry, not per message, staying inside the pacing envelope from fix(acp): honor relay rate limits and pace resubscribes on bad links #2199/fix(acp): pace relay observer frames (6/s + 90/min, zero burst) #2217.match_eventtakes the followed set and admits an unmentioned event only when its thread root is followed. Channel scope, kinds, and evalexpr filters still apply — following relaxes only the mention check.--no-follow-threads(BUZZ_ACP_NO_FOLLOW_THREADS) restores the previous strict-mention behavior. State is in-memory, matching the harness's existing recovery semantics (after restart, a fresh mention re-joins a thread).Testing
match_eventfollowed-thread admission (admits followed, ignores unfollowed and top-level, relaxes only the mention gate), andbuild_req_filtersclause construction (base-only, roots-ignored-without-mention-gate, two-clause shape).cargo test -p buzz-acp: 583 passed, 0 failed.cargo clippy --all-targets --all-featuresclean,cargo fmtclean.claude-code-acp): mention answered in-thread; untagged human follow-up in the followed thread answered (~15s,ptags empty); untagged replies in unfollowed threads silent; agent-authored unmentioned replies suppressed under bothowner-onlyand--respond-to anyone(see review thread for transcripts). Can add these as abuzz-test-clientintegration test on request.Follow-up candidates (out of scope here)
🤖 Generated with Claude Code