Skip to content

fix: cap MCP tool-call concurrency via MCP_TOOL_CALL_CONCURRENCY (default 4) - #767

Draft
bhaveshpatel640 wants to merge 9 commits into
mainfrom
AGE-2257
Draft

bhaveshpatel640 wants to merge 9 commits into
mainfrom
AGE-2257

Conversation

@bhaveshpatel640

@bhaveshpatel640 bhaveshpatel640 commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

Summary

Cap unbounded Promise.all in executeToolCalls so a large tool batch cannot multiply RAM (bodies stay in memory until LargeToolResponse truncates).

AGE-2257 / INFOSEC-15

Changes

  • Run MCP tool calls through mapWithConcurrency instead of unbounded Promise.all
  • Required concurrency / mcpToolCallConcurrency from config → resolver → AgentThreadexecuteToolCalls (no silent fallback)
  • Env MCP_TOOL_CALL_CONCURRENCY defaults to DEFAULT_MCP_TOOL_CALL_CONCURRENCY (4)

How was this tested?

  • packages/trueforge-core typecheck
  • executeToolCalls unit test: 8 calls, cap 2, order preserved
  • AgentThread / TurnResourceResolver wiring tests that construct those objects

Checklist

  • I have read the contributing guidelines
  • pnpm build, pnpm test, pnpm typecheck, pnpm lint:ci, and pnpm format:check pass locally
  • Tests added/updated where it makes sense
  • No hand-edits to generated code (packages/trueforge-sdk, python/trueforge_sdk, .github/fern/openapi/openapi.json, docs/openapi.json) — fork PRs omit SDK regen; maintainers regenerate after merge
  • Docs / .env.example updated if configuration or behavior changed

Note

Medium Risk
Changes core MCP tool execution concurrency and cancellation semantics on the agent turn loop; behavior is tested but affects resource usage and turn completion on abort.

Overview
Replaces unbounded parallel MCP tool execution with a concurrency-limited batch so large assistant tool dumps cannot spike memory (tool bodies stay in RAM until truncation).

executeToolCalls now runs through mapWithConcurrency with a required concurrency argument and optional AbortSignal; queued work stops after cancel while in-flight calls may still finish. DEFAULT_MCP_TOOL_CALL_CONCURRENCY (4) is exported and wired explicitly from MCP_TOOL_CALL_CONCURRENCY in TrueForge config → TurnResourceResolver / ITurnResourceResolverAgentThread (including sub-agents), with no silent fallback.

AgentThread passes the turn abort signal into tool execution and exits the run loop after a step when aborted, avoiding invalid partial batches and leftover open tool calls. Docs/.env.example document the knob; new unit tests cover the cap and abort behavior.

Reviewed by Cursor Bugbot for commit ee677e1. Bugbot is set up for automated code reviews on this repo. Configure here.

@changeset-bot

changeset-bot Bot commented Sep 16, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: ee677e1

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
@truefoundry/trueforge-core Patch
@truefoundry/trueforge Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread packages/trueforge-core/src/core/util/promiseUtils.ts

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread packages/trueforge-core/src/core/runtime/AgentThread.ts

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread .github/fern/openapi/openapi.json
@bhaveshpatel640
bhaveshpatel640 marked this pull request as ready for review September 17, 2026 05:55

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit ee677e1. Configure here.

// After a step, abort must return before the next deriveState().
// A partial tool batch leaves open calls; tool-response-required → tool-response-required is invalid.
// Do not check at the top of the loop: user-input-required must still emit.
if (outcome === 'exit' || signal?.aborted) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Abort skips user-input events

Medium Severity

The new post-step signal?.aborted return runs after every step, not only after a partial tool batch. If an LLM step commits approval or client-side tool calls and the run is already aborted, execute() returns before user-input-required, so TOOL_APPROVAL_REQUIRED / TOOL_RESPONSE_REQUIRED never emit even though the comment says that step must still run.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit ee677e1. Configure here.

@bhaveshpatel640
bhaveshpatel640 marked this pull request as draft September 17, 2026 07:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant