Streaming + chunking

OpenClaw에는 두 개의 별도 "streaming" 레이어가 있습니다:

  • 블록 streaming (channel): assistant가 작성할 때 완성된 블록을 emit합니다. 이것은 일반 channel 메시지입니다(토큰 델타가 아님).
  • 토큰식 streaming (Telegram만 해당): 생성하는 동안 부분 텍스트로 초안 버블을 업데이트합니다. 최종 메시지는 마지막에 전송됩니다.

오늘날 외부 channel 메시지에 대한 실제 토큰 streaming은 없습니다. Telegram 초안 streaming이 유일한 부분 스트림 표면입니다.

블록 streaming (channel 메시지)

블록 streaming은 사용 가능해질 때 조잡한 청크로 assistant 출력을 전송합니다.

Model 출력
  └─ text_delta/events
       ├─ (blockStreamingBreak=text_end)
       │    └─ chunker가 버퍼가 커질 때 블록을 emit
       └─ (blockStreamingBreak=message_end)
            └─ chunker가 message_end에서 flush
                   └─ channel 전송 (블록 답장)

범례:

  • text_delta/events: model 스트림 이벤트(비streaming model의 경우 sparse할 수 있음).
  • chunker: 최소/최대 경계 + 중단 선호도를 적용하는 EmbeddedBlockChunker.
  • channel send: 실제 아웃바운드 메시지(블록 답장).

제어:

  • agents.defaults.blockStreamingDefault: "on"/"off" (기본값 off).
  • Channel 재정의: *.blockStreaming (및 계정별 변형)을 사용하여 channel별로 "on"/"off"를 강제합니다.
  • agents.defaults.blockStreamingBreak: "text_end" 또는 "message_end".
  • agents.defaults.blockStreamingChunk: { minChars, maxChars, breakPreference? }.
  • agents.defaults.blockStreamingCoalesce: { minChars?, maxChars?, idleMs? } (전송 전 streaming된 블록 병합).
  • Channel 하드 캡: *.textChunkLimit (예: channels.whatsapp.textChunkLimit).
  • Channel 청크 모드: *.chunkMode (length 기본값, newline은 길이 chunking 전에 빈 줄(단락 경계)에서 분할).
  • Discord 소프트 캡: channels.discord.maxLinesPerMessage (기본값 17)는 UI 클리핑을 방지하기 위해 긴 답장을 분할합니다.

경계 의미:

  • text_end: chunker가 emit하자마자 블록을 stream합니다. 각 text_end에서 flush합니다.
  • message_end: assistant 메시지가 완료될 때까지 기다린 다음 버퍼링된 출력을 flush합니다.

message_end는 버퍼링된 텍스트가 maxChars를 초과하면 여전히 chunker를 사용하므로 마지막에 여러 청크를 emit할 수 있습니다.

Chunking 알고리즘 (저/고 경계)

블록 chunking은 EmbeddedBlockChunker로 구현됩니다:

  • 저 경계: 버퍼 >= minChars가 될 때까지 emit하지 않습니다(강제되지 않는 한).
  • 고 경계: maxChars 전에 분할을 선호합니다. 강제되면 maxChars에서 분할합니다.
  • 중단 선호도: paragraphnewlinesentencewhitespace → 하드 중단.
  • 코드 펜스: 펜스 내부에서 절대 분할하지 않습니다. maxChars에서 강제되면 Markdown을 유효하게 유지하기 위해 펜스를 닫고 다시 엽니다.

maxChars는 channel textChunkLimit로 제한되므로 channel별 캡을 초과할 수 없습니다.

Coalescing (streaming된 블록 병합)

블록 streaming이 활성화되면 OpenClaw는 전송하기 전에 연속된 블록 청크를 병합할 수 있습니다. 이렇게 하면 점진적 출력을 제공하면서도 "한 줄 스팸"을 줄일 수 있습니다.

  • Coalescing은 flush 전에 idle 간격(idleMs)을 기다립니다.
  • 버퍼는 maxChars로 제한되며 초과하면 flush됩니다.
  • minChars는 충분한 텍스트가 축적될 때까지 작은 조각이 전송되는 것을 방지합니다(최종 flush는 항상 남은 텍스트를 전송).
  • Joiner는 blockStreamingChunk.breakPreference에서 파생됩니다(paragraph\n\n, newline\n, sentence → 공백).
  • Channel 재정의는 *.blockStreamingCoalesce를 통해 사용할 수 있습니다(계정별 config 포함).
  • 기본 coalesce minChars는 재정의하지 않는 한 Signal/Slack/Discord에 대해 1500으로 증가합니다.

블록 간 사람 같은 pacing

블록 streaming이 활성화되면 블록 답장 사이(첫 번째 블록 이후)에 무작위 일시 중지를 추가할 수 있습니다. 이렇게 하면 다중 버블 응답이 더 자연스럽게 느껴집니다.

  • Config: agents.defaults.humanDelay (agent별로 agents.list[].humanDelay를 통해 재정의).
  • 모드: off (기본값), natural (800–2500ms), custom (minMs/maxMs).
  • 블록 답장에만 적용되며 최종 답장이나 tool 요약에는 적용되지 않습니다.

"청크를 Stream하거나 모든 것을 Stream"

이는 다음에 매핑됩니다:

  • 청크 stream: blockStreamingDefault: "on" + blockStreamingBreak: "text_end" (진행하면서 emit). 비Telegram channel도 *.blockStreaming: true가 필요합니다.
  • 마지막에 모든 것을 stream: blockStreamingBreak: "message_end" (한 번 flush, 매우 긴 경우 여러 청크 가능).
  • 블록 streaming 없음: blockStreamingDefault: "off" (최종 답장만).

Channel 참고: 비Telegram channel의 경우 *.blockStreaming이 명시적으로 true로 설정되지 않는 한 블록 streaming이 꺼져 있습니다. Telegram은 블록 답장 없이 초안을 stream할 수 있습니다(channels.telegram.streamMode).

Config 위치 알림: blockStreaming* 기본값은 루트 config가 아닌 agents.defaults 아래에 있습니다.

Telegram 초안 streaming (토큰식)

Telegram은 초안 streaming이 있는 유일한 channel입니다:

  • topic이 있는 private chat에서 Bot API sendMessageDraft를 사용합니다.
  • channels.telegram.streamMode: "partial" | "block" | "off".
    • partial: 최신 stream 텍스트로 초안을 업데이트합니다.
    • block: chunked 블록으로 초안을 업데이트합니다(동일한 chunker 규칙).
    • off: 초안 streaming 없음.
  • 초안 청크 config (streamMode: "block"에만 해당): channels.telegram.draftChunk (기본값: minChars: 200, maxChars: 800).
  • 초안 streaming은 블록 streaming과 별개입니다. 블록 답장은 기본적으로 꺼져 있으며 비Telegram channel에서 *.blockStreaming: true로만 활성화됩니다.
  • 최종 답장은 여전히 일반 메시지입니다.
  • /reasoning stream은 reasoning을 초안 버블에 작성합니다(Telegram만 해당).

초안 streaming이 활성화되면 OpenClaw는 이중 streaming을 방지하기 위해 해당 답장에 대한 블록 streaming을 비활성화합니다.

Telegram (private + topic)
  └─ sendMessageDraft (초안 버블)
       ├─ streamMode=partial → 최신 텍스트 업데이트
       └─ streamMode=block   → chunker가 초안 업데이트
  └─ 최종 답장 → 일반 메시지

범례:

  • sendMessageDraft: Telegram 초안 버블(실제 메시지가 아님).
  • final reply: 일반 Telegram 메시지 전송.