Labels: bug, prompt-construction, session-management, telegram
Summary
Assistant replies delivered to a channel are mirrored into the session transcript as channel-final delivery-mirror entries (delivery receipts). These mirrors are supposed to be filtered out of the model prompt, but the filter keys exclusively on provider/model metadata — and session rebuild / side-branch merge strips that metadata. The result: the model's prompt contains adjacent, byte-identical duplicate assistant messages. We measured 33% of sampled submitted prompts contaminated (108 of 323 prompt.submitted trajectory events, 278 duplicate pairs). Downstream, models conditioned on "the assistant says everything twice" mimic the pattern and emit their answer twice inside a single completion (full version + condensed restatement) in ~2–5% of long replies — user-visible duplicated content on chat surfaces.
Environment
- OpenClaw version: 2026.6.11 (mirror machinery churned in this release per its own CHANGELOG "Channel delivery" notes)
- Platform: macOS (Darwin 25.3.0 arm64), Telegram channel primary
- Node: v22.22.2
Reproduction
- Run a Telegram channel with any streaming mode on a long-lived session.
- Every final delivery appends a delivery-mirror twin:
mirrorTelegramAssistantReplyToTranscript (dist/bot-*.js, three call sites) → appendExactAssistantMessageToSessionTranscript (dist/transcript-*.js). Channel-final mirrors bypass the redundancy check (isRedundantDeliveryMirror(...) && !identifiedChannelFinal), so a byte-identical mirror is always appended.
- Let the session go through rebuilds / prompt-released side-branch merges (normal long-session operation).
- Inspect a
prompt.submitted trajectory event: adjacent byte-identical role:"assistant" pairs appear, with message keys reduced to {content, role, usage} — provider/model absent.
- Observe occasional replies that contain the same answer twice within one completion.
Confirming the mechanism: immediately after a compaction, duplicate pairs drop to 0 in submitted prompts, then re-accumulate as new finals are delivered.
Root Cause
Chain of failure:
- Reply delivered → real assistant message plus a delivery-mirror twin (
provider: "openclaw", model: "delivery-mirror", zero usage, openclawDeliveryMirror: {kind: "channel-final"}, idempotency key) appended to the transcript. The equivalence dedup is intentionally bypassed for channel-finals (dist/transcript-*.js ~lines 219, 244).
- Session rebuild / side-branch merge (
mergePromptReleasedSessionEntries, dist/session-manager-*.js ~line 1901 — whose own comment says it moves the branch "without … adding delivery mirrors to its context") re-materializes messages without provider/model, and rewrites usage to non-zero.
- The prompt-side filter,
isTranscriptOnlyOpenClawAssistantMessage (used by normalizeAssistantReplayContent, dist/attempt.model-diagnostic-events-*.js ~lines 3621/3635, applied on every prompt path via dist/selection-*.js), checks only message.provider/message.model. With both stripped, the mirror is indistinguishable from a genuine assistant turn and survives into the prompt.
- The model sees its recent replies doubled and sometimes continues the pattern → intra-message duplication.
Mirrors cannot simply be suppressed at append time: they are delivery receipts — re-delivery logic searches history for a matching delivery-mirror entry, and missing mirrors can trigger actual duplicate sends.
Impact
- ~33% of submitted prompts on a busy session carry duplicated assistant history (scales with session length and rebuild frequency).
- ~2–5% of long replies contain user-visible duplicated content (same answer written twice in one message).
- Wasted context-window tokens on every contaminated prompt.
Proposed Fix
Either or both (both recommended — one fixes the invariant, the other is defense-in-depth):
- Preserve the mirror identity through rebuild/merge. Keep
provider/model (or add an explicit transcriptOnly/deliveryMirror flag that survives serialization) on messages through session rebuild and side-branch merge so the existing isTranscriptOnlyOpenClawAssistantMessage filter keeps working.
- Content-based fallback dedup in
normalizeAssistantReplayContent: drop an assistant message whose content is byte-identical to the immediately preceding assistant message when neither carries tool calls (check both camelCase toolCall/toolUse and snake_case tool_use block types). A user turn resets adjacency, so legitimate "say that again" flows are unaffected; a model never legitimately emits two consecutive identical no-tool-call turns — the only producer of that shape is the mirror machinery.
Workaround
Local dist patch GW-P13 on 2026.6.11: adjacent-duplicate collapse added to normalizeAssistantReplayContent (dist/attempt.model-diagnostic-events-*.js), just before each assistant message is pushed:
// P13: adjacent-duplicate collapse — delivery-mirror prompt contamination fix
if (out.length > 0 && !hasAssistantToolCall(assistantMessage)) {
const prev = out[out.length - 1];
if (prev?.role === "assistant" && !hasAssistantToolCall(prev)) {
const prevText = typeof prev.content === "string" ? prev.content : JSON.stringify(prev.content);
const curText = typeof assistantMessage.content === "string" ? assistantMessage.content : JSON.stringify(assistantMessage.content);
if (prevText === curText) { touched = true; continue; }
}
}
Verified live: pre-patch 107/322 prompts contaminated (33%); post-patch 0. Unit tests confirm duplicates collapse, distinct adjacent messages survive, and tool-bearing turns are never dropped. Delivery-mirror transcript entries (receipts) remain intact — only the prompt is cleaned.
Labels: bug, prompt-construction, session-management, telegram
Summary
Assistant replies delivered to a channel are mirrored into the session transcript as
channel-finaldelivery-mirror entries (delivery receipts). These mirrors are supposed to be filtered out of the model prompt, but the filter keys exclusively onprovider/modelmetadata — and session rebuild / side-branch merge strips that metadata. The result: the model's prompt contains adjacent, byte-identical duplicate assistant messages. We measured 33% of sampled submitted prompts contaminated (108 of 323prompt.submittedtrajectory events, 278 duplicate pairs). Downstream, models conditioned on "the assistant says everything twice" mimic the pattern and emit their answer twice inside a single completion (full version + condensed restatement) in ~2–5% of long replies — user-visible duplicated content on chat surfaces.Environment
Reproduction
mirrorTelegramAssistantReplyToTranscript(dist/bot-*.js, three call sites) →appendExactAssistantMessageToSessionTranscript(dist/transcript-*.js). Channel-final mirrors bypass the redundancy check (isRedundantDeliveryMirror(...) && !identifiedChannelFinal), so a byte-identical mirror is always appended.prompt.submittedtrajectory event: adjacent byte-identicalrole:"assistant"pairs appear, with message keys reduced to{content, role, usage}—provider/modelabsent.Confirming the mechanism: immediately after a compaction, duplicate pairs drop to 0 in submitted prompts, then re-accumulate as new finals are delivered.
Root Cause
Chain of failure:
provider: "openclaw",model: "delivery-mirror", zero usage,openclawDeliveryMirror: {kind: "channel-final"}, idempotency key) appended to the transcript. The equivalence dedup is intentionally bypassed for channel-finals (dist/transcript-*.js~lines 219, 244).mergePromptReleasedSessionEntries,dist/session-manager-*.js~line 1901 — whose own comment says it moves the branch "without … adding delivery mirrors to its context") re-materializes messages withoutprovider/model, and rewrites usage to non-zero.isTranscriptOnlyOpenClawAssistantMessage(used bynormalizeAssistantReplayContent,dist/attempt.model-diagnostic-events-*.js~lines 3621/3635, applied on every prompt path viadist/selection-*.js), checks onlymessage.provider/message.model. With both stripped, the mirror is indistinguishable from a genuine assistant turn and survives into the prompt.Mirrors cannot simply be suppressed at append time: they are delivery receipts — re-delivery logic searches history for a matching
delivery-mirrorentry, and missing mirrors can trigger actual duplicate sends.Impact
Proposed Fix
Either or both (both recommended — one fixes the invariant, the other is defense-in-depth):
provider/model(or add an explicittranscriptOnly/deliveryMirrorflag that survives serialization) on messages through session rebuild and side-branch merge so the existingisTranscriptOnlyOpenClawAssistantMessagefilter keeps working.normalizeAssistantReplayContent: drop an assistant message whose content is byte-identical to the immediately preceding assistant message when neither carries tool calls (check both camelCasetoolCall/toolUseand snake_casetool_useblock types). A user turn resets adjacency, so legitimate "say that again" flows are unaffected; a model never legitimately emits two consecutive identical no-tool-call turns — the only producer of that shape is the mirror machinery.Workaround
Local dist patch GW-P13 on 2026.6.11: adjacent-duplicate collapse added to
normalizeAssistantReplayContent(dist/attempt.model-diagnostic-events-*.js), just before each assistant message is pushed:Verified live: pre-patch 107/322 prompts contaminated (33%); post-patch 0. Unit tests confirm duplicates collapse, distinct adjacent messages survive, and tool-bearing turns are never dropped. Delivery-mirror transcript entries (receipts) remain intact — only the prompt is cleaned.