Merged
Size
XL
Change Breakdown
Performance65%
Bug Fix25%
Dependencies5%
Maintenance5%
#27967fix(instance-ai): reduce memory footprint (no-changelog)

Memory footprint reduced in instance-ai module

Memory optimizations address heap profiling findings in instance-ai: storage-only Mastra singletons prevent agent closure leakage, Brotli compression is disabled to free ~8.6 MB per connection, node types now read from static JSON instead of a 31 MB registry rebuild, and a regex-based JSON highlighter is replaced with a structural walker.

Heap profiling revealed multiple memory leaks and inefficient patterns in the instance-ai module. The most severe: Mastra singletons were storing agent objects with their closures (tools, prompts, thread context) in the internal #agents dict — in multi-user environments this meant User A's agent got pinned in shared memory for all subsequent users. The fix creates Mastra with only storage, while agents get the back-reference needed for suspend/resume via __registerMastra() without being added to the agents dictionary.

A separate bottleneck: collectTypes() triggers a full rebuild of the node type registry, costing ~31 MB each time. The adapter service now reads from the static JSON file that FrontendService writes at startup instead.

Brotli compressors from Express response compression retained ~8.6 MB of native memory each via keep-alive connections. A middleware now strips br from Accept-Encoding to downgrade to gzip, and SSE streams have compression disabled entirely.

The Mastra upgrade brings lazy tiktoken loading, avoiding ~15 MB of WASM allocation on import. A global stub in prevents Mastra's workspace tools from eagerly loading the 66 MB BPE table — a character-based estimate is sufficient for truncation.

On the tracing side, LangSmith requests now use a gzipFetch wrapper that forces gzip encoding and suppresses noisy 409 "payloads already received" errors on retries.

A bug fix: DaytonaFilesystem.stat() threw DaytonaNotFoundError which Mastra didn't recognize, causing write_file to fail on new files. The error is now translated to FileNotFoundError.

In the frontend, a regex-based JSON syntax highlighter was vulnerable to catastrophic backtracking on large tool outputs. It has been replaced with a structural character walker.

View Original GitHub Description

Summary

Fixes several memory and stability issues in instance-ai discovered during heap profiling.

Memory fixes

  • The cached Mastra singleton in registerWithMastra() and ensureMastraRegistered() was passing agents to the constructor, which stored the first agent's closures (tools, prompts, thread context) permanently in Mastra's internal #agents dict. In a multi-user environment this means User A's agent gets pinned in the singleton shared by all subsequent users. Fixed by creating Mastra with only storage. Agents get the back-reference for suspend/resume via __registerMastra() without being added to #agents.

  • InstanceAiAdapterService was calling collectTypes() which triggers postProcessLoaders(), an expensive rebuild of the full node type registry (~31 MB). Switched to reading from the static JSON file that FrontendService writes at startup.

  • Brotli compressors from Express response compression were costing ~8.6 MB of native memory each, retained via keep-alive connections. Added a controller middleware that strips br from Accept-Encoding to downgrade to gzip, and disabled compression entirely on instance-ai SSE streams.

  • Upgraded Mastra packages to get lazy tiktoken loading (avoids ~15 MB WASM allocation on import) and a token growth fix. Also patched the module loader to prevent Mastra from eagerly importing tiktoken and tokenizer (~60 MB combined).

  • LangSmith tracing now uses a gzipFetch wrapper that forces gzip encoding for both proxy and direct client paths, avoiding the brotli decompressor overhead on the tracing side.

Other fixes

  • LangSmith patchRun was logging noisy 409 "payloads already received" errors on retries. The gzipFetch wrapper now returns a synthetic 200 for 409 responses since the data already landed.

  • Mastra's wrapWithReadTracker calls stat() before file writes and catches FileNotFoundError for new files. But DaytonaFilesystem.stat() was throwing Daytona SDK's DaytonaNotFoundError which Mastra didn't recognize, causing write_file to fail on new files that don't exist yet. Now translates Daytona 404s to Mastra's FileNotFoundError.

  • The regex-based JSON syntax highlighter in ToolResultJson.vue was vulnerable to catastrophic backtracking on large tool outputs. Replaced with a structural character walker.

Review / Merge checklist

  • PR title and summary are descriptive. (conventions) <!-- **Remember, the title automatically goes into the changelog. Use `(no-changelog)` otherwise.** -->
  • Docs updated or follow-up ticket created.
  • Tests included. <!-- A bug is not considered fixed, unless a test is added to prevent it from happening again. A feature is not complete without tests. -->
  • PR Labeled with Backport to Beta, Backport to Stable, or Backport to v1 (if the PR is an urgent fix that needs to be backported)
© 2026 · via Gitpulse