Repository navigation
Description
Observed Behavior
Closing the response stream returned by FunctionalWorkflow.as_agent().run(..., stream=True) leaves the underlying workflow marked as running. A subsequent call on the same caller-scoped agent raises RuntimeError, even though await response.close() has returned.
This affects cancellation or disconnect cleanup followed by another turn. Closing before the first pull reproduces it without executing the workflow function. Closing after the first output also makes an immediate follow-up run fail.
Expected Behavior
Explicitly closing the agent response stream should close its owned workflow stream and release the run state before returning. The next sequential run should succeed, as it does when closing a stream returned directly by FunctionalWorkflow.run().
Steps to Reproduce
- Use
agent-framework-corefrom commit924a6e50ec0336f3c965ee8b1151c54085888b6e(currentmain, package version 1.25.0). - Run the snippet below. No model, credentials or external service is needed.
- The second run raises instead of returning
result: 2.
Minimal Reproduction
import asyncio
from agent_framework import workflow
@workflow
async def pipeline(value: int) -> str:
return f"result: {value}"
async def main() -> None:
agent = pipeline.build().as_agent()
response = agent.run(1, stream=True)
await response.close()
print((await agent.run(2)).text)
asyncio.run(main())Error Messages and Stack Traces
File "agent_framework/_workflows/_functional.py", line 2062, in run
self._ensure_not_running()
File "agent_framework/_workflows/_functional.py", line 2664, in _ensure_not_running
raise RuntimeError("Workflow is already running. Concurrent executions are not allowed.")
RuntimeError: Workflow is already running. Concurrent executions are not allowed.
Package Versions
agent-framework-core: 1.25.0, installed from the unmodified checkout abovemsgspec: 0.22.0;pydantic: 2.14.3;opentelemetry-api: 1.45.2
Python Version
CPython 3.12.13
Operating System
macOS, arm64
Regression
Unknown
Additional Context
Five local controls used the same workflow and a fresh instance for each case:
| Stream path and close point | Immediate next run |
|---|---|
| Direct workflow stream, before first pull | Succeeds |
| Agent wrapper stream, before first pull | Raises RuntimeError |
| Direct workflow stream, after first workflow event | Succeeds |
| Agent wrapper stream, after first output | Raises RuntimeError |
| Agent wrapper stream, after full consumption | Succeeds |
After deleting the closed stream, calling gc.collect() and yielding three event-loop ticks, the before-first-pull agent case still raises. The partially consumed agent case recovers only after that delayed cleanup.
FunctionalWorkflowAgent._run_streaming() eagerly creates the inner workflow stream, acquiring the run guard. It then returns a separate ResponseStream around _generate_updates(), with no cleanup hook or finally that closes the inner stream. The direct workflow stream has the cleanup hook that releases its guard.
This concerns the current experimental FUNCTIONAL_WORKFLOWS adapter. #8949 tracks the broader vNext design; this report asks whether propagating close to the owned inner stream is an appropriate bounded fix for the current API. No implementation has been started. The direct-workflow ContextVar issue in #7787 is separate; the direct close controls above pass.
I personally ran the minimal reproduction above on this checkout and observed the reported RuntimeError. Codex assisted with the source analysis and report preparation, and executed the additional five-case controls locally.
Acknowledgements
- I searched existing issues and did not find a duplicate.
- I personally verified this behavior and the reproduction details are authentic.
- I will wait for explicit maintainer agreement before starting implementation of a non-trivial change.
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsDone
Activity
github-actions commentedon Nov 7, 2026
Reproduced on
main(924a6e5, core 1.25.0) with an independently written test based on the report: afterawait agent.run(1, stream=True).close(),agent.run(2)raisesRuntimeError: Workflow is already running. Routed to the workflows owner.Support boundary: unclear. This is the experimental functional-workflow adapter, and I found no maintained spec or decision record that sets the scope. A maintainer should decide. Whether propagating close to the inner stream is the right fix is also for maintainers.
Run: https://github.com/microsoft/agent-framework/actions/runs/42361134794
Yongthyuan commentedon Nov 7, 2026
A tested patch is available in my fork: Yongthyuan@f61d189.
It registers the owned workflow stream's
close()as a cleanup hook on the outer agent response. This uses the existing stream cleanup path rather than clearing the workflow's run guard directly.The added tests reproduce the failure before the fix for both an unstarted and partially consumed stream, and check normal completion, repeated close while a newer run is active, workflow errors, and cancellation. With the fix, 249 functional workflow tests and 114 ResponseStream tests pass; source/test Pyright and pinned Ruff checks pass. The original minimal reproduction now prints
result: 2, and all five original comparison cases can run again immediately after close.I have kept this as a fork patch while waiting for maintainer direction on #10164 and the current experimental adapter. No PR has been opened. Codex assisted with implementation, tests, and this note; the account owner personally verified the original failure.