Streaming + fragmentación

OpenClaw tiene dos capas de streaming separadas:

  • Streaming de bloques (canales): emite bloques completados a medida que el asistente escribe. Son mensajes normales del canal (no deltas de tokens).
  • Streaming de vista previa (Telegram/Discord/Slack): actualiza un mensaje de vista previa temporal durante la generación.

No hay streaming de delta de tokens real hacia mensajes de canal hoy. El streaming de vista previa es basado en mensajes (envío + ediciones/adiciones).

Streaming de bloques (mensajes de canal)

El streaming de bloques envía la salida del asistente en fragmentos gruesos a medida que están disponibles.

Model output
  └─ text_delta/events
       ├─ (blockStreamingBreak=text_end)
       │    └─ chunker emits blocks as buffer grows
       └─ (blockStreamingBreak=message_end)
            └─ chunker flushes at message_end
                   └─ channel send (block replies)

Leyenda:

  • text_delta/events: eventos del stream del modelo (pueden ser escasos para modelos sin streaming).
  • chunker: EmbeddedBlockChunker aplicando límites min/max + preferencia de corte.
  • channel send: mensajes salientes reales (respuestas por bloques).

Controles:

  • agents.defaults.blockStreamingDefault: "on"/"off" (por defecto off).
  • Sobreescrituras por canal: *.blockStreaming (y variantes por cuenta) para forzar "on"/"off" por canal.
  • agents.defaults.blockStreamingBreak: "text_end" o "message_end".
  • agents.defaults.blockStreamingChunk: { minChars, maxChars, breakPreference? }.
  • agents.defaults.blockStreamingCoalesce: { minChars?, maxChars?, idleMs? } (fusionar bloques del stream antes de enviar).
  • Tope estricto por canal: *.textChunkLimit (por ejemplo, channels.whatsapp.textChunkLimit).
  • Modo de fragmentación por canal: *.chunkMode (length por defecto, newline divide en líneas en blanco (límites de párrafo) antes de la fragmentación por longitud).
  • Tope suave de Discord: channels.discord.maxLinesPerMessage (por defecto 17) divide respuestas altas para evitar recorte en la UI.

Semántica de límites:

  • text_end: enviar bloques del stream tan pronto como el chunker los emite; flush en cada text_end.
  • message_end: esperar hasta que el mensaje del asistente termine, luego flush de la salida almacenada.

message_end aún usa el chunker si el texto almacenado excede maxChars, por lo que puede emitir múltiples fragmentos al final.

Algoritmo de fragmentación (límites bajo/alto)

La fragmentación por bloques se implementa mediante EmbeddedBlockChunker:

  • Límite bajo: no emitir hasta que el buffer >= minChars (a menos que se fuerce).
  • Límite alto: preferir cortes antes de maxChars; si se fuerza, cortar en maxChars.
  • Preferencia de corte: paragraphnewlinesentencewhitespace → corte duro.
  • Bloques de código: nunca cortar dentro de fences; cuando se fuerza en maxChars, cierra + reabre el fence para mantener el Markdown válido.

maxChars se limita al textChunkLimit del canal, así que no puedes exceder los topes por canal.

Coalescencia (fusionar bloques del stream)

Cuando el streaming de bloques está habilitado, OpenClaw puede fusionar fragmentos de bloques consecutivos antes de enviarlos. Esto reduce el “spam de una sola línea” mientras sigue proporcionando salida progresiva.

  • La coalescencia espera gaps de inactividad (idleMs) antes de hacer flush.
  • Los buffers tienen un tope de maxChars y hacen flush si lo exceden.
  • minChars previene que fragmentos pequeños se envíen hasta que se acumule suficiente texto (el flush final siempre envía el texto restante).
  • El separador se deriva de blockStreamingChunk.breakPreference (paragraph\n\n, newline\n, sentence → espacio).
  • Las sobreescrituras por canal están disponibles mediante *.blockStreamingCoalesce (incluyendo configuraciones por cuenta).
  • El minChars de coalescencia por defecto se eleva a 1500 para Signal/Slack/Discord a menos que se sobreescriba.

Pausas con ritmo humano entre bloques

Cuando el streaming de bloques está habilitado, puedes agregar una pausa aleatorizada entre respuestas por bloques (después del primer bloque). Esto hace que las respuestas multi-burbuja se sientan más naturales.

  • Config: agents.defaults.humanDelay (sobreescribir por agente mediante agents.list[].humanDelay).
  • Modos: off (por defecto), natural (800–2500ms), custom (minMs/maxMs).
  • Aplica solo a respuestas por bloques, no a respuestas finales ni resúmenes de herramientas.

”Fragmentos del stream o todo”

Esto se mapea a:

  • Fragmentos del stream: blockStreamingDefault: "on" + blockStreamingBreak: "text_end" (emitir sobre la marcha). Los canales no-Telegram también necesitan *.blockStreaming: true.
  • Todo al final del stream: blockStreamingBreak: "message_end" (flush una vez, posiblemente múltiples fragmentos si es muy largo).
  • Sin streaming de bloques: blockStreamingDefault: "off" (solo respuesta final).

Nota sobre canales: El streaming de bloques está desactivado a menos que *.blockStreaming esté explícitamente configurado a true. Los canales pueden hacer streaming de una vista previa en vivo (channels.<channel>.streaming) sin respuestas por bloques.

Recordatorio de ubicación de configuración: los valores por defecto de blockStreaming* están bajo agents.defaults, no en la raíz de la configuración.

Modos de streaming de vista previa

Clave canónica: channels.<channel>.streaming

Modos:

  • off: deshabilitar streaming de vista previa.
  • partial: una sola vista previa que se reemplaza con el último texto.
  • block: vista previa se actualiza en pasos fragmentados/adicionados.
  • progress: vista previa de progreso/estado durante la generación, respuesta final al completarse.

Mapeo por canal

Canaloffpartialblockprogress
Telegrammapea a partial
Discordmapea a partial
Slack

Solo Slack:

  • channels.slack.nativeStreaming activa/desactiva las llamadas a la API nativa de streaming de Slack cuando streaming=partial (por defecto: true).

Migración de clave legacy:

  • Telegram: streamMode + booleano streaming migran automáticamente al enum streaming.
  • Discord: streamMode + booleano streaming migran automáticamente al enum streaming.
  • Slack: streamMode migra automáticamente al enum streaming; booleano streaming migra automáticamente a nativeStreaming.

Comportamiento en runtime

Telegram:

  • Usa sendMessage + editMessageText para actualizaciones de vista previa en DMs y grupos/temas.
  • El streaming de vista previa se omite cuando el streaming de bloques de Telegram está explícitamente habilitado (para evitar streaming doble).
  • /reasoning stream puede escribir razonamiento en la vista previa.

Discord:

  • Usa send + edit para mensajes de vista previa.
  • El modo block usa fragmentación de borrador (draftChunk).
  • El streaming de vista previa se omite cuando el streaming de bloques de Discord está explícitamente habilitado.

Slack:

  • partial puede usar streaming nativo de Slack (chat.startStream/append/stop) cuando está disponible.
  • block usa vistas previas de borrador estilo append.
  • progress usa texto de vista previa de estado, luego respuesta final.