Skip to content

[Bug]: Sub-agents inherit the parent's client-declared frontend tools; calling one silently loses the sub-task #3308

Open

Description

@xuchaoguo1983

AgentScope-Java Version

main @ 857e3e6 (not released yet; 2.0.3 is not affected)

Description

Since frontend tools are passed per call via RuntimeContext#getToolRequestConfig() (#3283), a sub-agent spawned with agent_spawn also gets the parent's client-declared frontend tools: DefaultAgentManager#invokeAgent builds the child context with RuntimeContext.builder(parentRc), and RuntimeContext.Builder#from copies toolRequestConfig.

The sub-agent has no client to execute those tools. When it calls one, the call suspends with [Awaiting external execution] inside the sub-agent, the sub-agent ends with an empty reply, and the parent's agent_spawn result is status: ok with an empty reply. The call never reaches the AG-UI client (no pending call in the parent run), so the sub-task is silently lost.

On 2.0.3 the sub-agent does not see client-declared tools (calling one yields Tool not found), so this is a behaviour change on main.

Reproduction (verified on 2.0.4-SNAPSHOT built from main, AG-UI server, scripted model)

  1. The client declares a frontend tool choose_x in RunAgentInput.tools.
  2. The parent model calls agent_spawn (general-purpose) with a task; the sub-agent's model calls choose_x.
  3. Events show subagent.tool_call choose_x, then a sub-agent tool result [Awaiting external execution] with state RUNNING, then AGENT_END; the parent's agent_spawn result is status: ok, reply: (empty); the parent run finishes normally with nothing pending for the client.

Expected

Either sub-agents do not inherit the parent's per-call frontend tools (as in 2.0.3 — a sub-agent cannot interact with the client), or a frontend call made inside a sub-agent is surfaced to the client through the parent run. In neither case should the sub-task be reported as ok with an empty reply.

Activity

  1. oss-maintainer commented on Sep 26, 2026

    @oss-maintainer
    Collaborator

    Issue Evaluation

    Category: bug | Status: Confirmed

    Verified against main@857e3e6cf by static code analysis (no runtime reproduction: this check host has no JDK/Maven toolchain). Every claim in the report holds; the chain is traced below.

    Root cause — the parent's per-call tool difference is copied into the child context and nothing strips it:

    • agentscope-harness/.../subagent/DefaultAgentManager.java:181-187 (sync) and :236-242 (stream) build the child context as RuntimeContext.builder(parentRc).sessionId(..).userId(..).build().
    • agentscope-core/.../agent/RuntimeContext.java:276-278 delegates builder(source) to Builder#from (:442-465), which copies toolRequestConfig unconditionally at :453. The field javadoc (:28-34, :69-73) describes it as "per-call metadata for an agent run"; the deliberate runId copy shows the inheritance of toolRequestConfig is incidental rather than an intended contract. No code in agentscope-harness/src/main references toolRequestConfig at all, so there is no stripping anywhere on the spawn path.
    • The child ReActAgent seeds its per-call scope from that context (ReActAgent.java:762-763) and composes the model tool surface with it (:2452-2455); Toolkit.getToolSchemas(groups, config) (Toolkit.java:410-436) appends the external/schema-only entries at :425-434. The sub-agent is therefore offered tools it has no executor for.

    Why it fails silently instead of loudly — three independent effects compound:

    1. ToolExecutor.java:247-249 short-circuits an external tool to ToolResultBlock suspension (ToolResultBlock.java:184-194, text [Awaiting external execution]) instead of Tool not found (ToolExecutor.java:211-213), so the child never sees an error.
    2. The child turn terminates with GenerateReason.TOOL_SUSPENDED (ReActAgent.java:2986-2996, :3042-3045, buildSuspendedMsg at :3530-3541). That message contains only ToolUseBlock + ToolResultBlock, so Msg.getTextContent() (Msg.java:544-550, top-level TextBlocks only) returns "" — the suspension text is in memory but discarded by the caller.
    3. AgentSpawnTool.java:960-965 (textOf at :1120-1122) formats that empty text as status: ok\nreply:\n. On the client side, sub-agent events are routed through SubagentEventConverter (AgentEventConverterRegistry.java:100-106), bypassing AgentLifecycleEventConverter's interrupt collection (:95-125), and that collector also skips suspended results whose name is in the run's frontend tool set (skip at :120-122). No interrupt is raised, so there is nothing to resume.

    Additional severity the report does not mention — the inherited config also hides the child's own tools. With ToolMergeMode.EXTERNAL_ONLY (ToolMergeMode.java:34, ToolRequestConfig.hidesBackend) the child's entire backend toolkit is dropped (Toolkit.java:415-424), and with MERGE_EXTERNAL_PRIORITY a child tool whose name collides with a frontend tool is shadowed (ToolRequestConfig.resolveTool:89-98). A sub-agent can thus lose legitimate server-side tools it was expected to use.

    Blast radius — every DefaultAgentManager-driven spawn: agent_spawn and agent_send (sync :800/:825, stream :849/:855, async :474/:651/:1651), plus the SubagentsMiddleware / DynamicSubagentsMiddleware paths. agent_send has the identical defect (:688-698 + :1651). The async paths store the empty string as the task result, so task_output / wait_async_results report an empty success too. Not affected: callers that pass no parent context (e.g. DefaultAgentManager.java:175-177).

    Severity — high. Silent loss of an entire sub-task under a success-looking status, with no error, no client interrupt and no resume path.

    Behaviour change vs 2.0.3 — consistent with the report, but only partially verifiable from this checkout: it is shallow and its tags stop at v2.0.2, which has no toolRequestConfig field at all (RuntimeContext.java: 0 occurrences) and injected frontend tools by mutating the top-level agent's shared toolkit (v2.0.2 AguiAgentAdapter#injectFrontendTools + ToolInjection.close() in doFinally) — a child built with its own toolkit never saw them, hence the old Tool not found. The v2.0.3 tag itself could not be inspected here.

    Fix direction (harness layer, smallest blast radius) — stop propagating the parent's per-call tool difference when the child context is built in DefaultAgentManager (explicitly clear toolRequestConfig, or add a dedicated child-context factory), so a sub-agent only ever sees tools it can execute and a client-only tool call fails loudly with Tool not found again. Copying runId / attributes / state should stay as-is. Clearing it inside RuntimeContext.Builder#from instead would be a global semantic change to every derived context and is not recommended.

    Complementary hardening (worth doing regardless of the above) — agent_spawn / agent_send should inspect the child reply for GenerateReason.TOOL_SUSPENDED and return an error/suspended status naming the offending tools, instead of ok with an empty reply. Silently-empty success is the part that makes this hard to diagnose.

    On the report's second option (surface the child's frontend call to the client through the parent run): not straightforwardly viable today. The sub-agent run is a separate memory/run boundary nested inside a parent tool call that has already completed, and there is no resume path keyed to the sub-agent session id. If the maintainers want that behaviour, it is a design change rather than a fix.

    Tests — agentscope-harness/src/test contains zero references to ToolRequestConfig or external-tool execution, so child-context inheritance is entirely uncovered. Existing coverage is parent-path only (ToolRequestConfigTest, ReActAgentToolkitConcurrencyTest:189-211, AguiAgentAdapterTest:107-160/:300-345). A fix should assert that a spawned child's tool surface excludes client-declared frontend tools, and that agent_spawn never reports status: ok with an empty reply when the child ended suspended.

    Reply /approve to generate a PR for the harness-layer containment + loud-failure hardening, /revise <notes> to adjust the proposal, or /reject to stop.


    Automated evaluation by github-manager-bot

  2. added
    area/core/agentAgent runtime, pipeline, hooks, plan
    area/harnessagentscope-harness (test/runtime support)
    bugSomething isn't working
    on Sep 26, 2026
  3. wyi184246-creator commented on Sep 29, 2026

    @wyi184246-creator

    Hi, I'd like to work on this issue. I'll first reproduce it on main, then investigate preventing client-declared frontend tools from being inherited by spawned sub-agents, with regression tests for both synchronous and streaming paths. I'll also check the case where a suspended child run is reported as a successful empty result. If someone is already working on this or there's a preferred approach, please let me know. Thanks!

  4. oss-maintainer commented on Sep 29, 2026

    @oss-maintainer
    Collaborator

    Proposal window closed — no competing PR will be filed from this proposal window

    The 72 h feedback window opened by the evaluation above (2026-09-26T12:14:27Z) reached its bound on 2026-09-29T12:14Z with no /approve, /revise or /reject directive from a harness owner.

    The thread answered the question in another way: @wyi184246-creator wrote on 2026-09-29T02:53:14Z that they were taking this issue, and the work is now up as PR #3357. As of this read #3357 is OPEN, not a draft, review decision APPROVED, head dcf3f51dd4, +533/-8 across 8 files, last updated 2026-09-29T07:44:46Z, and it declares Fixes #3308.

    So the implementation path for this request is already owned by a community PR awaiting maintainer merge. Filing a second PR against the same issue is exactly the collision this window policy exists to prevent, so this proposal is stood down and no automated PR will be generated from it.

    Nothing here disputes the evaluation: a sub-agent inheriting the parent's client-declared frontend tools, so that the sub-task is silently lost, is a real defect, and #3357 addresses the inheritance boundary the evaluation pointed at. Merge, close, or request changes on #3357 — that is the productive thread now. If #3357 is closed unmerged and this remains wanted, reply here and the question will be re-evaluated against whatever main looks like at that point.

    Automated feedback-window maintenance by github-manager-bot

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/core/agentAgent runtime, pipeline, hooks, planarea/harnessagentscope-harness (test/runtime support)bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions