Skip to content

SubAgentEventStream.recv() does not forward intermediate events (text_delta, tool_execution) from sub-agents #18

@zoubo9034

Description

@zoubo9034

SubAgentEventStream.recv() does not forward intermediate events from sub-agents

Environment

  • a3s-code version: 1.5.7
  • Python: 3.11
  • OS: Linux

Description

When using Orchestrator.spawn_subagent() to launch sub-agents, the SubAgentEventStream returned by handle.events() does not deliver intermediate events such as text_delta (LLM thinking text), tool_execution_started, or tool_execution_completed.

Only turn_start events are received. All other calls to stream.recv() return None until handle.state() transitions to Completed, at which point the final output is available via handle.wait().

This makes it impossible to monitor sub-agent reasoning and tool usage in real-time, which is critical for debugging and observability in multi-agent orchestration scenarios.

Minimal Reproduction

from a3s_code import Agent, Orchestrator, SubAgentConfig

agent = Agent.create("config.hcl")
orchestrator = Orchestrator.create(agent=agent)

handle = orchestrator.spawn_subagent(SubAgentConfig(
    agent_type="my-scoring-agent",
    prompt="Analyze the input and return a JSON result.",
    workspace="/path/to/workspace",
    permissive=True,
    skill_dirs=["./skills"],
    agent_dirs=["./agents"],
))

stream = handle.events()

event_count = 0
text_delta_count = 0
tool_event_count = 0

while True:
    event = stream.recv(timeout_ms=2000)

    if event is None:
        st = str(handle.state())
        if 'Completed' in st or 'Failed' in st:
            print(f"Agent completed. Events: {event_count}, text_delta: {text_delta_count}, tool_events: {tool_event_count}")
            break
        continue

    event_count += 1
    etype_outer = event.get('event_type', '')
    etype_inner = event.get('type', '')

    if etype_inner == 'text_delta':
        text_delta_count += 1
    elif etype_outer in ('tool_execution_started', 'tool_execution_completed'):
        tool_event_count += 1

    print(f"Event: event_type={etype_outer!r}, type={etype_inner!r}")

result = handle.wait()
print(f"Final output: {result[:200]}")

Actual Output

Event: event_type='sub_agent_internal_event', type='turn_start'
Agent completed. Events: 1, text_delta: 0, tool_events: 0
Final output: {"total_score": 14, ...}

Only 1 event (turn_start) is received. The sub-agent clearly performed LLM reasoning and tool calls (evidenced by the structured output), but none of those intermediate events were forwarded through the event stream.

Expected Output

We expect stream.recv() to deliver detailed intermediate events with full content, enabling real-time observability of sub-agent reasoning and tool usage:

# 1. Thinking starts
Event: {
  'event_type': 'sub_agent_internal_event',
  'type': 'turn_start',
  'turn': 1
}

# 2. LLM reasoning text streamed incrementally
Event: {
  'event_type': 'sub_agent_internal_event',
  'type': 'text_delta',
  'text': 'I will call the scoring-video-adapter Skill with the following parameters...'
}

# 3. Tool call initiated — with tool name and full input arguments
Event: {
  'event_type': 'tool_execution_started',
  'tool_name': 'Skill',
  'tool_id': 'tool_abc123',
  'input': {
    'name': 'scoring-video-adapter',
    'args': 'video_path: /path/to/video.mp4\nscoring_items_file: /path/to/items.json\nkeyframes_dir: /path/to/keyframes'
  }
}

# 4. (Optional) Incremental tool output streamed as it's produced
Event: {
  'event_type': 'sub_agent_internal_event',
  'type': 'tool_output_delta',
  'tool_id': 'tool_abc123',
  'delta': '{"status": "processing", "progress": 0.5}'
}

# 5. Tool call completed — with full result, duration, and exit status
Event: {
  'event_type': 'tool_execution_completed',
  'tool_name': 'Skill',
  'tool_id': 'tool_abc123',
  'exit_code': 0,
  'result': '{"status": "success", "findings": [...], "coverage": 1.0, ...}',
  'duration_ms': 45000
}

# 6. Post-tool reasoning text
Event: {
  'event_type': 'sub_agent_internal_event',
  'type': 'text_delta',
  'text': 'Based on the analysis results, I will now calculate scores...\n{"total_score": 14, ...}'
}

# 7. Thinking ends
Event: {
  'event_type': 'sub_agent_internal_event',
  'type': 'turn_end',
  'turn': 1,
  'usage': {'input_tokens': 5000, 'output_tokens': 2000}
}

Agent completed. Events: 50+, text_delta: 40+, tool_events: 2+

Key requirements for each event type:

Event Required Fields Purpose
text_delta text (full incremental content) Monitor LLM reasoning in real-time
tool_execution_started tool_name, tool_id, input (full arguments/prompt) See what the sub-agent is calling and with what parameters
tool_output_delta tool_id, delta Stream long-running tool output incrementally
tool_execution_completed tool_name, tool_id, result (full output), exit_code, duration_ms Verify tool success/failure and inspect returned data
turn_end turn, usage (token counts) Track resource consumption per reasoning round

The SDK's own EventType enum already defines TEXT_DELTA, TOOL_START, TOOL_END, and TOOL_OUTPUT_DELTA, suggesting these event types are architecturally supported but not forwarded for sub-agents.

Additional Context

  • The same handle.events() API is used in both dashboard (Rich live display) and file-based logging modes — both receive the same limited events.
  • A 3-second drain period after detecting Completed state yields no additional events.
  • handle.wait() correctly returns the full final output, confirming the sub-agent executed successfully.
  • This has been observed consistently across multiple sub-agent types and configurations.

Feature Request

Please consider one of the following:

  1. Enable intermediate event forwarding by default for SubAgentEventStream
  2. Add a SubAgentConfig option (e.g., forward_events=True or event_detail_level="full") to opt-in to intermediate event forwarding
  3. Document the current limitation if intermediate event forwarding is intentionally disabled for performance or architectural reasons

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions