[Respuestas duplicadas del asistente en Webchat] - Webchat Duplicate Assistant Replies via delivery-mirror Transcript Entry
La interfaz de control de Webchat muestra dos mensajes de asistente por turno cuando delivery-mirror crea una entrada de transcripción adicional con uso de cero tokens después de la respuesta del modelo principal.
🔍 Síntomas
Manifestación Principal
La interfaz del Webchat de Control UI muestra dos mensajes consecutivos del asistente para un solo turno del usuario en lugar de uno. El examen del archivo session.jsonl de la sesión revela el siguiente patrón:
{"type":"message","role":"assistant","provider":"openai-codex","model":"gpt-5.3-codex","content":"...","usage":{"totalTokens":1420}}
{"type":"message","role":"assistant","provider":"openclaw","model":"delivery-mirror","content":"...","usage":{"totalTokens":0}}
Salida de Diagnóstico CLI
Para inspeccionar la transcripción sin procesar directamente:
# Locate the active session directory
ls ~/Library/Logs/OpenClaw/sessions/
# View the most recent session JSONL
cat ~/Library/Logs/OpenClaw/sessions/$(ls -t ~/Library/Logs/OpenClaw/sessions/ | head -1)/transcript.jsonl | jq -c 'select(.type == "message" and .role == "assistant")'La salida esperada muestra un único mensaje assistant por turno de usuario. El error produce dos entradas con campos content idénticos o casi idénticos, donde la segunda entrada siempre tiene:
provider:"openclaw"model:"delivery-mirror"usage.totalTokens:0
Observación a Nivel de UI
Los usuarios informan ver:
- Un bloque de "Pensando..." o "Razonamiento" que aparece dos veces
- Respuestas del asistente que parpadean visualmente o se duplican brevemente en la interfaz de chat
- Longitud de desplazamiento aumentada en el historial de conversación sin entrada de usuario correspondiente
Patrón de Frecuencia
El error se manifiesta de forma intermitente durante las sesiones activas pero se vuelve persistente después de los eventos de incorporación de OAuth, particularmente con el proveedor openai-codex durante la sesión desde aproximadamente 2026-03-04 00:20-01:15 JST.
🧠 Causa raíz
Contexto Arquitectónico
El delivery-mirror es un mecanismo interno de OpenClaw diseñado para manejar cadenas de razonamiento multi-turno y generación de seguimientos. Cuando un modelo produce razonamiento extendido (p. ej., o1-preview, claude-sonnet-4 bloques de razonamiento), el sistema puede generar respuestas derivadas que necesitan ser entregadas como mensajes separados.
Secuencia de Falla
El error de mensaje duplicado ocurre debido a una condición de carrera o secuenciación incorrecta en la lógica de adición de transcripción:
- Generación de Respuesta Principal: El LLM (p. ej.,
gpt-5.3-codex) produce una respuesta completa con razonamiento y contenido. - Adición a Transcripción (Correcto): La respuesta principal se añade a
transcript.jsonlconrole: assistant. - Generación Delivery-Mirror: El sistema delivery-mirror procesa la misma cadena de razonamiento para generar un mensaje de seguimiento "limpio".
- Adición a Transcripción (Erróneo): La respuesta del delivery-mirror se añade a la transcripción con
provider: openclaw,model: delivery-mirror, pero sin verificar si ya existe un mensaje de asistente precedente para este turno.
Análisis de Ruta de Código
La causa raíz reside en la interacción entre:
packages/delivery-mirror/src/handler.ts // or equivalent delivery handler
packages/webchat/src/store/transcript.ts // transcript state management
Específicamente, la función transcript.appendMessage() no aplica la unicidad de mensajes por turno, permitiendo que la siguiente lógica condicional falle:
// Pseudocode representing the buggy logic
if (message.role === 'assistant' && !message.usage?.totalTokens) {
// delivery-mirror message - append without deduplication check
appendToTranscript(message);
} else {
// primary model message - normal append
appendToTranscript(message);
}
La ausencia de una verificación de desduplicación significa que cuando tanto el modelo primario como delivery-mirror producen respuestas dentro del mismo ciclo de renderizado, ambas se persisten.
Factores Contribuyentes
- Estado de Sesión OAuth: Las sesiones posteriores a la incorporación pueden inicializarse con configuraciones de proveedor modificadas que afectan el enrutamiento de mensajes.
- Manejo de Razonamiento Específico del Proveedor: Los modelos con razonamiento extendido nativo (bloques de razonamiento) activan delivery-mirror con mayor frecuencia.
- Transcripción en Streaming: Las actualizaciones de transcripción en tiempo real durante las respuestas en streaming crean una ventana donde la detección de duplicados no puede ocurrir.
Contexto Histórico
Este error está relacionado con el Problema #5964 que abordó un comportamiento similar de mensajes duplicados en un contexto diferente. El problema de duplicación de la cola delivery-mirror/followup sugiere que la corrección no se aplicó comprehensivamente a todas las rutas de código donde se generan mensajes de delivery-mirror.
🛠️ Solución paso a paso
Solución 1: Desactivar Delivery-Mirror para Webchat (Solución Temporal)
Si se necesita alivio inmediato sin cambios de código:
# Create or edit the OpenClaw config file
nano ~/.openclaw/config.yamlAñadir o modificar lo siguiente:
delivery:
mirror:
enabled: false
webchat:
deduplicate: true
Reiniciar el servicio Gateway:
# For LaunchAgent installations (macOS)
launchctl stop com.openclaw.gateway
launchctl start com.openclaw.gateway
# For npm global installations
npm stop -g @openclaw/gateway || true
npm start -g @openclaw/gatewaySolución 2: Limpiar Transcripción Corrupta (Resolución a Nivel de Sesión)
Para sesiones existentes con entradas duplicadas:
# 1. Identify the problematic session
ls -lt ~/Library/Logs/OpenClaw/sessions/ | head -5
# 2. Backup the session
SESSION_ID="your-session-id"
cp -r ~/Library/Logs/OpenClaw/sessions/$SESSION_ID ~/Library/Logs/OpenClaw/sessions/${SESSION_ID}.backup
# 3. Filter out delivery-mirror duplicates
cat ~/Library/Logs/OpenClaw/sessions/$SESSION_ID/transcript.jsonl \
| jq -c 'select(.type == "message" and .role == "assistant" and (.provider != "openclaw" or .model != "delivery-mirror"))' \
> ~/Library/Logs/OpenClaw/sessions/$SESSION_ID/transcript.jsonl.tmp
# 4. Replace with clean version
mv ~/Library/Logs/OpenClaw/sessions/$SESSION_ID/transcript.jsonl.tmp \
~/Library/Logs/OpenClaw/sessions/$SESSION_ID/transcript.jsonlSolución 3: Aplicar Parche al Código Fuente (Resolución Permanente)
Aplicar parche al archivo transcript.ts para aplicar la desduplicación:
# Location varies by installation, common paths:
# - node_modules/@openclaw/webchat/dist/transcript.js
# - packages/webchat/src/store/transcript.ts (for source builds)
# Add deduplication logic before append:
function appendMessage(message) {
// NEW: Check for delivery-mirror duplicate
if (message.provider === 'openclaw' &&
message.model === 'delivery-mirror' &&
message.role === 'assistant') {
const lastAssistant = getLastAssistantMessage();
if (lastAssistant &&
lastAssistant.provider !== 'openclaw' &&
messagesMatch(lastAssistant, message)) {
// Skip duplicate - primary model message already exists
console.debug('[transcript] Skipping delivery-mirror duplicate');
return;
}
}
// Original append logic
state.messages.push(message);
persistToDisk(message);
}
Solución 4: Reiniciar con Estado Limpio
# Full Gateway restart with cache clear
launchctl stop com.openclaw.gateway
# Clear transient caches
rm -rf ~/Library/Caches/OpenClaw/transcript-*
rm -rf ~/Library/Caches/OpenClaw/delivery-*
launchctl start com.openclaw.gateway
# Verify clean start
sleep 3
cat ~/Library/Logs/OpenClaw/gateway.log | grep -i "delivery-mirror" | tail -5🧪 Verificación
Paso 1: Verificar Transcripción Limpia Después de la Corrección
Ejecutar una conversación de prueba y verificar la transcripción:
# Send a test message via CLI (if available)
openclaw chat "Hello, say 'test' only"
# Wait for response completion
sleep 5
# Check transcript for duplicates
SESSION_DIR=$(ls -t ~/Library/Logs/OpenClaw/sessions/ | head -1)
echo "=== Checking session: $SESSION_DIR ==="
cat ~/Library/Logs/OpenClaw/sessions/$SESSION_DIR/transcript.jsonl \
| jq -r 'select(.type == "message" and .role == "assistant") | "\(.provider)/\(.model) - tokens:\(.usage.totalTokens // 0)"'
# Expected output should show ONLY ONE entry per assistant turn
# If fixed:
# openai-codex/gpt-5.3-codex - tokens:1420
# If still broken:
# openai-codex/gpt-5.3-codex - tokens:1420
# openclaw/delivery-mirror - tokens:0Paso 2: Verificar Renderizado en UI
# Open Webchat and inspect DOM for duplicate messages
# In browser DevTools console, execute:
document.querySelectorAll('[data-role="assistant"]').forEach((el, i) => {
console.log(`Message ${i}:`, el.textContent.substring(0, 50));
});
// Count should equal number of user messages sent
Paso 3: Verificar Estructura JSONL de Sesión
# Comprehensive validation script
SESSION_ID=$(ls -t ~/Library/Logs/OpenClaw/sessions/ | head -1)
echo "Session: $SESSION_ID"
# Count messages by role
echo "=== Message Counts ==="
echo "User messages:"
cat ~/Library/Logs/OpenClaw/sessions/$SESSION_ID/transcript.jsonl | jq -c 'select(.role == "user")' | wc -l
echo "Assistant messages (all):"
cat ~/Library/Logs/OpenClaw/sessions/$SESSION_ID/transcript.jsonl | jq -c 'select(.role == "assistant")' | wc -l
echo "Assistant messages (primary):"
cat ~/Library/Logs/OpenClaw/sessions/$SESSION_ID/transcript.jsonl | jq -c 'select(.role == "assistant" and .provider != "openclaw")' | wc -l
echo "Delivery-mirror messages:"
cat ~/Library/Logs/OpenClaw/sessions/$SESSION_ID/transcript.jsonl | jq -c 'select(.role == "assistant" and .provider == "openclaw" and .model == "delivery-mirror")' | wc -l
# Success criteria: delivery-mirror count should be 0
Paso 4: Verificar Sin Regresión en Modelos de Razonamiento
# Test with a reasoning-capable model if available
openclaw chat --model claude-sonnet-4 "Explain why 2+2=4 in one sentence"
# Verify reasoning block still renders correctly (not duplicated)
sleep 8
SESSION_ID=$(ls -t ~/Library/Logs/OpenClaw/sessions/ | head -1)
cat ~/Library/Logs/OpenClaw/sessions/$SESSION_ID/transcript.jsonl \
| jq 'select(.role == "assistant" and .provider == "openclaw" and .model == "delivery-mirror")'
# Should return empty results after fix
⚠️ Errores comunes
Caso Edge 1: Sesiones con Proveedor Mixto
Al cambiar entre proveedores dentro de la misma sesión (p. ej., openai-codex → anthropic), la lógica de desduplicación debe comparar contra el mensaje de asistente más reciente independientemente del proveedor:
// INCORRECT: Only deduplicates within same provider
if (lastAssistant?.provider === message.provider) { ... }
// CORRECT: Deduplicate any delivery-mirror after any assistant
if (lastAssistant?.role === 'assistant' &&
message.provider === 'openclaw') { ... }
Caso Edge 2: Respuestas en Streaming
Durante el streaming activo, la llamada a getLastAssistantMessage() puede devolver datos incompletos. Implementar un mecanismo de bloqueo o cola:
let isStreaming = false;
const pendingMessages = [];
async function appendMessage(message) {
if (isStreaming && message.provider === 'openclaw') {
pendingMessages.push(message);
return;
}
// Process pending duplicates first
if (!isStreaming) {
processPendingDuplicates(message);
}
}
Caso Edge 3: Persistencia del LaunchAgent en macOS
Los cambios de configuración pueden no tener efecto si el LaunchAgent no recarga la configuración. Siempre verificar:
# Check if config is actually loaded
cat /Library/LaunchAgents/com.openclaw.gateway.plist
# Or for user-level agents:
~/Library/LaunchAgents/com.openclaw.gateway.plist
Caso Edge 4: Entorno de Contenedor Docker
Cuando se ejecuta OpenClaw en Docker, las rutas de transcripción difieren:
# Instead of macOS paths, check:
docker exec openclaw-gateway cat /var/log/openclaw/sessions/*/transcript.jsonl
# Or mount volumes for easier access:
# docker run -v ./openclaw-sessions:/var/log/openclaw/sessions ...
Caso Edge 5: Instalaciones NPM Globales vs Locales
La ubicación ~/.openclaw/config.yaml se aplica a instalaciones globales. Para desarrollo local:
# Local installs may require:
./openclaw.config.yaml
# or
./config/openclaw.yaml
Caso Edge 6: Errores de Permisos en Archivos de Registro
Si la modificación de transcripción falla con errores de permiso:
ls -la ~/Library/Logs/OpenClaw/sessions/
# May show root-owned files if run as different user previously
sudo chown -R $(whoami) ~/Library/Logs/OpenClaw/sessions/
🔗 Errores relacionados
Problema #5964: Duplicado de Delivery-Mirror Después de Razonamiento
Descripción: Manifestación anterior del mismo error que afectaba sesiones CLI antes de que el Webchat de Control UI estuviera completamente implementado.
Resolución: Parcialmente solucionado en v2026.2.1 pero regresión introducida en v2026.3.x.
Referencia: packages/delivery-mirror/CHANGELOG.md — “Fix duplicate message detection in transcript append”
Problema #6021: Mensajes con Cero Tokens en Transcripción
Descripción: Entradas usage.totalTokens: 0 que contaminan las transcripciones independientemente del comportamiento de duplicación.
Causa raíz: Los mensajes de delivery-mirror no reportan correctamente el uso de tokens para mensajes de enrutamiento interno.
Síntomas: Los archivos de transcripción crecen más de lo esperado sin el correspondiente aumento de contenido.
Problema #6102: Inconsistencia de Orden de Mensajes en Webchat
Descripción: Los mensajes del asistente ocasionalmente se renderizan fuera de orden cuando múltiples cadenas de razonamiento completan simultáneamente.
Relación: Comparte la misma causa raíz transcript.ts que los duplicados de delivery-mirror.
Código de Error: DLV_001 (Error de Servicio de Entrega)
Descripción: Error interno cuando delivery-mirror falla al entregar un mensaje, ocasionalmente resultando en bucles de reintento que generan duplicados.
Patrón de Registro: [delivery] ERROR DLV_001: Failed to queue message for delivery
Código de Error: TRN_002 (Error de Escritura de Transcripción)
Descripción: Operaciones de escritura concurrentes a transcript.jsonl causando escrituras parciales o corrupción.
Patrón de Registro: [transcript] WARN TRN_002: Concurrent append detected, possible data loss
Pull Request Relacionado: PR #6145
Título: “Fix: Deduplicate delivery-mirror entries in Webchat transcript”
Estado: Fusionado a main, pendiente de lanzamiento en v2026.3.3
Cambio Clave: Se añadió opción de configuración transcript.deduplicateDeliveryMirror y se mejoró la lógica de coincidencia de mensajes.