openclaw - ✅(Solved) Fix [Feature]: configurable gateway chat-delta broadcast throttle (OPENCLAW_CHAT_DELTA_THROTTLE_MS env var) [1 pull requests, 1 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#74166Fetched 2026-04-30 06:27:47
View on GitHub
Comments
1
Participants
2
Timeline
3
Reactions
0
Author
Timeline (top)
cross-referenced ×2commented ×1

Make the gateway chat-delta broadcast throttle configurable via OPENCLAW_CHAT_DELTA_THROTTLE_MS. Default stays at the existing 150 ms, so behavior is unchanged unless operators opt in.

Root Cause

Make the gateway chat-delta broadcast throttle configurable via OPENCLAW_CHAT_DELTA_THROTTLE_MS. Default stays at the existing 150 ms, so behavior is unchanged unless operators opt in.

Fix Action

Fixed

PR fix notes

PR #74152: feat(gateway): make chat-delta broadcast throttle configurable via OPENCLAW_CHAT_DELTA_THROTTLE_MS

Description (problem / solution / changelog)

Summary

Make the gateway chat-delta broadcast throttle configurable via OPENCLAW_CHAT_DELTA_THROTTLE_MS. Default stays at the existing 150 ms, so behavior is unchanged for everyone who does not opt in.

No linked issue — operator observability/UX seam, filed in response to a real deployment where the hardcoded value did not fit.

Problem

emitChatDelta in src/gateway/server-chat.ts rate-limits assistant text broadcasts to one frame per 150 ms per run via a hardcoded threshold. With fast LLMs (Gemma 31B at OpenRouter, Sonnet 4.6, etc. emitting 30–40 ms/token), the visible streaming pattern lands at ~6 frames/second with ~5 tokens batched per visible chunk, plus a residual flush at the final event. On small mobile screens this reads as "silent then chunks" rather than the smoother per-token rendering that providers' SSE streams already make available.

The 150 ms value is a sensible default but it is the wrong default for some deployments:

  • Local/loopback gateways where bandwidth is free and operators want maximum smoothness.
  • Mobile-only deployments where operators are willing to trade extra WebSocket frames for a more readable stream.
  • Bandwidth-constrained / metered links where operators want to raise the throttle.

Today there is no knob to tune this without a recompile.

For comparison, channel-side throttles like Telegram's existing streamThrottleMs request (#38066) cover their own surface — the gateway broadcast path that feeds Control UI / mobile / 3rd-party WebSocket clients has no equivalent.

Change

src/gateway/chat-delta-throttle.ts (new, 22 LOC):

  • Exports DEFAULT_CHAT_DELTA_THROTTLE_MS = 150 and resolveChatDeltaThrottleMs().
  • Reads OPENCLAW_CHAT_DELTA_THROTTLE_MS and falls back to the default on unset / empty / non-numeric / NaN / negative.
  • 0 is honored (disables the throttle).

src/gateway/server-chat.ts:

  • One-line import of the resolver.
  • Replaces the hardcoded < 150 in emitChatDelta with < resolveChatDeltaThrottleMs().
  • Updates the existing comment near flushBufferedChatDeltaIfNeeded so it stays accurate ("default 150 ms, overridable via env var").

No public API change. No new dependency. No changelog entry (operator-facing diagnostic / tuning knob, off by default — same convention as OPENCLAW_RAW_STREAM).

Why this shape

  • Default unchanged: existing 150 ms preserved; users who do not set the env var see zero behavior change.
  • Resolver called per emit, not memoized at module load — keeps env mutation honored under tests and avoids a restart-only contract for an operator knob.
  • Single env var, no schema change: matches OPENCLAW_RAW_STREAM precedent for operator-only diagnostic/tuning knobs and is automatically blocked from workspace .env by the existing OPENCLAW_* prefix policy in src/infra/dotenv.ts.
  • Strict fallback: Number.isFinite + >= 0 keeps a typo (abc, -50, NaN) from silently breaking streaming in a hard-to-debug way.

Alternatives considered

  • Config-file knob (agents.defaults.streaming.chatDeltaThrottleMs): would be more discoverable but introduces a config-schema migration for a setting with narrow operator audience. Easy to add later if there is demand.
  • Per-channel override: out of scope — this PR targets the gateway broadcast path that all WebSocket clients share.
  • Memoized at module load: rejected; tests need to mutate env between assertions, and operator restarts are an unnecessary tax on a knob this small.

Tests

src/gateway/chat-delta-throttle.test.ts (new, 8 cases):

  • Default returned when env var is unset / empty.
  • Honors a positive integer override.
  • Honors 0 (disables throttle).
  • Honors fractional values (e.g. 75.5).
  • Falls back to default on non-numeric / NaN / negative.
$ pnpm test src/gateway/chat-delta-throttle.test.ts
Test Files  1 passed (1)
     Tests  8 passed (8)
$ pnpm test src/gateway/server-chat.agent-events.test.ts
Test Files  1 passed (1)
     Tests  42 passed (42)

pnpm build clean; the resulting server-chat-*.js bundle contains the new env-var lookup.

Notes

  • OPENCLAW_CHAT_DELTA_THROTTLE_MS is operator-only by design; workspace .env already cannot set it.
  • A config-file alias under agents.defaults.streaming is a possible follow-up if maintainers prefer that surface. Happy to roll it into this PR if requested — kept minimal here so the contract is one knob, one place.

Changed files

  • src/gateway/chat-delta-throttle.test.ts (added, +57/-0)
  • src/gateway/chat-delta-throttle.ts (added, +21/-0)
  • src/gateway/server-chat.ts (modified, +6/-4)
RAW_BUFFERClick to expand / collapse

Summary

Make the gateway chat-delta broadcast throttle configurable via OPENCLAW_CHAT_DELTA_THROTTLE_MS. Default stays at the existing 150 ms, so behavior is unchanged unless operators opt in.

Problem to solve

emitChatDelta in src/gateway/server-chat.ts rate-limits assistant text broadcasts to one frame per 150 ms per run via a hardcoded threshold. With fast LLMs (Gemma 31B at OpenRouter, Sonnet 4.6, etc. emitting 30–40 ms/token), the visible streaming pattern lands at ~6 frames/second with ~5 tokens batched per visible chunk, plus a residual flush at the final event. On small mobile screens this reads as "silent then chunks" rather than the smoother per-token rendering that providers' SSE streams already make available.

The 150 ms value is a sensible default but it is the wrong default for some deployments:

  • Local/loopback gateways where bandwidth is free and operators want maximum smoothness.
  • Mobile-only deployments where operators are willing to trade extra WebSocket frames for a more readable stream.
  • Bandwidth-constrained / metered links where operators want to raise the throttle.

Today there is no knob to tune this without a recompile. Channel-side throttles like Telegram's existing streamThrottleMs request (#38066) cover their own surface — the gateway broadcast path that feeds Control UI / mobile / 3rd-party WebSocket clients has no equivalent.

Proposed solution

New file src/gateway/chat-delta-throttle.ts:

  • Exports DEFAULT_CHAT_DELTA_THROTTLE_MS = 150 and resolveChatDeltaThrottleMs().
  • Reads OPENCLAW_CHAT_DELTA_THROTTLE_MS. Falls back to default on unset / empty / non-numeric / NaN / negative. 0 honored (disables throttle).

server-chat.ts:

  • Replaces hardcoded < 150 in emitChatDelta with < resolveChatDeltaThrottleMs().
  • Updates the existing comment near flushBufferedChatDeltaIfNeeded so it stays accurate.

No public API change. No new dependency. No changelog entry (operator-facing tuning knob, off-by-default — same convention as OPENCLAW_RAW_STREAM).

Implementation ready as PR if accepted (closed with this issue): #74152 (feat/configurable-chat-delta-throttle branch on chphch/openclaw-pr). 8 new unit tests pass; existing 42 server-chat.agent-events tests still pass against the unchanged default. Live-deployed on personal infra at 30 ms.

Alternatives considered

  • Config-file knob (e.g. agents.defaults.streaming.chatDeltaThrottleMs): more discoverable but introduces a config-schema migration for a setting with narrow operator audience. Could be added later if there is demand. Happy to roll into the PR if maintainers prefer.
  • Per-channel override: out of scope — this proposal targets the gateway broadcast path that all WebSocket clients share.
  • Memoized at module load: rejected; tests need to mutate env between assertions, and operator restarts are an unnecessary tax on a knob this small.

Impact

  • Affected: self-hosted operators with custom-rate or mobile-first deployments. Anyone who feels the 5-token-chunk visible rate is the wrong default for their setup.
  • Severity: low (cosmetic / UX) but persistently noticeable.
  • Frequency: every assistant turn — an operator who finds the default chunky sees it on every interaction.
  • Consequence: less smooth streaming UX; not a correctness bug.

Evidence/examples

  • Side-by-side: 30 ms throttle gives ~33 frames/second (close to per-token); 150 ms default gives ~6 frames/second.
  • Telegram already has a parallel request open (#38066) for streamThrottleMs — same shape, channel-scoped. This proposal is the gateway-broadcast equivalent.
  • Convention precedent: OPENCLAW_RAW_STREAM is operator-only, off by default, blocked from workspace .env by the OPENCLAW_* prefix policy. Same shape.

Additional information

🤖 AI-assisted (Claude Code / Sonnet 4.6). Implementation written and fully tested locally. Marked per CONTRIBUTING.md.

Filing this issue first per CONTRIBUTING.md guidance for new features. PR #74152 was filed prematurely and is being closed; will reopen if maintainers agree this surface is welcome.

extent analysis

TL;DR

To address the issue of the hardcoded chat delta throttle, introduce a configurable throttle via the OPENCLAW_CHAT_DELTA_THROTTLE_MS environment variable.

Guidance

  • Review the proposed solution in src/gateway/chat-delta-throttle.ts to understand how the throttle is made configurable.
  • Update server-chat.ts to use the resolveChatDeltaThrottleMs() function instead of the hardcoded value.
  • Test the implementation with different values of OPENCLAW_CHAT_DELTA_THROTTLE_MS to verify the desired streaming behavior.
  • Consider the trade-offs between smoothness and bandwidth usage when choosing a throttle value.

Example

// src/gateway/chat-delta-throttle.ts
export const DEFAULT_CHAT_DELTA_THROTTLE_MS = 150;
export function resolveChatDeltaThrottleMs() {
  const throttleMs = parseInt(process.env.OPENCLAW_CHAT_DELTA_THROTTLE_MS, 10);
  return isNaN(throttleMs) || throttleMs < 0 ? DEFAULT_CHAT_DELTA_THROTTLE_MS : throttleMs;
}

Notes

The proposed solution does not introduce any public API changes or new dependencies, making it a relatively low-risk change.

Recommendation

Apply the proposed workaround by introducing the OPENCLAW_CHAT_DELTA_THROTTLE_MS environment variable and updating the server-chat.ts file to use the configurable throttle value. This allows operators to tune the throttle to their specific use case without requiring a recompile.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

openclaw - ✅(Solved) Fix [Feature]: configurable gateway chat-delta broadcast throttle (OPENCLAW_CHAT_DELTA_THROTTLE_MS env var) [1 pull requests, 1 comments, 2 participants]