Skip to content

Python: [Bug]: Harness Agent drops structured value on the streaming path with tool approval #7418

Closed

Description

@lmx-2077

Description

What happened?

ToolApprovalMiddleware._process_stream finalizes the outer stream with AgentResponse.from_updates without passing output_format_type:

return ResponseStream(_stream(), finalizer=AgentResponse.from_updates)

Because from_updates receives the text updates but no response_format, the final AgentResponse.value is None for streaming runs that combine tool approval with a structured-output schema.

The non-streaming path is unaffected because it returns the inner AgentResponse directly.

What did you expect to happen?

response.value should be parsed into the Pydantic model / schema supplied via response_format, matching the non-harness streaming path in _agents.py:1229.

Steps to reproduce the issue

  1. Create a create_harness_agent(...) with stream=True.
  2. Register a tool that requires approval.
  3. Pass options={"response_format": SomePydanticModel} to agent.run.
  4. Inspect response.value — it is None.

Same "per-run concept lost by a re-wrapping layer" family as #7402 and #7236.

Code Sample

from typing import Any

import asyncio
from pydantic import BaseModel

from agent_framework import (
    AgentSession,
    BaseChatClient,
    ChatResponse,
    Message,
    create_harness_agent,
)


class Answer(BaseModel):
    answer: str


class EchoTool:
    @tool(approval_mode="always_require")
    async def echo(self, text: str) -> str:
        return text


class FakeStreamingClient(BaseChatClient):
    async def _inner_get_response(self, *, messages, stream, options, **kwargs):
        return ChatResponse(
            messages=[Message("assistant", ['{"answer": "42"}'])]
        )


async def main():
    agent = create_harness_agent(
        client=FakeStreamingClient(),
        tools=[EchoTool().echo],
        stream=True,
    )
    session = AgentSession()

    response = await agent.run(
        "Call echo and return an Answer.",
        session=session,
        options={"response_format": Answer},
    )

    print(type(response.value))  # Currently: NoneType
    assert isinstance(response.value, Answer)  # Fails today


asyncio.run(main())

Error Messages / Stack Traces

Package Versions

agent-framework-core: 1.12.1

Python Version

No response

Additional Context

No response

Activity

  1. added theissue type on Jul 30, 2026
  2. added
    pythonUsage: [Issues, PRs], Target: Python
    triageUsage: [Issues], Target: All issues that still need to be triaged
    on Jul 30, 2026
  3. github-actions commented on Jul 30, 2026

    @github-actions
    Contributor

    👋 Hi lmx-2077 — thanks for the report!

    Your issue describes a clear pattern where ToolApprovalMiddleware._process_stream re-wraps the response stream via ResponseStream(_stream(), finalizer=AgentResponse.from_updates) without forwarding the output_format_type parameter, which would cause response.value to be None for structured-output streaming runs with tool approval. This appears consistent with the sibling issues you referenced (#7402, #7236). Unfortunately, were unable to verify against the current source code due to environment access issues, so it's possible this has already been addressed in a newer version; worth checking if you're on the latest release.

    If this still reproduces after trying the above, please reopen and we'll take another look. 🙏

  4. lmx-2077 commented on Jul 30, 2026

    @lmx-2077
    Author

    👋 Hi @lmx-2077 — thanks for the report!

    Your issue describes a clear pattern where ToolApprovalMiddleware._process_stream re-wraps the response stream via ResponseStream(_stream(), finalizer=AgentResponse.from_updates) without forwarding the output_format_type parameter, which would cause response.value to be None for structured-output streaming runs with tool approval. This appears consistent with the sibling issues you referenced (#7402, #7236). Unfortunately, were unable to verify against the current source code due to environment access issues, so it's possible this has already been addressed in a newer version; worth checking if you're on the latest release.

    If this still reproduces after trying the above, please reopen and we'll take another look. 🙏

    Confirmed this still reproduces on current main. The finalizer at python/packages/core/agent_framework/_harness/_tool_approval.py:500 still calls:

    return ResponseStream(_stream(), finalizer=AgentResponse.from_updates)

    without passing output_format_type. Because AgentResponse.value is a lazy property that only parses structured output when _response_format is set, streaming runs with tool approval currently return response.value = None.

    This is a distinct finalizer omission from the sibling issues #7402 / #7236 (both are about the agent loop, not the harness middleware re-wrapping layer). I have a focused fix + regression test ready and will link a PR shortly. Could the likely-fixed label be removed?

  5. added
    agentsUsage: [Issues, PRs], Target: Single agent
    and removed
    triageUsage: [Issues], Target: All issues that still need to be triaged
    on Aug 3, 2026
  6. Shivani767 commented on Aug 20, 2026

    @Shivani767
    Contributor

    Opened a fix in #7800.

    ToolApprovalMiddleware._process_stream now forwards response_format (run options, then agent default_options) into AgentResponse.from_updates, so streaming structured output is parsed the same way as the non-streaming path.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

agentsUsage: [Issues, PRs], Target: Single agentharness[Issues, PRs], Target: harness-level itemslikely-fixedpythonUsage: [Issues, PRs], Target: Python

Type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions