Skip to content

fix(tool): honor execution config retries for tool exceptions - #2830

Open
kevinyang03 wants to merge 4 commits into
agentscope-ai:mainfrom
kevinyang03:fix/tool-retry-on-exceptions
Open

fix(tool): honor execution config retries for tool exceptions#2830
kevinyang03 wants to merge 4 commits into
agentscope-ai:mainfrom
kevinyang03:fix/tool-retry-on-exceptions

Conversation

@kevinyang03

Copy link
Copy Markdown

AgentScope-Java Version

2.0.3-SNAPSHOT

Description

Fixes #2829.

Background: Tool calls never honored the retry settings in ExecutionConfig. ToolExecutor.executeCore(...) converted tool failures into completed ToolResultBlock error results before the timeout/retry layers ran, so applyRetry only ever saw completed results. Verified on main with the reproduction from #2829: a tool throwing IOException once was invoked only once (maxAttempts=3 with RETRYABLE_ERRORS configured), and in the timeout comparison the retry layer ran 3 attempts (Retries exhausted: 2/2) without ever re-invoking the tool.

Changes:

  • Add an execution channel: AgentTool.callAsyncForExecution(...) (new default method, backward compatible) plus ToolMethodInvoker.invokeRawAsync(...), which keep failures as reactive error signals. ToolExecutor.executeWithInfrastructure(...) now runs this channel through scheduling → timeout → retry → shutdown and converts the final error into an error result only after retries are exhausted. The existing callAsync(...) contract — failures surface as error results on direct calls — is unchanged and covered by tests for custom, annotation-based, and MCP tools.
  • Make tool timeouts retryable: the timeout fallback now emits TimeoutException, which ExecutionConfig.RETRYABLE_ERRORS recognizes.
  • Preserve the root cause when retries are exhausted via onRetryExhaustedThrow((spec, signal) -> signal.failure()), instead of the generic Retries exhausted: N/N message.
  • Detect ToolSuspendException anywhere in the cause chain (ExceptionUtils.findToolSuspendException(...), identity-guarded against circular causes) so suspensions — even wrapped in reflection/future/arbitrary exceptions — become suspended results and are never retried, including under the retry-everything fallback when no retryOn predicate is configured.
  • Keep MCP protocol-level business errors (isError=true) as completed error results so they are never retried.
  • Document the execution-channel contract on AgentTool and the tool retry semantics on ExecutionConfig.TOOL_DEFAULTS.

How to test:

  • mvn -pl agentscope-core test -Dtest=ToolExecutorTest — 18 new end-to-end tests (class total: 30), covering retry of annotation tools (sync/Mono/CompletableFuture) and MCP transport failures, no-retry for protocol-level business errors and ToolSuspendException (raw and wrapped), retry-exhaustion error results with id/name, timeout retries, retry isolation in parallel batches, and the direct-call compatibility contract.
  • The reproduction from [Bug]: ExecutionConfig retry settings are ignored for tool failures #2829 now returns the recovered result after 2 attempts (state=RUNNING, output ["recovered"]), and the timeout comparison re-invokes the tool on every attempt with the root cause preserved (Tool execution timeout after PT0.1S).

Checklist

Please check the following items before code is ready to be reviewed.

  • Code has been formatted with mvn spotless:apply
  • All tests are passing (mvn test) — mvn -pl agentscope-core clean verify passes locally: 2312 tests, 0 failures, 0 errors (9 pre-existing skips); the full-reactor clean verify runs in CI
  • Javadoc comments are complete and follow project conventions — mvn -pl agentscope-core javadoc:javadoc passes with doclint=all,-missing
  • Related documentation has been updated (e.g. links, examples, etc.) — no doc changes needed; the new API is a backward-compatible default method, and the ExecutionConfig javadoc is updated in code
  • Code is ready for review

- Remove redundant null check in ExceptionUtils.unwrapExecutionWrapper
- Fix inaccessible ToolExecutor javadoc link in McpTool
- Trim verbose comments to match repository style
- Search the full cause chain (identity-guarded) for ToolSuspendException
  so suspensions hidden behind arbitrary wrappers are never retried
- Drop the redundant Mono.defer in McpTool.callAsyncForExecution and the
  unreachable external-tool branch in ReflectiveFunctionTool
- Document the execution-channel contract on AgentTool and clarify
  TOOL_DEFAULTS retry semantics when no retry predicate is configured
- Add regression tests for Mono/Future/wrapped suspensions and for
  RETRYABLE_ERRORS not retrying deterministic failures
@CLAassistant

CLAassistant commented Aug 24, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@kevinyang03 kevinyang03 reopened this Aug 24, 2026
@codecov

codecov Bot commented Aug 25, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 89.85507% with 7 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...n/java/io/agentscope/core/util/ExceptionUtils.java 73.33% 1 Missing and 3 partials ⚠️
...ain/java/io/agentscope/core/tool/ToolExecutor.java 90.62% 1 Missing and 2 partials ⚠️

📢 Thoughts on this report? Let us know!

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.

[Bug]: ExecutionConfig retry settings are ignored for tool failures

2 participants