Skip to content

perf(desktop): bound tool-result rendering so big /learn runs don't freeze - #52273

Merged
OutThisLife merged 1 commit into
mainfrom
bb/desktop-tool-render-cap
Jun 25, 2026
Merged

perf(desktop): bound tool-result rendering so big /learn runs don't freeze#52273
OutThisLife merged 1 commit into
mainfrom
bb/desktop-tool-render-cap

Conversation

@OutThisLife

Copy link
Copy Markdown
Collaborator

Problem

/learn over a large file/directory makes the desktop app hang/throttle forever or crash outright — the agent's gathering loop never visibly completes. (Reported: "On large files/directories, the desktop app either crashes completely or just hangs/throttles forever without actually executing the learning process.")

It is not a /learn bug — build_learn_prompt() is fixed-size and never embeds file contents. The fault is in the desktop tool renderer.

Root cause

ToolFallback constructs a fresh part object on every render:

const part: ToolPart = { args, isError, result, toolCallId, toolName, type: 'tool-call' }

That fresh reference defeats the useMemo around buildToolView, which unconditionally JSON.stringifys the entire result (rawResult) and extracts the full detail. During a /learn run over a big directory — many ~100KB tool results — every ~33ms stream flush re-renders the thread and re-serializes every row: the main thread saturates (hang/throttle) and accumulated strings spike renderer memory (crash). Small dirs never trip it because results are few and tiny.

Fix

  • Stabilize the memos. Re-derive a stablePart from the referentially-stable args/result/… and key buildToolView + toolCopyPayload on it, so they recompute only when this tool's data changes — not on every unrelated stream delta.
  • Bound what's painted. New clampForDisplay (MAX_TOOL_RENDER_CHARS = 20_000) applied to prettyJson (rawArgs/rawResult), rawTechnicalTrace, and the rendered detail/stdout/stderr blocks, with a "… N more characters truncated — use Copy" notice. The row's Copy button still reads the uncapped view.detail, so the full output is one click away.

No new core surface, no schema change — purely desktop renderer.

Test plan

  • vitest run apps/desktop/src/components/assistant-ui/tool-fallback-model.test.ts — 14 passed (3 new: clamp pass-through, truncation + omitted-count, buildToolView rawResult cap)
  • tsc -p apps/desktop --noEmit — clean
  • eslint on changed files — clean (pre-existing model-file lints left untouched)
  • The 7 failing assistant-ui tests (block-direction/streaming/user-message-edit) are pre-existing jsdom-layout failures — confirmed identical with these changes stashed

…reeze

ToolFallback rebuilt the `part` wrapper every render, defeating the
buildToolView memo and re-running a full JSON.stringify of the result on
every ~33ms stream delta. A /learn over a large directory (many ~100KB
tool results) saturated the renderer main thread (hang/throttle) and
spiked memory until it OOMd (crash).

- Re-derive a stable `part` from the referentially-stable args/result so
  the view/copy memos hold across deltas.
- Clamp every inline-painted payload (detail, stdout/stderr, rawResult,
  technical trace) to MAX_TOOL_RENDER_CHARS; the row's Copy button still
  reads the uncapped view.detail for the full output.
@OutThisLife
OutThisLife enabled auto-merge (squash) June 25, 2026 02:52
@OutThisLife
OutThisLife merged commit cbe5c56 into main Jun 25, 2026
21 checks passed
@OutThisLife
OutThisLife deleted the bb/desktop-tool-render-cap branch June 25, 2026 02:52
@alt-glitch alt-glitch added type/perf Performance improvement or optimization comp/desktop Electron desktop app (apps/desktop/*) P3 Low — cosmetic, nice to have labels Jun 25, 2026
pai-scaffolde pushed a commit to pai-scaffolde/hermes-agent that referenced this pull request Jun 28, 2026
…reeze (NousResearch#52273)

ToolFallback rebuilt the `part` wrapper every render, defeating the
buildToolView memo and re-running a full JSON.stringify of the result on
every ~33ms stream delta. A /learn over a large directory (many ~100KB
tool results) saturated the renderer main thread (hang/throttle) and
spiked memory until it OOMd (crash).

- Re-derive a stable `part` from the referentially-stable args/result so
  the view/copy memos hold across deltas.
- Clamp every inline-painted payload (detail, stdout/stderr, rawResult,
  technical trace) to MAX_TOOL_RENDER_CHARS; the row's Copy button still
  reads the uncapped view.detail for the full output.
waefrebeorn pushed a commit to waefrebeorn/slermes that referenced this pull request Jul 2, 2026
…reeze (NousResearch#52273)

ToolFallback rebuilt the `part` wrapper every render, defeating the
buildToolView memo and re-running a full JSON.stringify of the result on
every ~33ms stream delta. A /learn over a large directory (many ~100KB
tool results) saturated the renderer main thread (hang/throttle) and
spiked memory until it OOMd (crash).

- Re-derive a stable `part` from the referentially-stable args/result so
  the view/copy memos hold across deltas.
- Clamp every inline-painted payload (detail, stdout/stderr, rawResult,
  technical trace) to MAX_TOOL_RENDER_CHARS; the row's Copy button still
  reads the uncapped view.detail for the full output.
habarmc1223-sudo pushed a commit to habarmc1223-sudo/hermes-agent-fluxmem that referenced this pull request Jul 8, 2026
…reeze (NousResearch#52273)

ToolFallback rebuilt the `part` wrapper every render, defeating the
buildToolView memo and re-running a full JSON.stringify of the result on
every ~33ms stream delta. A /learn over a large directory (many ~100KB
tool results) saturated the renderer main thread (hang/throttle) and
spiked memory until it OOMd (crash).

- Re-derive a stable `part` from the referentially-stable args/result so
  the view/copy memos hold across deltas.
- Clamp every inline-painted payload (detail, stdout/stderr, rawResult,
  technical trace) to MAX_TOOL_RENDER_CHARS; the row's Copy button still
  reads the uncapped view.detail for the full output.
santhreal pushed a commit to santhreal/hermes-agent that referenced this pull request Jul 13, 2026
…reeze (NousResearch#52273)

ToolFallback rebuilt the `part` wrapper every render, defeating the
buildToolView memo and re-running a full JSON.stringify of the result on
every ~33ms stream delta. A /learn over a large directory (many ~100KB
tool results) saturated the renderer main thread (hang/throttle) and
spiked memory until it OOMd (crash).

- Re-derive a stable `part` from the referentially-stable args/result so
  the view/copy memos hold across deltas.
- Clamp every inline-painted payload (detail, stdout/stderr, rawResult,
  technical trace) to MAX_TOOL_RENDER_CHARS; the row's Copy button still
  reads the uncapped view.detail for the full output.
Gravezzz pushed a commit to Gravezzz/hermes-agent that referenced this pull request Jul 21, 2026
…reeze (NousResearch#52273)

ToolFallback rebuilt the `part` wrapper every render, defeating the
buildToolView memo and re-running a full JSON.stringify of the result on
every ~33ms stream delta. A /learn over a large directory (many ~100KB
tool results) saturated the renderer main thread (hang/throttle) and
spiked memory until it OOMd (crash).

- Re-derive a stable `part` from the referentially-stable args/result so
  the view/copy memos hold across deltas.
- Clamp every inline-painted payload (detail, stdout/stderr, rawResult,
  technical trace) to MAX_TOOL_RENDER_CHARS; the row's Copy button still
  reads the uncapped view.detail for the full output.
leewenjie pushed a commit to leewenjie/hermes-agent that referenced this pull request Aug 7, 2026
…reeze (NousResearch#52273)

ToolFallback rebuilt the `part` wrapper every render, defeating the
buildToolView memo and re-running a full JSON.stringify of the result on
every ~33ms stream delta. A /learn over a large directory (many ~100KB
tool results) saturated the renderer main thread (hang/throttle) and
spiked memory until it OOMd (crash).

- Re-derive a stable `part` from the referentially-stable args/result so
  the view/copy memos hold across deltas.
- Clamp every inline-painted payload (detail, stdout/stderr, rawResult,
  technical trace) to MAX_TOOL_RENDER_CHARS; the row's Copy button still
  reads the uncapped view.detail for the full output.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/desktop Electron desktop app (apps/desktop/*) P3 Low — cosmetic, nice to have type/perf Performance improvement or optimization

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants