Installs into .claude/skills of the current project.
Are you the author of 997 Agent Streaming Plugin Fix 7495bbef?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.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