Skip to content

fix(harness): isolate subagents from parent frontend tools - #3357

Open
wyi184246-creator wants to merge 4 commits into
agentscope-ai:mainfrom
wyi184246-creator:fix/subagent-frontend-tools-3308
Open

wyi184246-creator wants to merge 4 commits into
agentscope-ai:mainfrom
wyi184246-creator:fix/subagent-frontend-tools-3308

Conversation

@wyi184246-creator

@wyi184246-creator wyi184246-creator commented Sep 29, 2026 •

Copy link
Copy Markdown

AgentScope-Java Version

2.0.4-SNAPSHOT

Description

Fixes #3308.

Local subagents inherited the parent's per-call frontend tools even though they have no client to execute them. Calling one could suspend the child and report an empty successful reply to the parent.

Clear the inherited ToolRequestConfig when creating child invocation contexts, preserving identity and run context. Reject TOOL_SUSPENDED replies with a diagnostic naming the pending tools, including background tasks and calls promoted after a synchronous timeout. Update the English and Chinese subagent documentation.

Validation

  • Added 21 regression cases covering tool merge modes, ReActAgent/HarnessAgent, synchronous/streaming invocation, spawn/send/persistent reuse, background execution, and timeout promotion.
  • The six follow-up cases use a real WorkspaceTaskRepository and verify the live exception type, persisted FAILED status and pending-tool diagnostic, and task_output through both the live repository and a fresh repository instance.
  • Regression proof: the initial 15 cases fail against the original production code; the six new repository cases also fail against the original AgentSpawnTool implementation. All pass with the fix.
  • Follow-up dcf3f51 passed 46 tests with zero failures, errors, or skips: the focused DefaultAgentManagerToolRequestConfigTest, AgentSpawnToolSuspensionTest, and WorkspaceTaskRepositoryTest suites. Attached Spotless checks passed.
  • Follow-up documentation checks passed: npm test (12 tests), npm run check (411 pages and 481 redirects), and punctuation checks for the new English/Chinese release notes.
  • Passed git diff --check. Added bilingual compatibility and migration notes under Unreleased.
  • Production-fix commit ef08b35 passed the affected reactor suite (4,089 tests reported, 26 skipped, zero failures/errors) and GitHub Linux/Windows clean verify, Mintlify validation, and Codecov checks. CI for the latest follow-up is tracked in the PR checks.
  • The earlier local full-repository run was stopped during dependency downloads for unchanged Service modules; local Mintlify CLI checks were unavailable after incomplete dependency installation. These are not counted as successful local full checks.

Checklist

  • Code has been formatted with mvn spotless:apply
  • All tests are passing (follow-up local checks passed; latest full CI results are in the PR checks)
  • Javadoc comments are complete and follow project conventions
  • Related documentation has been updated
  • Code is ready for review

@CLAassistant

CLAassistant commented Sep 29, 2026 •

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@codecov

codecov Bot commented Sep 29, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 88.88889% with 2 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
.../agentscope/harness/agent/tool/AgentSpawnTool.java 87.50% 1 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

@oss-maintainer

Copy link
Copy Markdown
Collaborator

CLA Not Signed

The Contributor License Agreement (CLA) check is currently pending on this PR (license/cla: Contributor License Agreement is not signed yet.). This PR cannot be merged until the CLA is signed.

@wyi184246-creator please sign the CLA via the CLA assistant badge in the comment above, or visit https://cla-assistant.io/agentscope-ai/agentscope-java. Once signed, the license/cla status will turn green.


Automated check by github-manager-bot

@oss-maintainer oss-maintainer left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Summary

Good, well-scoped fix for #3308: local subagent invocations no longer inherit the parent's per-call ToolRequestConfig (client/AG-UI frontend tools stay with the parent's client), and a suspended child reply is now surfaced as an explicit error instead of a silently successful empty reply. I verified on the PR head that all AgentSpawnTool result paths converge on the new guard (execLocalSync's three paths → execWithTimeoutPromotion's .map(requireNonSuspendedReply) / textOf(msg), promoted tasks via bridge.thenApply(textOf), and the three background LocalTaskRunSpec lambdas via textOf(reply)), and that RuntimeContext.Builder stores toolRequestConfig as a plain field so the explicit .toolRequestConfig(null) genuinely clears a value copied from the parent. Tests cover sync/stream/background/promoted × MERGE_EXTERNAL_PRIORITY/EXTERNAL_ONLY — thorough.

Findings are one compatibility call-out and two non-blocking suggestions (see inline comments).

⚠️ Cannot approve yet: the license/cla check is still pending (CLA not signed). The code itself looks ready to merge once the CLA is signed and the maintainers are happy with the compatibility note.


Automated review by github-manager-bot

Comment on lines +186 to +187
// Frontend tools belong to the parent run's client.
.toolRequestConfig(null)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Warning] Compatibility: with this change, any existing deployment that relied on a subagent reaching the parent client's frontend tools (parent ToolRequestConfig inherited via AG-UI / per-call config) will now fail with an IllegalStateException instead of silently returning an empty reply. The direction is right (child must not consume parent-client tools), but consider calling this out in the release notes / migration doc as a behavior change, not only in docs/v2/*/harness/subagent.md.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

@oss-maintainer, thanks for the review. Added English and Chinese compatibility and migration notes under Unreleased in dcf3f51. They distinguish removing inherited client tools from rejecting replies that still carry TOOL_SUSPENDED, and explain that client interactions should stay in the parent or be replaced with backend-executable child tools. Please take another look at the migration wording.

? e.getMessage()
: e.getClass().getSimpleName());
}
return textOf(reply);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Info] The three new return textOf(reply); call sites sit after a catch (RuntimeException) that only stringifies e.getMessage(). The new IllegalStateException from requireNonSuspendedReply therefore loses its type and stack when rendered as "Error: " + message (and in the background-task path, when TaskRunSpec results are persisted). Pre-existing pattern, but since this PR deliberately introduces a structured failure mode here, wrapping with the exception class name (or keeping the cause) would make "suspended" distinguishable from other runtime failures downstream.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

@oss-maintainer, the guard is deliberately outside the preceding catch, so a suspended reply propagates as an IllegalStateException through the live task future. The added repository tests verify the exception type, persisted FAILED status and diagnostic, and task_output after reconstructing the repository. The existing persisted error-message format is unchanged; the suspended-tool diagnostic remains visible to consumers.

Comment on lines +1126 to 1144
requireNonSuspendedReply(msg);
return msg != null ? msg.getTextContent() : "";
}

private static Msg requireNonSuspendedReply(Msg msg) {
if (msg != null && msg.getGenerateReason() == GenerateReason.TOOL_SUSPENDED) {
List<String> tools =
msg.getContentBlocks(ToolUseBlock.class).stream()
.map(ToolUseBlock::getName)
.distinct()
.toList();
throw new IllegalStateException(
"Subagent suspended awaiting external tool execution: " + tools);
}
return msg;
}

private static String formatTimeoutPromoted(String taskId, long timeoutMs) {
return String.format(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Info] Coverage gap: AgentSpawnToolSuspensionTest asserts on the captured TaskRunSpec (execution().get() / future().get()), which validates the guard, but does not assert what TaskRepository/task-status consumers ultimately observe for a suspended background task. If any consumer catches and stores the exception string asynchronously, a suspended child could still surface as a blank/"" result there. A small test that drives the task-status read path (not just the spec) would close the loop on the issue #3308 report end-to-end.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

@oss-maintainer, added six cases using a real WorkspaceTaskRepository, covering spawn/send/persistent reuse in background and timeout-promoted execution. They assert FAILED with no success result, the pending-tool diagnostic, and task_output through both the live repository and a fresh instance.

All six fail against the original implementation and pass with the fix; the related 46-test suite also passes. The follow-up is in dcf3f51 and is ready for another review.

@oss-maintainer oss-maintainer left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Summary

Re-review on dcf3f51d. The follow-up commit resolves every point from my previous pass, and the change now reads as complete:

  • [Warning] compatibility → addressed: EN + ZH release notes under Unreleased spell out that local subagents no longer inherit the parent's per-call ToolRequestConfig, plus a migration note pointing at the subagent tool-execution-boundary section.
  • [Info] error-type flattening at AgentSpawnTool:490 → accepted as designed: keeping requireNonSuspendedReply outside the adjacent catch (RuntimeException) is what lets the IllegalStateException reach the live task future intact, and the new tests assert on that concrete type rather than a stringified message.
  • [Info] coverage gap at AgentSpawnTool:1144 → addressed: suspendedTaskPersistsFailureVisibleToTaskOutput is exactly what was missing. Driving a real WorkspaceTaskRepository over spawn/send/reuse × direct/timeout-promoted, asserting TaskStatus.FAILED with a null result and the pending-tool diagnostic on the live task, the persisted TaskRecord, and the recovered view from a second repository, means a suspended child can no longer be reported as a successful empty reply anywhere a task consumer looks (TaskTool.taskOutput included).

license/cla is now green, so approving. One residual note below.

Findings

  • [Info] AgentSpawnToolSuspensionTest.java:131 — the new test threads the failure through listTasks/getTask/readTaskRecord, which covers what the issue reports. The non-persisting paths (the earlier suspendedBackgroundChildFailsTask / suspendedPromotedChildFailsFuture cases) still assert only on the captured TaskRunSpec, so if a future refactor changes what the mock repository records, those two stay green while behavior drifts. Not blocking — just worth knowing which assertions are load-bearing.

Verification

  • CI on dcf3f51d: build (ubuntu-latest), Check License, Check Module Sync, validate, codecov patch all pass; build (windows-latest) still pending at review time — worth a glance before merge since this test uses @TempDir and path-relative workspace files.

LGTM from my side.


Automated review by github-manager-bot

This branch has not been deployed

No deployments
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]: Sub-agents inherit the parent's client-declared frontend tools; calling one silently loses the sub-task

3 participants