Summary
On OpenClaw 2026.5.2 (8b2a6e5) with acp.stream.deliveryMode = "final_only" and a Telegram-backed agent session, when an agent turn includes a long tool loop (8+ tool calls, ~15+ minutes of work) the assistant emits a sequence of legitimate progress-narration TEXT chunks — but every chunk has textSignature.phase = "commentary". No chunk in the entire turn ever gets marked phase = "final". Under final_only, the channel delivery layer silently strips all of them. The user sees: typing indicator on/off, then total silence, while the agent has actually done correct work and saved correct status messages to the session JSONL.
This appears to be a regression vs. earlier OpenClaw behavior where the runtime would either (a) promote the last commentary chunk to phase=final on turn-end, or (b) emit an explicit phase=final wrap-up itself.
Environment
- OpenClaw:
2026.5.2 (8b2a6e5)
- Runtime: Node
v22.22.0
- OS: Darwin 25.4.0 arm64
- Channel: Telegram group, topic 1 (
agent:main:telegram:group:<chat_id>:topic:1)
- Provider/model:
openai-codex/gpt-5.5
- ACP stream config:
{coalesceIdleMs: 300, maxChunkChars: 1200, deliveryMode: "final_only"}
Reproduction
- Run OpenClaw
2026.5.2 with acp.stream.deliveryMode = "final_only", an agent configured for a Telegram channel, and a session that has at least one prior turn so context isn't trivial.
- Send the agent a request that legitimately requires many tool calls — e.g., "clean up the plugin migration warnings" (forces doctor → config edit → reload → doctor → secrets reload → verification, 8+ tool calls).
- Observe in
~/.openclaw/agents/main/sessions/<sessionId>.jsonl: the agent emits multiple assistant TEXT chunks during the turn, each saved with textSignature containing "phase":"commentary".
- Observe in
~/.openclaw/logs/gateway.log: zero [telegram] sendMessage ok chat=<id> lines for the affected chat for the duration of the turn.
- Observe on Telegram: nothing arrives, even after the runtime nudge
[OpenClaw heartbeat poll] Continue the OpenClaw runtime event fires and the agent acknowledges and continues.
- Eventually the agent produces a final
phase=commentary chunk that internally signals "done" via tone (e.g., "Verifying clean boot now…" or "Restarting once now to load the cleaned config…") but never explicitly marks phase=final. Nothing delivers.
Concrete evidence — the operator's session today
User asked at 2026-05-03T16:13:59 UTC (12:13 EDT) in topic 1: "can you fix the other plugin errors".
Across the next 17 minutes the agent emitted these assistant TEXT chunks (extracted from the session JSONL with phase from textSignature):
| ts (UTC) |
phase |
text excerpt |
| 16:13:59 |
commentary |
"Got it. I'll start with the actual plugin load surface first…" |
| 16:14:45 |
commentary |
"I found three distinct buckets already: stale config refs diffs, acpx, bluebubbles…" |
| 16:15:10 |
commentary |
"The gateway config tool refused this as 'protected config paths,' so I'm switching to the safe fallback…" |
| 16:25:13 |
commentary |
"Continuing. I've got enough context from the event and active task file…" |
| 16:25:31 |
commentary |
"Yes — I found real signal. Stale plugin refs are cleaned from config…" |
| 16:27:55 |
commentary |
"Short version: two fixes are done, one thing remains…" |
| 16:28:51 |
commentary |
"Verifying clean boot now: plugin doctor, full doctor, then the latest xAI and lossless warnings…" |
Telegram delivery to the same chat in the same window:
$ grep "sendMessage.*chat=<chat_id>" ~/.openclaw/logs/gateway.log
2026-05-03T10:50:57.713-04:00 [telegram] sendMessage ok chat=<chat_id> message=4069
2026-05-03T10:58:48.021-04:00 [telegram] sendMessage ok chat=<chat_id> message=4072
# (no further lines for chat=<chat_id> during the 12:13–12:31 EDT window)
So the agent emitted 7 valid status chunks, the runtime persisted all of them to the session JSONL, the work succeeded, and 0 messages were delivered to Telegram. Every chunk's phase value was commentary. Under final_only semantics that is correct rejection — but it is also the entire user-visible failure: the user-side outcome is identical to a hung session.
The agent's own NO_REPLY self-tagging at the close of the turn (also recorded in the JSONL) confirms it intended to ship something, then never re-entered a state where it produced a phase=final chunk.
What it is NOT
Expected behavior
Either:
(a) On turn-end (last assistant chunk before tool-call queue drains and the runtime emits the equivalent of agent_end), the runtime should automatically promote the last commentary chunk to phase=final if no explicit final chunk was produced. This preserves final_only semantics without dropping the entire reply on long tool-loop turns. Or
(b) The agent runtime should refuse to close a turn that has zero phase=final chunks while the user-facing channel is configured final_only — emit an explicit "ran out of tokens / context window / time without a final" diagnostic that the gateway can surface, rather than silently shipping nothing.
Today the runtime appears to do neither: it accepts a turn with 0 phase=final chunks and ships nothing under final_only, with no operator-visible signal that delivery was suppressed.
Suggested fix direction
- Auto-promote-last-commentary on turn-end — easiest path. When the runtime detects an agent turn closing with zero
phase=final emissions and the channel's deliveryMode = "final_only", automatically re-tag the last sufficiently-substantial commentary chunk as phase=final. Preserves observability, preserves the fix from 2026-03-18, doesn't drop messages.
- Hard diagnostic on no-final-emission close — at minimum, emit a gateway-log warning like
[stream] turn closed with 0 final-phase chunks; deliveryMode=final_only suppressed N commentary chunks. Right now the failure is utterly silent in the gateway log (we had to grep textSignature.phase in the session JSONL to find it).
- Per-channel
deliveryMode fallback option — e.g. final_or_last_commentary. Lets operators opt into "deliver the final OR, if there isn't one, the last commentary chunk" without having to choose between the 2026-03-18 preview-finalized observability bug and the 2026-05-03 silent-suppression bug.
Related issues (cross-link these)
- #76424 — Telegram context-overflow retry replays same inbound message and delivers stale turn. Same family (long tool-loops + Telegram +
final_only); different mechanism (input-side replay vs. output-side never-final). Both contribute to "agent went silent on long-running turns" symptom.
- #76772 —
session_status leaks stale model identity via current alias and cached last-run model. Different surface entirely.
- #74257 — Telegram async exec followup leaks HEARTBEAT_OK/internal text after context overflow. Adjacent: also about turn-end phase mishandling, but different direction (over-emits internal text rather than under-emits final text).
Summary
On OpenClaw
2026.5.2 (8b2a6e5)withacp.stream.deliveryMode = "final_only"and a Telegram-backed agent session, when an agent turn includes a long tool loop (8+ tool calls, ~15+ minutes of work) the assistant emits a sequence of legitimate progress-narration TEXT chunks — but every chunk hastextSignature.phase = "commentary". No chunk in the entire turn ever gets markedphase = "final". Underfinal_only, the channel delivery layer silently strips all of them. The user sees: typing indicator on/off, then total silence, while the agent has actually done correct work and saved correct status messages to the session JSONL.This appears to be a regression vs. earlier OpenClaw behavior where the runtime would either (a) promote the last commentary chunk to
phase=finalon turn-end, or (b) emit an explicitphase=finalwrap-up itself.Environment
2026.5.2 (8b2a6e5)v22.22.0agent:main:telegram:group:<chat_id>:topic:1)openai-codex/gpt-5.5{coalesceIdleMs: 300, maxChunkChars: 1200, deliveryMode: "final_only"}Reproduction
2026.5.2withacp.stream.deliveryMode = "final_only", an agent configured for a Telegram channel, and a session that has at least one prior turn so context isn't trivial.~/.openclaw/agents/main/sessions/<sessionId>.jsonl: the agent emits multiple assistant TEXT chunks during the turn, each saved withtextSignaturecontaining"phase":"commentary".~/.openclaw/logs/gateway.log: zero[telegram] sendMessage ok chat=<id>lines for the affected chat for the duration of the turn.[OpenClaw heartbeat poll] Continue the OpenClaw runtime eventfires and the agent acknowledges and continues.phase=commentarychunk that internally signals "done" via tone (e.g., "Verifying clean boot now…" or "Restarting once now to load the cleaned config…") but never explicitly marksphase=final. Nothing delivers.Concrete evidence — the operator's session today
User asked at
2026-05-03T16:13:59 UTC(12:13 EDT) in topic 1: "can you fix the other plugin errors".Across the next 17 minutes the agent emitted these assistant TEXT chunks (extracted from the session JSONL with phase from
textSignature):commentarycommentarydiffs,acpx,bluebubbles…"commentarycommentarycommentarycommentarycommentaryTelegram delivery to the same chat in the same window:
So the agent emitted 7 valid status chunks, the runtime persisted all of them to the session JSONL, the work succeeded, and 0 messages were delivered to Telegram. Every chunk's
phasevalue wascommentary. Underfinal_onlysemantics that is correct rejection — but it is also the entire user-visible failure: the user-side outcome is identical to a hung session.The agent's own
NO_REPLYself-tagging at the close of the turn (also recorded in the JSONL) confirms it intended to ship something, then never re-entered a state where it produced aphase=finalchunk.What it is NOT
[context-overflow-diag]events fire during this turn.compactionAttempts=0. The user message arrives once, is recorded once, no replay.openai-codex/gpt-5.5throughout.[lcm] assemble: donelines fire normally during the turn.final_onlybeing misconfigured. The setting is doing exactly what it was designed to do (drop commentary, deliver final). The bug is upstream of it: the runtime / model never actually emits aphase=finalchunk for the turn.Expected behavior
Either:
(a) On turn-end (last assistant chunk before tool-call queue drains and the runtime emits the equivalent of
agent_end), the runtime should automatically promote the last commentary chunk tophase=finalif no explicit final chunk was produced. This preservesfinal_onlysemantics without dropping the entire reply on long tool-loop turns. Or(b) The agent runtime should refuse to close a turn that has zero
phase=finalchunks while the user-facing channel is configuredfinal_only— emit an explicit "ran out of tokens / context window / time without a final" diagnostic that the gateway can surface, rather than silently shipping nothing.Today the runtime appears to do neither: it accepts a turn with 0
phase=finalchunks and ships nothing underfinal_only, with no operator-visible signal that delivery was suppressed.Suggested fix direction
phase=finalemissions and the channel'sdeliveryMode = "final_only", automatically re-tag the last sufficiently-substantial commentary chunk asphase=final. Preserves observability, preserves the fix from 2026-03-18, doesn't drop messages.[stream] turn closed with 0 final-phase chunks; deliveryMode=final_only suppressed N commentary chunks. Right now the failure is utterly silent in the gateway log (we had to greptextSignature.phasein the session JSONL to find it).deliveryModefallback option — e.g.final_or_last_commentary. Lets operators opt into "deliver the final OR, if there isn't one, the last commentary chunk" without having to choose between the 2026-03-18 preview-finalized observability bug and the 2026-05-03 silent-suppression bug.Related issues (cross-link these)
final_only); different mechanism (input-side replay vs. output-side never-final). Both contribute to "agent went silent on long-running turns" symptom.session_statusleaks stale model identity viacurrentalias and cached last-run model. Different surface entirely.