Skip to content

.NET: Python: [Bug]: InvokeAzureAgent silently drops input.arguments, never sends them to the agent #7902

Description

@AnaNeri

Description

InvokeAzureAgentExecutor (the executor backing the declarative
InvokeAzureAgent action) accepts a .NET-style input.arguments mapping,
as documented in its own class docstring and used by the repo's own
customer_support sample (ticket_agent, escalate_agent, resolution_agent
actions in
python/samples/getting_started/workflows/declarative/customer_support/workflow.yaml,
which invoke TicketingAgent / TicketEscalationAgent / TicketResolutionAgent
with only input.arguments, no input.messages).

In _build_input_text()
(agent_framework_declarative/_workflows/_executors_agents.py), every entry
of input.arguments is evaluated into a local evaluated_args dict — and
then never used again:

async def _build_input_text(self, state, arguments, messages_expr) -> str:
    # Evaluate arguments
    evaluated_args: dict[str, Any] = {}
    for key, value in arguments.items():
        evaluated_args[key] = state.eval_if_expression(value)

    # Evaluate messages/input
    if messages_expr:
        ...
    # evaluated_args is not read from this point on
    ...
    return input_text if input_text else ""

evaluated_args is dropped: it isn't concatenated into the returned text,
and isn't stored anywhere in workflow state. Note that options/run_kwargs
is not a viable channel for it either — that dict is populated from the
kwargs passed to workflow.run() (via WORKFLOW_RUN_KWARGS_KEY) so that
tools can see them; it has nothing to do with input.arguments, and
SupportsAgentRun.run() has no separate "arguments" parameter at all, only
messages. So messages and arguments are two distinct inputs on the
declarative-action side (_build_input_text(state, arguments, messages_expr)),
but only one channel — the returned text, which becomes the message sent to
agent.run() — actually reaches the agent. The fix has to fold
evaluated_args into that returned text (the way the existing
Workflow.Inputs fallback already formats multiple values as key: value
lines), not thread them through options. As a result, any
InvokeAzureAgent action that relies on input.arguments without also
specifying input.messages sends the agent no information about those
arguments at all — in the reproduction below, the agent is invoked with an
empty string.

The equivalent .NET implementation
(InvokeAzureAgentExecutor.cs, InvokeAgentAsync, lines 63-64) evaluates the
structured inputs via GetStructuredInputs() into inputParameters and
threads them into agentProvider.InvokeAgentAsync(...), so this is a
Python-only regression relative to .NET feature parity, not an intentional
behavior difference.

The same "evaluate into evaluated_args, then never use it" pattern also
exists in the older interpreter-based action handler,
_actions_agents.py::_run_invoke_azure_agent (not currently wired into
WorkflowFactory, but shipped in the package) — so this isn't isolated to
one code path.

Expected behavior: the evaluated input.arguments values are included in
(or otherwise made available to) the input sent to the agent, matching the
documented schema and the .NET behavior.

Actual behavior: input.arguments are evaluated and silently discarded;
the agent receives none of that data.

Steps to reproduce:

  1. Define a declarative workflow with an InvokeAzureAgent action that uses
    input.arguments and no input.messages (see the workflow definition
    below).
  2. Run the workflow with WorkflowFactory, using an agent that records what
    it's asked to run.
  3. Observe that the agent receives an empty string instead of the arguments.

A minimal, self-contained repro (using a stub agent, no Azure credentials
required) is included below.

Code Sample

Workflow definition:

kind: Workflow
trigger:
  kind: OnConversationStart
  id: main
  actions:
    - kind: InvokeAzureAgent
      id: ticket_agent
      agent:
        name: TicketingAgent
      input:
        arguments:
          IssueDescription: "The printer on the 3rd floor is jammed."
          AttemptedResolutionSteps: "Restarted the printer twice."
      output:
        resultProperty: Local.AgentReply


Reproduction script:

import asyncio
from pathlib import Path
from typing import Any

from agent_framework.declarative import WorkflowFactory


class RecordingAgent:
    """Duck-typed stand-in for a real agent (see `SupportsAgentRun` protocol).

    Just records what it was asked to run instead of calling a model.
    """

    def __init__(self, name: str) -> None:
        self.id = f"{name}-001"
        self.name = name
        self.description = "Records the input it receives; does not call a model."
        self.received_input: Any = "<agent.run() was never called>"

    async def run(self, received_input: Any = None, *, options: Any = None, **kwargs: Any) -> Any:
        self.received_input = received_input

        class _Result:
            text = "(stub response)"

        return _Result()

    def get_new_thread(self, **kwargs: Any) -> dict[str, Any]:
        return {"id": "stub-thread", "messages": []}


async def main() -> None:
    agent = RecordingAgent("TicketingAgent")

    factory = WorkflowFactory(agents={"TicketingAgent": agent})
    workflow = factory.create_workflow_from_yaml_path(Path(__file__).parent / "workflow.yaml")

    async for _event in workflow.run({}, stream=True):
        pass

    defined_arguments = {
        "IssueDescription": "The printer on the 3rd floor is jammed.",
        "AttemptedResolutionSteps": "Restarted the printer twice.",
    }

    agent_was_invoked = agent.received_input != "<agent.run() was never called>"
    received_text = str(agent.received_input).lower()
    missing_arguments = [
        name for name, value in defined_arguments.items() if value.lower() not in received_text
    ]

    print("input.arguments defined in workflow.yaml:")
    print(f"  {defined_arguments}")
    print()
    print("What TicketingAgent.run() actually received:")
    print(f"  {agent.received_input!r}")
    print()

    if not agent_was_invoked:
        print("BUG: TicketingAgent.run() was never even called.")
    elif missing_arguments:
        print(
            "BUG CONFIRMED: TicketingAgent.run() WAS called (the workflow -> agent "
            "wiring works), but what it received does not include the values from "
            f"input.arguments: {missing_arguments}. _build_input_text() evaluates "
            "them into `evaluated_args` and then never reads that variable again, "
            "so the arguments are silently dropped before the agent is invoked."
        )
    else:
        print("OK: input.arguments made it to the agent. The bug appears fixed.")


if __name__ == "__main__":
    asyncio.run(main())

Error Messages / Stack Traces

None — this is a silent data-loss bug, not an exception. Running the script
above prints:


input.arguments defined in workflow.yaml:
  {'IssueDescription': 'The printer on the 3rd floor is jammed.', 'AttemptedResolutionSteps': 'Restarted the printer twice.'}

What TicketingAgent.run() actually received:
  ''

BUG CONFIRMED: TicketingAgent.run() WAS called (the workflow -> agent wiring works), but what it received does not include the values from input.arguments: ['IssueDescription', 'AttemptedResolutionSteps']. _build_input_text() evaluates them into `evaluated_args` and then never reads that variable again, so the arguments are silently dropped before the agent is invoked.


i.e. `TicketingAgent.run()` is called (the workflow → agent wiring works),
but what it receives is `''` instead of something that reflects
`IssueDescription` / `AttemptedResolutionSteps`.

Package Versions

agent-framework-declarative: 1.0.3, agent-framework: 1.15.0, agent-framework-core: 1.15.0 (also reproduces on agent-framework-declarative 1.0.0rc2 / agent-framework 1.11.0 / agent-framework-core 1.11.0, and on microsoft/agent-framework's main branch as of 2026-08-19)

Python Version

Python 3.13.1

Additional Context

Workaround: pass the same values via input.messages instead of
input.arguments (e.g. compose a single message string yourself), or set
Local.input / Local.userInput directly before the action runs.

Metadata

Metadata

Labels

.NETUsage: [Issues, PRs], Target: .NetdeclarativeUsage: [Issues, PRs], Target: declarative agents and workflowspythonUsage: [Issues, PRs], Target: PythonreproducedUsage: [Issues], Target: all issues that can be reproduced by the triage workflow

Type

Projects

Status
No status

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions