agentscope-ai/agentscope-java

[Feature]: Sub-agent events are not visible in AG-UI frontend when using `SubAgentTool` with `forwardEvents(true)`

Open

#1046 opened on Mar 26, 2026

View on GitHub
 (0 comments) (0 reactions) (0 assignees)Java (3,253 stars) (693 forks)user submission
enhancementhelp wanted

Description

Sub-agent events are not visible in AG-UI frontend when using SubAgentTool with forwardEvents(true)

Summary

When a supervisor agent delegates tasks to sub-agents via SubAgentTool with SubAgentConfig.forwardEvents(true), the sub-agent's reasoning process (thinking, tool calls, tool results) is completely invisible to the AG-UI frontend. The user sees the sub-agent tool call start, then a long pause (black box), and finally the complete result all at once — with no intermediate streaming.

Environment

  • agentscope-java version: 1.0.10
  • AG-UI adapter: agentscope-agui-spring-boot-starter:1.0.10

Steps to Reproduce

  1. Create a supervisor agent with a sub-agent registered via SubAgentConfig.forwardEvents(true):
SubAgentConfig config = SubAgentConfig.builder()
    .forwardEvents(true)
    .streamOptions(
        StreamOptions.builder()
            .eventTypes(EventType.ALL)
            .includeReasoningChunk(true)
            .includeActingChunk(true)
            .build()
    )
    .build();

toolkit.registration()
    .subAgent(mySubAgentProvider, config)
    .apply();
  1. Connect the supervisor agent to AG-UI via AguiAgentAdapter
  2. Send a message that triggers sub-agent invocation
  3. Observe the AG-UI SSE event stream

Expected Behavior

The frontend should receive real-time streaming events showing the sub-agent's:

  • Reasoning/thinking process (incremental text chunks)
  • Tool call invocations (tool name, arguments)
  • Tool call results

This would provide transparency into the sub-agent's execution, similar to how the parent agent's events are streamed.

Actual Behavior

The frontend receives:

  1. TOOL_CALL_START (sub-agent tool call begins)
  2. Nothing for the entire duration of sub-agent execution (black box)
  3. TOOL_CALL_END + TOOL_CALL_RESULT (final result appears all at once)

The sub-agent's intermediate reasoning and tool usage are completely hidden from the user.

Root Cause Analysis

I traced this through two specific gaps in the event pipeline:

Gap 1: AguiAgentAdapter.run() hardcodes StreamOptions without includeActingChunk

In AguiAgentAdapter.java line 98-99:

StreamOptions options =
    StreamOptions.builder().eventTypes(EventType.ALL).incremental(true).build();

Since includeActingChunk defaults to false, the parent agent's event stream silently drops all ActingChunkEvents. These are exactly the events that carry forwarded sub-agent data (emitted by SubAgentTool.forwardEvent() via ToolEmitter.emit()).

Gap 2: AguiAgentAdapter.convertEvent() has no handling for forwarded sub-agent events

Even if ActingChunkEvents were included in the stream, convertEvent() only handles two event types:

if (type == EventType.REASONING) {
    // ... handles text, thinking, tool use blocks
} else if (type == EventType.TOOL_RESULT && event.isLast()) {
    // ... handles tool results, but ONLY when isLast=true
}
// No handling for ACTING events or forwarded sub-agent metadata

The sub-agent events forwarded by SubAgentTool.forwardEvent() are wrapped in ToolResultBlock metadata:

// SubAgentTool.forwardEvent()
Map<String, Object> metadata = new HashMap<>();
metadata.put("subagent_event", event);
metadata.put("subagent_name", agent.getName());
metadata.put("subagent_id", agent.getAgentId());
metadata.put("subagent_session_id", sessionId);
emitter.emit(new ToolResultBlock(null, null,
    List.of(TextBlock.builder().text(json).build()), metadata));

But AguiAgentAdapter never inspects this metadata or converts it to AG-UI events.

Event Flow Diagram

Sub-agent generates events (REASONING chunks, TOOL_RESULT, etc.)
    ↓
SubAgentTool.forwardEvent() wraps as ToolResultBlock with metadata
    ↓
ToolEmitter.emit() → triggers ActingChunkEvent on parent agent
    ↓
AguiAgentAdapter.run() streams parent agent with includeActingChunk=false
    ↓
✗ ActingChunkEvent is DROPPED — never reaches convertEvent()
    ↓
Even if it did reach convertEvent(), no logic exists to extract
sub-agent metadata and convert to AG-UI events
    ↓
Frontend sees nothing until sub-agent completes

Workaround

We implemented an application-level workaround using a custom Hook + Sinks.Many<AguiEvent> merge pattern:

1. Register a per-request event sink

// In the chat service, before agent execution:
val subAgentSink = Sinks.many()
    .multicast()
    .onBackpressureBuffer<AguiEvent>()

// Store sink keyed by agent identity for hook access
AiChatContext.registerSubAgentSink(agent, SubAgentSinkInfo(
    subAgentSink, threadId, runId
))

// Merge custom events with main AG-UI stream
val mergedEvents = Flux.merge(
    aguiResult.events().doOnComplete { subAgentSink.tryEmitComplete() },
    subAgentSink.asFlux()
)

2. Custom Hook intercepts ActingChunkEvent and emits AG-UI events

@Component
class SubAgentEventForwardingHook : Hook {

    override fun <T : HookEvent> onEvent(event: T): Mono<T> {
        if (event !is ActingChunkEvent) return Mono.just(event)

        val metadata = event.chunk.metadata ?: return Mono.just(event)
        if (!metadata.containsKey("subagent_event")) return Mono.just(event)

        val sinkInfo = AiChatContext.getSubAgentSink(event.agent)
            ?: return Mono.just(event)
        val subEvent = metadata["subagent_event"] as? Event
            ?: return Mono.just(event)
        val agentName = metadata["subagent_name"] as? String ?: "SubAgent"

        // Convert to AG-UI event and push to sink
        val aguiEvent = convertToAguiEvent(subEvent, agentName, sinkInfo)
        aguiEvent?.let { sinkInfo.sink.tryEmitNext(it) }

        return Mono.just(event)
    }
}

This works but requires significant boilerplate at the application level. Ideally, the framework should handle this natively.

Important: StreamOptions tuning required

When configuring SubAgentConfig.streamOptions, you must set includeReasoningResult(false) to avoid duplicate content:

StreamOptions.builder()
    .eventTypes(EventType.ALL)
    .includeReasoningChunk(true)
    .includeReasoningResult(false)  // ← Critical: prevents duplicate output
    .includeActingChunk(true)
    .build()

With includeReasoningResult(true), both incremental chunks AND accumulated results are forwarded, causing every sentence to appear twice on the frontend.

Suggested Fix

Option A: Enhance AguiAgentAdapter to natively support sub-agent event forwarding

  1. Add includeActingChunk(true) to the StreamOptions in AguiAgentAdapter.run():
StreamOptions options = StreamOptions.builder()
    .eventTypes(EventType.ALL)
    .incremental(true)
    .includeActingChunk(true)  // Enable acting chunk events
    .build();
  1. Add sub-agent event handling in convertEvent():
// In convertEvent(), handle forwarded sub-agent events
if (msg.getContent() != null) {
    for (ContentBlock block : msg.getContent()) {
        if (block instanceof ToolResultBlock toolResult
                && toolResult.getMetadata() != null
                && toolResult.getMetadata().containsKey("subagent_event")) {
            Event subEvent = (Event) toolResult.getMetadata().get("subagent_event");
            String agentName = (String) toolResult.getMetadata().get("subagent_name");
            // Convert sub-agent event to appropriate AG-UI events
            events.addAll(convertSubAgentEvent(subEvent, agentName, state));
        }
    }
}

Option B: Allow AguiAdapterConfig to accept custom StreamOptions

Let users override the StreamOptions used in AguiAgentAdapter.run():

public class AguiAdapterConfig {
    // ... existing fields ...
    private StreamOptions streamOptions; // New: custom stream options
}

Option C: Provide a built-in SubAgentEventConverter

Create a dedicated component that integrates with AguiAgentAdapter to handle sub-agent event conversion, without requiring application-level hooks.

Related Issues

  • #918 — Streaming bug with agents as tools
  • #818 — Message delivery when using agent as a tool

Impact

This issue affects any AG-UI application using the supervisor + sub-agent pattern. Without the workaround, sub-agent execution appears as a "black box" to end users, degrading the user experience significantly — especially for sub-agents that perform complex, multi-step reasoning (e.g., permission analysis, data retrieval).

Contributor guide