# 997 Agent Streaming Plugin Fix 7495bbef

> Agent Streaming Plugin Execution Fix

- Skill: `tools-only/997-agent-streaming-plugin-fix-7495bbef` (Agent Skill, multi-file: 3 files)
- Install (CLI): `npx skillmds@latest add tools-only/997-agent-streaming-plugin-fix-7495bbef`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tools-only/997-agent-streaming-plugin-fix-7495bbef/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: tools-only (https://skillmd.com/u/tools-only)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/tools-only/997-agent-streaming-plugin-fix-7495bbef

---

# Agent Streaming Plugin Execution Fix

**Version:** 0.233.281  
**Fixed in:** December 19, 2025  
**Issue Type:** Bug Fix  
**Severity:** High  

## Problem

Agent streaming was failing when agents attempted to execute plugins (tools/functions) during streaming. The SmartHttpPlugin and other async plugins would work correctly in non-streaming mode but failed in streaming mode due to improper async event loop management.

### Symptoms
- Agent streaming worked for simple responses (no plugin calls)
- When agent tried to use plugins (e.g., SmartHttpPlugin.get_web_content_async), streaming would fail
- Non-streaming mode worked perfectly with the same plugins
- No error displayed to user, stream would just stop

### Example from Logs
```
DEBUG: [Log] [Plugin SUCCESS] SmartHttpPlugin.get_web_content_async (10535.3ms)
```
This shows the plugin worked in non-streaming mode, taking 10.5 seconds to download and process a PDF.

## Root Cause

The initial streaming implementation used `loop.run_until_complete(async_gen.__anext__())` in a while loop, attempting to iterate an async generator one item at a time. This approach:

1. **Created event loop conflicts** - New event loop per stream interfered with plugin async execution
2. **Broke async generator protocol** - Calling `__anext__()` directly bypassed proper async context
3. **Prevented plugin execution** - Plugins couldn't properly execute their async operations within the fragmented event loop
4. **Closed loop prematurely** - `loop.close()` in finally block prevented cleanup

### Original Problematic Code
```python
# ❌ BROKEN - tried to iterate async generator manually
async def stream_agent():
    async for response in selected_agent.invoke_stream(...):
        yield response.content

loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)

try:
    async_gen = stream_agent()
    while True:
        try:
            chunk_content = loop.run_until_complete(async_gen.__anext__())
            yield f"data: {json.dumps({'content': chunk_content})}\n\n"
        except StopAsyncIteration:
            break
finally:
    loop.close()  # ❌ Closes loop too early
```

## Solution

Changed to collect all streaming chunks within a single async context, then yield them to the SSE stream. This allows:

1. **Proper async execution** - Plugins run in a stable event loop
2. **Complete agent lifecycle** - Agent can execute all plugins before streaming to frontend
3. **Cleaner error handling** - Errors captured and reported properly
4. **Event loop reuse** - Attempts to use existing loop before creating new one

### Fixed Code
```python
# ✅ FIXED - collect chunks in single async context
async def stream_agent_async():
    """Collect all streaming chunks from agent"""
    chunks = []
    async for response in selected_agent.invoke_stream(messages=agent_message_history, thread=thread):
        if hasattr(response, 'content') and response.content:
            chunks.append(response.content)
    return chunks

# Execute async streaming with proper loop management
import asyncio
try:
    # Try to get existing event loop
    loop = asyncio.get_event_loop()
    if loop.is_closed():
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)
except RuntimeError:
    # No event loop in current thread
    loop = asyncio.new_event_loop()
    asyncio.set_event_loop(loop)

try:
    # Run streaming and collect chunks
    chunks = loop.run_until_complete(stream_agent_async())
    
    # Yield chunks to frontend
    for chunk_content in chunks:
        accumulated_content += chunk_content
        yield f"data: {json.dumps({'content': chunk_content})}\n\n"
except Exception as stream_error:
    print(f"❌ Agent streaming error: {stream_error}")
    import traceback
    traceback.print_exc()
    yield f"data: {json.dumps({'error': f'Agent streaming failed: {str(stream_error)}'})}\n\n"
    return
```

## Technical Details

### Why This Works

1. **Single Async Context**: All async operations (agent streaming, plugin execution) happen in one `run_until_complete` call
2. **Plugin-Friendly**: Plugins can execute their async operations without loop conflicts
3. **Event Loop Reuse**: Attempts to use existing event loop before creating new one
4. **Error Isolation**: Exceptions during plugin execution are caught and reported
5. **No Premature Cleanup**: Event loop not closed, allowing proper async cleanup

### Trade-offs

**Before Fix:**
- ✅ Attempted true streaming (chunk-by-chunk)
- ❌ Broke plugin execution
- ❌ Complex event loop management
- ❌ Poor error handling

**After Fix:**
- ✅ Plugins work correctly
- ✅ Simpler event loop management  
- ✅ Better error handling
- ⚠️ Collects chunks first, then streams (small delay before first chunk)

### Performance Impact

- **Latency**: Slight increase in time-to-first-token (waits for agent to complete all plugin calls)
- **Throughput**: No change - same total processing time
- **Memory**: Negligible - chunks accumulated in memory briefly
- **Reliability**: Significantly improved - plugins now work

## Affected Components

### Backend
- `route_backend_chats.py` - Agent streaming logic (lines ~3055-3095)

### User Experience
- ✅ Agents with plugins now work in streaming mode
- ✅ Long-running plugin calls (10+ seconds) complete successfully
- ✅ Error messages displayed if streaming fails
- ⚠️ Slight delay before first chunk appears (waits for plugins to complete)

## Testing Results

### Test Case: PDF Download and Summary
**Agent:** Default  
**Plugin:** SmartHttpPlugin.get_web_content_async  
**Action:** Download and extract 7-page PDF from whitehouse.gov  
**Plugin Duration:** 10.5 seconds  

**Before Fix:**
- ❌ Non-streaming: Worked perfectly
- ❌ Streaming: Failed silently

**After Fix:**
- ✅ Non-streaming: Still works  
- ✅ Streaming: Now works correctly

### Validation
From logs showing successful execution:
```
[DEBUG] [INFO]: [Plugin SUCCESS] SmartHttpPlugin.get_web_content_async (10535.3ms)
DEBUG: [Log] [Enhanced Agent Citations] Extracted 1 detailed plugin invocations
[DEBUG] [INFO]: Service aoai-chat-Default prompt_tokens: 8000, completion_tokens: 2016, total_tokens: 10016
```

## Future Improvements

1. **True Streaming**: Implement real chunk-by-chunk streaming without breaking plugins
   - Requires deeper integration with Semantic Kernel's async architecture
   - May need custom async generator wrapper

2. **Progress Indicators**: Show plugin execution status during the "waiting" period
   - "Agent is downloading PDF..."
   - "Agent is processing document (10.5s)..."

3. **Incremental Streaming**: Stream agent reasoning/thoughts while plugins execute
   - Show thinking process in real-time
   - Stream final response after plugins complete

4. **Event Loop Pooling**: Reuse event loops across requests for better performance

## Related Documentation

- Initial feature: `docs/features/AGENT_STREAMING_SUPPORT.md` (v0.233.280)
- This fix: `docs/fixes/AGENT_STREAMING_PLUGIN_FIX.md` (v0.233.281)

## Version History

- **v0.233.280** - Initial agent streaming implementation (broken with plugins)
- **v0.233.281** - Fixed plugin execution in streaming mode

