feat(acp): S1 content-delivery fallback + once-publish-per-trigger - #4191
Open
coldcanuk wants to merge 4 commits into
Open
feat(acp): S1 content-delivery fallback + once-publish-per-trigger#4191coldcanuk wants to merge 4 commits into
coldcanuk wants to merge 4 commits into
Conversation
buzz-agent's output is its tool calls; streamed assistant content is observability-only and normally never posted. Capable models reliably call `buzz messages send`, but weaker local models (e.g. via Buzz shared compute) often answer a conversational prompt in plain content and never call the send tool, silently dropping the reply. Add a content-delivery fallback in buzz-acp: track per-turn whether the model published its own message and buffer streamed content; on a normal turn end with content but no publish tool call, post that content as a threaded kind-9 reply (reusing buzz_sdk::build_message, signed with the agent keys, best-effort, mirroring post_failure_notice). Skips bare acknowledgements the base prompt forbids publishing. Live-validated against a small Gemma on a real community: the fallback fires when the model answers in prose and stays dormant when it calls the send tool. Unit tests cover send-tool detection (incl. read-vs-send discrimination) and bare-ack filtering. (cherry picked from commit 1fe074b) Co-authored-by: chuck <chuck@actvite.com> Signed-off-by: chuck <chuck@actvite.com>
Review findings from block#2448 (Wren + Eva): the content-delivery fallback inferred delivery from the send tool call's INPUT, which was wrong in both directions: - False negative (silent drop): turn_sent_message was set the moment a tool_call's rawInput matched 'messages send' — intent, not delivery. A send that subsequently failed (relay/network/auth/CLI error) still suppressed the fallback, dropping the exact reply the feature exists to save. - False positive (double post): the substring match missed argv-style publishes (subprocess.run(['buzz','messages','send',...])), so a capable model that narrates between tool calls and publishes via argv would get its whole turn narration re-posted by the fallback. Fix: registering a publish is now two-phase. - tool_call input matching (normalized so shell strings AND argv forms match) only registers a CANDIDATE toolCallId. - Delivery is confirmed solely from the candidate's terminal tool_call_update outcome: failed/cancelled → not delivered (fallback stays armed); completed → inspect rawOutput.isError, the CLI's {"accepted":...} response envelope, and any reported exit_code; a bare completed with no inspectable output keeps the status-quo direction (suppress, i.e. today's behavior). Tests: outcome classifier matrix (pending/failed/cancelled/completed x envelope/isError/exit_code) plus handler-level lifecycle tests through handle_session_update + take_undelivered_turn_message covering success, failure, cancel, failed-then-retry, and argv-style publish. Co-authored-by: Tyler Longwell <tlongwell@block.xyz> Signed-off-by: Tyler Longwell <tlongwell@block.xyz> (cherry picked from commit e992ae7)
Port already on branch (plaintext fallback + outcome confirmation). Extend publish detection for goose-prefixed MCP tool ids so Anvil's buzz_publish__buzz_messages_send path still suppresses double-post. plan(p2-m2) Co-authored-by: chuck <chuck@actvite.com> Signed-off-by: chuck <chuck@actvite.com>
After the first confirmed successful channel publish in a turn, send session/cancel so multi-step models cannot re-call buzz_messages_send (Cipher triple-post / Glitch B). Intentional cancel maps to EndTurn so the trigger batch is not requeued. Failed first publishes still allow retries until one confirms. Co-authored-by: chuck <chuck@actvite.com> Signed-off-by: chuck <chuck@actvite.com>
coldcanuk
force-pushed
the
fizz/acp-s1-auto-publish
branch
from
August 2, 2026 11:55
0f50200 to
1d0708a
Compare
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Harness end-turn content-delivery fallback (product S1) plus once-publish-per-trigger stop (S1.1 / Glitch B).
When a channel/DM mention turn ends with assistant text but no confirmed publish tool delivery,
buzz-acpposts that text as exactly one threaded kind:9. After the first confirmed successful publish in a turn, the harness cancels the goose session so multi-step models cannot re-callbuzz_messages_send(triple-post).This is the product fix for channel silence when self-hosted / weak tool-calling models complete a turn without calling
buzz messages send(or the MCP equivalent). Prompt-forced "you must publish" wording is not the product solution.Addresses #4233 (layer B — publish contract).
Related upstream design: #2681 / branch
micn/acp-plaintext-reply-fallback.Related (out of this PR): Layer C workspace clobber is covered by #4284.
What #4233 covers vs this PR
Provider not setwithoutGOOSE_DISABLE_KEYRING=1buzz-acp→ cargotarget/debugclobberBehavior
agent_message_chunktext during the turn.tool_call→ register candidate; terminaltool_call_update→ confirm or leave fallback armed (failed/cancelled send still falls back).StopReason::EndTurn: if undelivered + non-empty + not bare-ack → harness posts viabuzz_sdk::build_message+ signed event (same family as failure notices).messages send,social publishbuzz_messages_send(e.g.buzz_publish__buzz_messages_send)session/cancel→ intentional cancel maps toEndTurn(no batch requeue). Failed first publish still allows retries until one confirms.Files
crates/buzz-acp/src/acp.rscrates/buzz-acp/src/pool.rsFallbackReplyTarget,post_agent_content_fallback, EndTurn hookCommits
bca8bab78—fix(acp): deliver plain-text replies when the model skips the send tool(cherry-pick micn)f68a1d7ae—fix(acp): confirm message delivery from tool outcome, not input text(cherry-pick; PR fix(desktop): shared-compute usability — share toggle, usage indicator, model resync #2448 review)f5e5ecb89—feat(acp): S1 detect MCP buzz_messages_send tool names1d0708a9a—feat(acp): once-publish-per-trigger stop after first confirmed sendFork / worktree (repro & review)
fizz/acp-s1-auto-publish,fizz/acp-once-publish-stop→1d0708a9amainfast-forwarded to the same tip for local deploy tracking/opt/repo/worktrees/wt-acp-s1-auto-publish-20260801Test plan
cargo test -p buzz-acpgreen (672 passed at once-publish HEAD prior to rebase; DCO check green on this PR)@Agenttext (no forced publish poetry) → exactly one kind:9 (fallback may WARN in logs if model text-only)Host deploy (not part of merge CI)
Built debug binary lives in the worktree
target/debug/buzz-acp(install to Desktop spawn path only after operator dual-ack). Absoluteacp_commandpin recommended until Layer C lands.