Repository navigation
fix(integrations): stop posting the model's closing text after band_no_reply or band_send_message - #725
Merged
Conversation
bandzalkin
force-pushed
the
fix/acp-tool-settled-reply
branch
from
October 2, 2026 17:00
d8b08f6 to
78de6a7
Compare
…o_reply or band_send_message
The ACP bridge decided whether to relay the model's closing text by
reading Band tool names off the ACP tool_call title. OMP writes the
model's intent phrase into that title ("Ending the turn silently"), and
names MCP tools mcp__band_<tool>, which the resolver does not accept
either; so on OMP no band_no_reply or band_send_message was ever
recognized and every turn's closing narration was relayed to the room as
a reply to the sender being answered. In a live room that produced a
ten-minute loop between two agents, each one's "I ended the turn without
replying…" text starting the other's next turn.
The http/sse local MCP backend now honors the tool_result_hook the Claude
backend already had: the ACP adapter passes a hook that marks the room's
active RoomTurnEmitter as reply-settled when a reply-settling tool
completes in-process, and the emitter honors either that mark or the
stream-based detection. The hook runs only after a successful call, so a
failed post still falls back to text. The stream-based path stays for
remote band-mcp servers the SDK never executes.
Tests drive the real local MCP server through the fake ACP agent with a
title that differs from the tool name: band_no_reply suppresses the
closing text, band_send_message posts once without a text duplicate, and
an observing tool leaves the text reply flowing.
bandzalkin
force-pushed
the
fix/acp-tool-settled-reply
branch
from
October 2, 2026 17:00
78de6a7 to
bbb887e
Compare
AlexanderZ-Band
approved these changes
Oct 6, 2026
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.
Problem
RoomTurnEmitterrelays the model's closing text unlessturn_replied_in_room()sees a completedband_send_message/band_no_replyin the ACP stream — matched by the tool_call title (ACPToolCall.from_acpreadstitlefirst).On OMP that never matches:
iintent phrase (buildToolTitleinmodes/acp/acp-event-mapper.ts), e.g. "Ending the turn silently".mcp__band_<tool>, a spelling_resolve_mcp_tool_namedoes not accept.So every OMP turn's closing narration was posted to the room as a reply to the sender being answered. Observed live (band-agent, SDK 3.3.0): two OMP agents looped for ~10 minutes, one posting "X's message asks nothing new of me, so I ended the turn without replying. Task 2 is at …" every ~10 s — the runner log shows
no reply this turnat the same timestamps — each post starting the other agent's next turn. Realband_send_messagecalls were also duplicated by their narration ("I told the architect…").Fix
engine.py/backends.py: the http/sse local MCP backend now honorstool_result_hook(the Claude backend already had one; the http path silently dropped it). The hook fires only after a successful built-in or custom call, with(tool_name, chat_id, result).client_adapter.py: keeps the activeRoomTurnEmitterper room and passes a hook that callsmark_reply_settled()whensettles_turn_reply(tool_name)— the canonical name is known in-process regardless of what the ACP title says.room_emitter.py:__aexit__relays held text only if neither the in-process mark nor the stream-based detection settled the reply. The stream path stays for remote band-mcp servers the SDK never executes.Tests
tests/integrations/acp/test_client_adapter_behavior.pydrives the real local MCP server through the fake ACP agent with a title that differs from the tool name (will_call_mcp_tool(..., title=)):band_no_reply→ closing text not relayed (red before:["Huginn's me…ed the turn."] == [])band_send_message→ posts once, no text duplicate (red before)band_get_participants) → text still relayedtests/mcp/test_engine.py: hook observes successful built-in and custom calls with the room id; skipped when the call fails.pytest tests/mcp/test_engine.py tests/integrations/acp tests/integrations/test_mcp_backends.py tests/integrations/mcp: 476 passed, 10 skipped.pre-commit(ruff, pyrefly, secrets) clean.Not in this PR
_resolve_mcp_tool_namestill rejectsmcp__band_<tool>; irrelevant once execution-time settlement exists, and the narratedtool_callevent names on OMP remain the model's intent phrase (cosmetic).Testing
uv run pytest tests/ --ignore=tests/integration/): 6289 passed, 658 skippedChecklist
turn_replied_in_room,mark_reply_settled,ToolResultHook; changelog is release-please generated from the commit)