Skip to content

Python: [Bug]: agent-loop marker reaches the provider SDK (core 1.15.0) #7821

Closed

Description

Description

Since core 1.15.0, an agent built with create_harness_agent(..., loop_should_continue=...) fails every request against a real provider client.

AgentLoopMiddleware stamps a private marker into the run's options to identify the runs it drives (_harness/_loop.py):

stamped_options = dict(context.options) if context.options is not None else {}
stamped_options[_LOOP_ITERATION_TOKEN_KEY] = object()   # "_agent_loop_iteration"
context.options = stamped_options

Agent._prepare_run_context then forwards every option it does not recognise to the chat client, by design (_agents.py):

run_opts = {
    ...,  # known keys popped from opts
    **opts,  # Remaining options are provider-specific
}

The private marker is not a known key, so it lands in the provider-specific bucket and is passed to the SDK as a keyword argument. Clients that hand unrecognised options to their SDK then raise.

This appears to be an unintended interaction between PR #7289 (which introduced the marker) and the long-standing "remaining options are provider-specific" passthrough. The marker is consumed by Agent._run_after_providers to decide whether a turn-scoped context provider should be deferred to the loop boundary, so it needs to reach that code — but not the transport.

The trigger is specifically an active loop predicate. Without loop_should_continue no loop middleware is wired, nothing is stamped, and a harness agent looks healthy — which is likely why this was not caught.

Worth noting: the repository's own harness sample is affected. python/samples/02-agents/harness/harness_research.py passes loop_should_continue=todos_remaining(looping_modes=["execute"]) to a real FoundryChatClient.

Code Sample

# Reproduces with no application code — three public APIs and any running Ollama:


import asyncio

from agent_framework import create_harness_agent, todos_remaining, tool
from agent_framework_ollama import OllamaChatClient


@tool
def get_weather(city: str) -> str:
    """Return the weather for a city."""
    return f"It is sunny in {city}."


async def main() -> None:
    agent = create_harness_agent(
        OllamaChatClient(model="minimax-m3:cloud"),
        tools=[get_weather],
        loop_should_continue=todos_remaining(looping_modes=["execute"]),
    )
    await agent.run("What is the weather in Amsterdam?", session=agent.create_session())


asyncio.run(main())


# Removing only the `loop_should_continue` line makes it pass.

Error Messages / Stack Traces

Ollama:


agent_framework.exceptions.ChatClientException: ("Ollama chat request failed : AsyncClient.chat() got an unexpected keyword argument '_agent_loop_iteration'", TypeError("AsyncClient.chat() got an unexpected keyword argument '_agent_loop_iteration'"))


Azure AI Foundry, same run shape:


agent_framework.exceptions.ChatClientException: (" ... service failed to complete the prompt: AsyncResponses.create() got an unexpected keyword argument '_agent_loop_iteration'", TypeError("AsyncResponses.create() got an unexpected keyword argument '_agent_loop_iteration'"))


The marker arrives as a top-level keyword argument beside the real ones — captured at the SDK boundary:


kwarg keys: ['_agent_loop_iteration', 'messages', 'model', 'options', 'stream', 'tools']
  _agents.py:1126 _run_non_streaming
  ...
  agent_framework_ollama/_chat_client.py:385 _get_response

Package Versions

agent-framework-core: 1.15.0, agent-framework-ollama: 1.0.0b260813

Python Version

Python 3.12

Additional Context

Working on agent-framework-core: 1.14.0

Existing unit coverage does not catch this because test doubles accept **kwargs and silently absorb the marker; only a real SDK call raises. A regression test would need either a client whose signature rejects unknown keywords, or an assertion on the keys handed to the client.

Two directions, both plausible from the outside:

  • Keep the marker off the options mapping entirely — a contextvar or a private attribute on the context would carry it to _run_after_providers without passing through the provider-specific passthrough.
  • Or strip known-private keys (_-prefixed, or an explicit set) where run_opts is assembled, so the passthrough forwards only genuinely provider-specific options.

Activity

  1. added
    pythonUsage: [Issues, PRs], Target: Python
    triageUsage: [Issues], Target: All issues that still need to be triaged
    on Aug 22, 2026
  2. added
    reproducedUsage: [Issues], Target: all issues that can be reproduced by the triage workflow
    on Aug 22, 2026
  3. github-actions commented on Aug 22, 2026

    @github-actions
    Contributor

    🤖 Automated triage reproduction notes (agent-authored — trust but verify)

    Agent analysis

    python/packages/core/agent_framework/_harness/_loop.py::_process_non_streaming stamps _agent_loop_iteration, and python/packages/core/agent_framework/_agents.py::_prepare_run_context forwards it through **opts to the chat client. The trigger is an active AgentLoopMiddleware with a provider transport that rejects unknown keywords; minimally, run an Agent using the added StrictTransportChatClient and AgentLoopMiddleware(always_continue, max_iterations=1).

    • Failing test: python/packages/core/tests/core/test_harness_loop.py
    • Files examined: python/AGENTS.md, python/packages/core/agent_framework/_harness/_loop.py, python/packages/core/agent_framework/_agents.py, python/packages/core/tests/core/test_harness_loop.py, python/packages/core/tests/core/test_harness_agent.py, python/packages/core/pyproject.toml
    • Tests run: test_loop_stops_at_max_iterations, test_strict_provider_transport_suceeds_without_loop, test_loop_marker_is_not_forwarded_to_provider_transport
    • Reported version: 1.15.0
    • Current version: 1.15.0
  4. added
    agentsUsage: [Issues, PRs], Target: Single agent
    middlewareUsage: [Issues, PRs], Target: middleware related features
    and removed
    triageUsage: [Issues], Target: All issues that still need to be triaged
    on Aug 22, 2026
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 agentmiddlewareUsage: [Issues, PRs], Target: middleware related featurespythonUsage: [Issues, PRs], Target: PythonreproducedUsage: [Issues], Target: all issues that can be reproduced by the triage workflow

Type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions