Skip to content

message_tool_only: agent does not terminate after delivering a source reply — cascade of self-replies within one run #96827

Description

@kydorn

What Problem This Solves

Since v2026.6.10, an agent configured with sourceReplyDeliveryMode: "message_tool_only" (the default visible-reply mode for group chats and room_event turns) does not terminate its run after a successful message(action=send) delivery. Instead, the agentic loop continues: the model fetches again, produces another message(send), which delivers and loops again — a cascade of self-replies within a single embedded run.

On Telegram this surfaces as the bot sending a rapid sequence of messages to the group, each one a new generated reply to its own previous output, until some other limit (max iterations, token budget, or the model deciding to stop) kicks in. Observed: 7 sendMessage calls (messageIds 8031–8037) in a single 61-second run; 7 more (8039–8046) in the next 72-second run.

Why This Change Was Made

The terminal-send inhibition relies on two signals that were historically coupled:

  1. A state flag messageToolOnlySourceReplyDelivered, set in embedded-agent-subscribe.handlers.tools.ts after a delivered message(send) is detected.
  2. An afterToolCall hook (installed via installMessageToolOnlyTerminalHook in src/agents/embedded-agent-runner/run/message-tool-terminal.ts) that — on v2026.5.22 through v2026.6.6 — returned { ...hookResult, terminate: true } on a qualifying delivery.

Commit 462092936a ("fix(agent): continue after source message tool replies", #92343) refactored message-tool-terminal.ts for v2026.6.10 and dropped the terminate: true from the return (message-tool-terminal.ts:58 is now return hookResult;). The hook now sets only the state flag (via onDeliveredSourceReply), with no early-termination hint.

The flag-based path does not halt the loop: each successful sendMessage ok is followed by another provider fetch, the model sees its prior delivery in context and generates another message(send), which delivers and loops again. The AfterToolCallResult.terminate field (packages/agent-core/src/types.ts:80: "the agent should stop after the current tool batch") exists precisely to break this cycle, and was the mechanism that prevented it on v2026.6.6 and earlier.

User Impact

  • Telegram group chats (and any message_tool_only surface) see the bot reply to itself in a cascade — a visible regression from v2026.6.6 behavior, where a delivered source reply ended the run immediately.
  • Each cascade iteration costs an extra model round-trip and an extra delivered message.

Evidence

Pre-refactor (v2026.6.6)src/agents/embedded-agent-runner/run/message-tool-terminal.ts:

params.onDeliveredSourceReply?.();
return { ...hookResult, terminate: true };   // hard stop — run ends after delivery

v2026.6.10 — same file, lines 57–58:

params.onDeliveredSourceReply?.();
return hookResult;                            // no terminate hint — loop continues

Observed cascade (Telegram group, message_tool_only, v2026.6.10 image, single run 9b2fbadb):

15:07:55  embedded run start: runId=9b2fbadb…
15:08:02  outbound send ok messageId=8031
15:08:09  outbound send ok messageId=8032
15:08:21  outbound send ok messageId=8033
15:08:28  outbound send ok messageId=8034
15:08:36  outbound send ok messageId=8035
15:08:42  outbound send ok messageId=8036
15:08:51  outbound send ok messageId=8037
15:08:56  embedded run done: durationMs=61202 aborted=false

Each outbound send ok is preceded by its own provider fetch — distinct generated replies, not retransmits.

Workaround (applied locally on local-patches-v2026.6.10): restore return { ...hookResult, terminate: true }; at line 58. The detection path (isDeliveredMessageToolOnlySourceReply, which checks both context.result and hookResult) is unchanged from v2026.6.6, so the fix is a one-line restoration of v2026.6.6 semantics on top of v2026.6.10's refactored detection.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P1High-priority user-facing bug, regression, or broken workflow.clawsweeper:fix-shape-clearClawSweeper found a clear likely implementation shape for this issue.clawsweeper:needs-maintainer-reviewClawSweeper marked this issue as needing maintainer review before automation.clawsweeper:needs-product-decisionClawSweeper marked this issue as needing a product or behavior decision.clawsweeper:no-new-fix-prClawSweeper does not recommend queueing a new automated fix PR for this issue.clawsweeper:source-reproClawSweeper found a high-confidence source-level issue reproduction.impact:message-lossChannel message delivery can be lost, duplicated, or misrouted.issue-rating: 🦞 diamond lobsterVery strong issue quality with high-confidence source-level or clear reproduction.

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions