From b24e74551962b90c54be5e27e8a9e5b4464c5e5f Mon Sep 17 00:00:00 2001 From: eavanvalkenburg Date: Wed, 7 Oct 2026 14:26:20 +0200 Subject: [PATCH 1/3] Python: Foundry hosting: warn against hosting a WorkflowAgent through agent= Hosting a WorkflowAgent through agent= is deprecated in favor of native workflow= hosting, but only ResponsesHostServer warned, and without saying why. A WorkflowAgent is stateful, so one instance must never serve requests from different users or conversations. - Warn from InvocationsHostServer too, once per host: at construction for an instance, on the first request for a factory that returns a WorkflowAgent. - Say in both warnings that the path should be avoided and that a WorkflowAgent is stateful. - README: document the deprecation and the native alternative. The agent parameter docstrings of both hosts carry the same note. --- python/packages/foundry_hosting/README.md | 35 +++++++--- .../_invocations.py | 22 ++++++ .../_responses.py | 7 +- .../foundry_hosting/test_native_responses.py | 4 ++ .../foundry_hosting/tests/test_invocations.py | 68 +++++++++++++++++++ 5 files changed, 126 insertions(+), 10 deletions(-) diff --git a/python/packages/foundry_hosting/README.md b/python/packages/foundry_hosting/README.md index f992d3b091d..7ed0fe0a772 100644 --- a/python/packages/foundry_hosting/README.md +++ b/python/packages/foundry_hosting/README.md @@ -13,9 +13,27 @@ server = ResponsesHostServer(agent=create_agent) ``` Passing an instance reuses that object for the lifetime of the host. Pass a callable when the agent keeps mutable state -outside `AgentSession`. In particular, a `WorkflowAgent` wraps a stateful workflow, so its callable should build a new -workflow, executors, and wrapped agents. Keep the workflow name and executor IDs stable so later requests can find and -restore its checkpoints: +outside `AgentSession`. The Responses host continues regular agents through its existing session store and workflow +agents through its existing checkpoint store. A callable does not make arbitrary instance fields persistent; state +needed by later requests must remain in the supported stores. + +### Hosting a `WorkflowAgent` is deprecated + +Hosting a `WorkflowAgent` (for example `workflow.as_agent()`) through `agent=` is deprecated and should be avoided, and +both `ResponsesHostServer` and `InvocationsHostServer` emit a `DeprecationWarning` for it. A `WorkflowAgent` is +stateful: its workflow state stays in memory between runs, so one instance must never serve requests from different +users or conversations. Host the workflow natively with `workflow=` and a request-aware factory instead; see +[Native Responses workflows](#native-responses-workflows) and +[Native Invocations workflows](#native-invocations-workflows): + +```python +# build_workflow(request) returns a freshly built Workflow for each request. +server = ResponsesHostServer(workflow=build_workflow, parse_response=parse_response) +``` + +Until you migrate, pass a callable that builds a new workflow, executors, and wrapped agents for every request, and +never return a shared instance. Keep the workflow name and executor IDs stable so later requests can find and restore +its checkpoints: ```python def create_agent(): @@ -25,10 +43,6 @@ def create_agent(): server = ResponsesHostServer(agent=create_agent) ``` -The Responses host continues regular agents through its existing session store and workflow agents through its -existing checkpoint store. A callable does not make arbitrary instance fields persistent; state needed by later -requests must remain in the supported stores. - ## Native Responses workflows Use `workflow=` to host a built `Workflow` directly, without `.as_agent()`. @@ -98,7 +112,9 @@ only the current Responses turn to `list[Message]`. It does not load outer history or decode pending replies. Existing `agent=workflow.as_agent()` hosting remains for this beta with a once-per-host deprecation warning because wrapper context providers, history, event projection, and request-info translation are -real semantics and are not silently unwrapped. +real semantics and are not silently unwrapped. See +[Hosting a `WorkflowAgent` is deprecated](#hosting-a-workflowagent-is-deprecated) +for the instance-sharing restriction. With `resilient_background=True`, the application must also enable the AgentServer resilient task subsystem. Host-owned checkpoint/output pairs @@ -428,7 +444,8 @@ storage locally. Stored approvals are scoped under `function_approvals`. ## Native Invocations workflows Host a native workflow with an explicit application parser rather than wrapping it -with `workflow.as_agent()`: +with `workflow.as_agent()`, which is deprecated for hosting (see +[Hosting a `WorkflowAgent` is deprecated](#hosting-a-workflowagent-is-deprecated)): ```python server = InvocationsHostServer( diff --git a/python/packages/foundry_hosting/agent_framework_foundry_hosting/_invocations.py b/python/packages/foundry_hosting/agent_framework_foundry_hosting/_invocations.py index 0c038cc2f5b..af1310190ea 100644 --- a/python/packages/foundry_hosting/agent_framework_foundry_hosting/_invocations.py +++ b/python/packages/foundry_hosting/agent_framework_foundry_hosting/_invocations.py @@ -29,6 +29,7 @@ SessionStore, SupportsAgentRun, Workflow, + WorkflowAgent, WorkflowCheckpointException, WorkflowEvent, WorkflowExecutor, @@ -347,6 +348,8 @@ def __init__( Args: agent: The agent to handle responses for, or a zero-argument sync or async callable that creates one for each request. Use a callable for agents that keep mutable state outside `AgentSession`. + Hosting a `WorkflowAgent` here is deprecated and should be avoided: it is stateful, so one instance + must never serve requests from different users or conversations. Use `workflow=` instead. workflow: A built native workflow or a sync/async request-aware factory returning a freshly built workflow with stable graph/executor IDs. Hosted workflows require a factory. Supply exactly one of agent or workflow; local workflow instances are single-use and remain application-owned. @@ -402,6 +405,7 @@ def __init__( self._agent = agent self._owns_request_agent = agent is not None and not is_agent(agent) + self._warned_workflow_agent = False self._parse_request = parse_request self._prepare_options = prepare_options self._unsupported_options = validate_unsupported_options(unsupported_options) @@ -424,6 +428,8 @@ def __init__( ) warnings.warn(message, DeprecationWarning, stacklevel=2) logger.warning("DEPRECATION: %s", message) + if isinstance(agent, WorkflowAgent): + self._warn_legacy_workflow() self.invoke_handler(self._handle_invoke) mark_feature_used(FeatureIndex.FOUNDRY_HOSTING) @@ -493,11 +499,27 @@ def _hosted_scope(self, request: Request, context: FoundryAgentRequestContext) - is_hosted=True, ) + def _warn_legacy_workflow(self) -> None: + if not self._warned_workflow_agent: + self._warned_workflow_agent = True + warnings.warn( + "Hosting WorkflowAgent through agent= is deprecated for this beta release and should be avoided. " + "A WorkflowAgent is stateful and keeps workflow state in memory between runs, so one instance must " + "never serve requests from different users or conversations. " + "Use workflow=a_request_aware_factory with an explicit parse_request. " + "Until you migrate, pass a factory that builds a new WorkflowAgent for every request. " + "Wrapper history, context providers, and event semantics are not automatically unwrapped.", + DeprecationWarning, + stacklevel=3, + ) + @asynccontextmanager async def _request_agent(self) -> AsyncGenerator[SupportsAgentRun]: if self._agent is None: raise RuntimeError("No agent is configured for Invocations.") agent = await resolve_agent(self._agent) + if isinstance(agent, WorkflowAgent): + self._warn_legacy_workflow() async with AsyncExitStack() as resources: if self._owns_request_agent and isinstance(agent, AbstractAsyncContextManager): await resources.enter_async_context(agent) diff --git a/python/packages/foundry_hosting/agent_framework_foundry_hosting/_responses.py b/python/packages/foundry_hosting/agent_framework_foundry_hosting/_responses.py index 93bc39c7fdd..e9989e1d991 100644 --- a/python/packages/foundry_hosting/agent_framework_foundry_hosting/_responses.py +++ b/python/packages/foundry_hosting/agent_framework_foundry_hosting/_responses.py @@ -774,6 +774,8 @@ def __init__( Args: agent: The agent to handle responses for, or a zero-argument sync or async callable that creates one for each request. Use a callable for agents that keep mutable state outside `AgentSession`. + Hosting a `WorkflowAgent` here is deprecated and should be avoided: it is stateful, so one instance + must never serve requests from different users or conversations. Use `workflow=` instead. workflow: A built, unrun native workflow for one-shot execution, or a request-aware sync/async factory returning fresh built graphs, executors, agents, clients, tools, and providers with stable graph/executor IDs. Cannot be combined with ``agent``. @@ -996,8 +998,11 @@ def _warn_legacy_workflow(self) -> None: if not self._warned_workflow_agent: self._warned_workflow_agent = True warnings.warn( - "Hosting WorkflowAgent through agent= is deprecated for this beta release. " + "Hosting WorkflowAgent through agent= is deprecated for this beta release and should be avoided. " + "A WorkflowAgent is stateful and keeps workflow state in memory between runs, so one instance must " + "never serve requests from different users or conversations. " "Use workflow=a_request_aware_factory with an explicit parse_response. " + "Until you migrate, pass a factory that builds a new WorkflowAgent for every request. " "Wrapper history, context providers, approvals, and event semantics are not automatically unwrapped.", DeprecationWarning, stacklevel=3, diff --git a/python/packages/foundry_hosting/tests/foundry_hosting/test_native_responses.py b/python/packages/foundry_hosting/tests/foundry_hosting/test_native_responses.py index 322ea5d64ad..5de0940ead8 100644 --- a/python/packages/foundry_hosting/tests/foundry_hosting/test_native_responses.py +++ b/python/packages/foundry_hosting/tests/foundry_hosting/test_native_responses.py @@ -1004,3 +1004,7 @@ def create() -> Any: assert _texts(first) == ["legacy"] assert _texts(second) == ["again"] assert sum("Hosting WorkflowAgent" in str(item.message) for item in captured) == 1 + message = next(str(item.message) for item in captured if "Hosting WorkflowAgent" in str(item.message)) + assert "should be avoided" in message + assert "stateful" in message + assert "parse_response" in message diff --git a/python/packages/foundry_hosting/tests/test_invocations.py b/python/packages/foundry_hosting/tests/test_invocations.py index 8375d41daf6..bfce7c05cca 100644 --- a/python/packages/foundry_hosting/tests/test_invocations.py +++ b/python/packages/foundry_hosting/tests/test_invocations.py @@ -15,10 +15,12 @@ import json import logging import uuid +import warnings import weakref from collections.abc import AsyncIterator, Awaitable, Callable, Iterator, Mapping, Sequence from contextlib import contextmanager from itertools import product +from pathlib import Path from typing import Literal, cast from unittest.mock import AsyncMock, MagicMock, patch @@ -34,12 +36,17 @@ ChatResponse, ChatResponseUpdate, Content, + Executor, FunctionInvocationLayer, InMemoryHistoryProvider, Message, ResponseStream, ServiceSessionId, SessionStore, + WorkflowAgent, + WorkflowBuilder, + WorkflowContext, + handler, tool, ) from azure.ai.agentserver.core import ( @@ -288,6 +295,30 @@ async def _success_text(response: Response) -> str: return json.loads(bytes(response.body))["response"] +class _EchoExecutor(Executor): + def __init__(self) -> None: + super().__init__("echo") + + @handler + async def echo(self, messages: list[Message], ctx: WorkflowContext[str, str]) -> None: + await ctx.yield_output(messages[-1].text) + + +def _make_workflow_agent() -> WorkflowAgent: + return WorkflowBuilder(name="legacy", start_executor=_EchoExecutor()).build().as_agent() + + +def _workflow_agent_warnings(captured: Sequence[warnings.WarningMessage]) -> list[warnings.WarningMessage]: + return [item for item in captured if "Hosting WorkflowAgent" in str(item.message)] + + +async def _invoke_text(server: InvocationsHostServer, message: str) -> str: + request = _make_request({"message": message}) + with _request_context(session_id="session"): + response = await server._handle_invoke(request) # pyright: ignore[reportPrivateUsage] + return await _success_text(response) + + # endregion @@ -648,6 +679,43 @@ def test_rejects_invalid_configuration(self, parameter: str, value: Any, error: InvocationsHostServer(_make_agent(response_text="ok"), **{parameter: value}) +class TestLegacyWorkflowAgentHosting: + async def test_instance_warns_once_per_host(self) -> None: + with warnings.catch_warnings(record=True) as captured: + warnings.simplefilter("always", DeprecationWarning) + server = InvocationsHostServer(_make_workflow_agent()) + texts = [await _invoke_text(server, "one"), await _invoke_text(server, "two")] + + deprecations = _workflow_agent_warnings(captured) + assert len(deprecations) == 1 + assert deprecations[0].category is DeprecationWarning + message = str(deprecations[0].message) + assert "should be avoided" in message + assert "stateful" in message + assert "parse_request" in message + assert Path(deprecations[0].filename).name == Path(__file__).name + assert texts == ["one", "two"] + + async def test_factory_warns_once_on_first_request(self) -> None: + with warnings.catch_warnings(record=True) as captured: + warnings.simplefilter("always", DeprecationWarning) + server = InvocationsHostServer(_make_workflow_agent) + assert _workflow_agent_warnings(captured) == [] + texts = [await _invoke_text(server, "one"), await _invoke_text(server, "two")] + + assert len(_workflow_agent_warnings(captured)) == 1 + assert texts == ["one", "two"] + + async def test_regular_agent_does_not_warn(self) -> None: + with warnings.catch_warnings(record=True) as captured: + warnings.simplefilter("always", DeprecationWarning) + server = InvocationsHostServer(_make_agent(response_text="hi")) + text = await _invoke_text(server, "hello") + + assert text == "hi" + assert _workflow_agent_warnings(captured) == [] + + # endregion From 1912c6d1251f451aba1ee5225c57b08220e7b5e5 Mon Sep 17 00:00:00 2001 From: eavanvalkenburg Date: Wed, 7 Oct 2026 14:39:38 +0200 Subject: [PATCH 2/3] Python: Foundry hosting: also log the WorkflowAgent hosting deprecation A DeprecationWarning raised while serving a request is attributed to library code, so Python's default filter hides it when a factory returns a WorkflowAgent. Log the same message once per host at WARNING level as well, as legacy_wire_format already does, so the deprecation is visible regardless of warning filters. The README mentions the log line. --- python/packages/foundry_hosting/README.md | 7 ++++--- .../_invocations.py | 10 ++++++---- .../_responses.py | 10 ++++++---- .../foundry_hosting/test_native_responses.py | 6 +++++- .../foundry_hosting/tests/test_invocations.py | 18 +++++++++++++++--- 5 files changed, 36 insertions(+), 15 deletions(-) diff --git a/python/packages/foundry_hosting/README.md b/python/packages/foundry_hosting/README.md index 7ed0fe0a772..00963610828 100644 --- a/python/packages/foundry_hosting/README.md +++ b/python/packages/foundry_hosting/README.md @@ -20,9 +20,10 @@ needed by later requests must remain in the supported stores. ### Hosting a `WorkflowAgent` is deprecated Hosting a `WorkflowAgent` (for example `workflow.as_agent()`) through `agent=` is deprecated and should be avoided, and -both `ResponsesHostServer` and `InvocationsHostServer` emit a `DeprecationWarning` for it. A `WorkflowAgent` is -stateful: its workflow state stays in memory between runs, so one instance must never serve requests from different -users or conversations. Host the workflow natively with `workflow=` and a request-aware factory instead; see +both `ResponsesHostServer` and `InvocationsHostServer` emit a `DeprecationWarning` for it and log the same message at +`WARNING` level, once per host. A `WorkflowAgent` is stateful: its workflow state stays in memory between runs, so one +instance must never serve requests from different users or conversations. Host the workflow natively with `workflow=` +and a request-aware factory instead; see [Native Responses workflows](#native-responses-workflows) and [Native Invocations workflows](#native-invocations-workflows): diff --git a/python/packages/foundry_hosting/agent_framework_foundry_hosting/_invocations.py b/python/packages/foundry_hosting/agent_framework_foundry_hosting/_invocations.py index af1310190ea..0823ce441f6 100644 --- a/python/packages/foundry_hosting/agent_framework_foundry_hosting/_invocations.py +++ b/python/packages/foundry_hosting/agent_framework_foundry_hosting/_invocations.py @@ -502,16 +502,18 @@ def _hosted_scope(self, request: Request, context: FoundryAgentRequestContext) - def _warn_legacy_workflow(self) -> None: if not self._warned_workflow_agent: self._warned_workflow_agent = True - warnings.warn( + message = ( "Hosting WorkflowAgent through agent= is deprecated for this beta release and should be avoided. " "A WorkflowAgent is stateful and keeps workflow state in memory between runs, so one instance must " "never serve requests from different users or conversations. " "Use workflow=a_request_aware_factory with an explicit parse_request. " "Until you migrate, pass a factory that builds a new WorkflowAgent for every request. " - "Wrapper history, context providers, and event semantics are not automatically unwrapped.", - DeprecationWarning, - stacklevel=3, + "Wrapper history, context providers, and event semantics are not automatically unwrapped." ) + warnings.warn(message, DeprecationWarning, stacklevel=3) + # Request-time calls cannot be attributed to application code, so Python's default filter hides the + # warning there; log it as well. + logger.warning("DEPRECATION: %s", message) @asynccontextmanager async def _request_agent(self) -> AsyncGenerator[SupportsAgentRun]: diff --git a/python/packages/foundry_hosting/agent_framework_foundry_hosting/_responses.py b/python/packages/foundry_hosting/agent_framework_foundry_hosting/_responses.py index e9989e1d991..1d4d684e8ac 100644 --- a/python/packages/foundry_hosting/agent_framework_foundry_hosting/_responses.py +++ b/python/packages/foundry_hosting/agent_framework_foundry_hosting/_responses.py @@ -997,16 +997,18 @@ def __init__( def _warn_legacy_workflow(self) -> None: if not self._warned_workflow_agent: self._warned_workflow_agent = True - warnings.warn( + message = ( "Hosting WorkflowAgent through agent= is deprecated for this beta release and should be avoided. " "A WorkflowAgent is stateful and keeps workflow state in memory between runs, so one instance must " "never serve requests from different users or conversations. " "Use workflow=a_request_aware_factory with an explicit parse_response. " "Until you migrate, pass a factory that builds a new WorkflowAgent for every request. " - "Wrapper history, context providers, approvals, and event semantics are not automatically unwrapped.", - DeprecationWarning, - stacklevel=3, + "Wrapper history, context providers, approvals, and event semantics are not automatically unwrapped." ) + warnings.warn(message, DeprecationWarning, stacklevel=3) + # Request-time calls cannot be attributed to application code, so Python's default filter hides the + # warning there; log it as well. + logger.warning("DEPRECATION: %s", message) async def _ensure_agent_ready(self) -> None: """Lazily enter the agent's async context exactly once. diff --git a/python/packages/foundry_hosting/tests/foundry_hosting/test_native_responses.py b/python/packages/foundry_hosting/tests/foundry_hosting/test_native_responses.py index 5de0940ead8..e1583ded9a1 100644 --- a/python/packages/foundry_hosting/tests/foundry_hosting/test_native_responses.py +++ b/python/packages/foundry_hosting/tests/foundry_hosting/test_native_responses.py @@ -983,7 +983,9 @@ async def stage(run: HostedWorkflowRun, snapshot: Any, **kwargs: Any) -> None: set_resilient_tasks_enabled(enabled) -async def test_legacy_wrapper_warns_once_per_host_and_keeps_message_semantics() -> None: +async def test_legacy_wrapper_warns_once_per_host_and_keeps_message_semantics( + caplog: pytest.LogCaptureFixture, +) -> None: class _Legacy(Executor): def __init__(self) -> None: super().__init__("legacy") @@ -1008,3 +1010,5 @@ def create() -> Any: assert "should be avoided" in message assert "stateful" in message assert "parse_response" in message + logged = [record for record in caplog.records if "DEPRECATION: Hosting WorkflowAgent" in record.getMessage()] + assert [record.levelno for record in logged] == [logging.WARNING] diff --git a/python/packages/foundry_hosting/tests/test_invocations.py b/python/packages/foundry_hosting/tests/test_invocations.py index bfce7c05cca..3cb52b7978c 100644 --- a/python/packages/foundry_hosting/tests/test_invocations.py +++ b/python/packages/foundry_hosting/tests/test_invocations.py @@ -312,6 +312,14 @@ def _workflow_agent_warnings(captured: Sequence[warnings.WarningMessage]) -> lis return [item for item in captured if "Hosting WorkflowAgent" in str(item.message)] +def _workflow_agent_logs(caplog: pytest.LogCaptureFixture) -> list[logging.LogRecord]: + return [ + record + for record in caplog.records + if record.levelno == logging.WARNING and "DEPRECATION: Hosting WorkflowAgent" in record.getMessage() + ] + + async def _invoke_text(server: InvocationsHostServer, message: str) -> str: request = _make_request({"message": message}) with _request_context(session_id="session"): @@ -680,7 +688,7 @@ def test_rejects_invalid_configuration(self, parameter: str, value: Any, error: class TestLegacyWorkflowAgentHosting: - async def test_instance_warns_once_per_host(self) -> None: + async def test_instance_warns_once_per_host(self, caplog: pytest.LogCaptureFixture) -> None: with warnings.catch_warnings(record=True) as captured: warnings.simplefilter("always", DeprecationWarning) server = InvocationsHostServer(_make_workflow_agent()) @@ -694,19 +702,22 @@ async def test_instance_warns_once_per_host(self) -> None: assert "stateful" in message assert "parse_request" in message assert Path(deprecations[0].filename).name == Path(__file__).name + assert len(_workflow_agent_logs(caplog)) == 1 assert texts == ["one", "two"] - async def test_factory_warns_once_on_first_request(self) -> None: + async def test_factory_warns_once_on_first_request(self, caplog: pytest.LogCaptureFixture) -> None: with warnings.catch_warnings(record=True) as captured: warnings.simplefilter("always", DeprecationWarning) server = InvocationsHostServer(_make_workflow_agent) assert _workflow_agent_warnings(captured) == [] + assert _workflow_agent_logs(caplog) == [] texts = [await _invoke_text(server, "one"), await _invoke_text(server, "two")] assert len(_workflow_agent_warnings(captured)) == 1 + assert len(_workflow_agent_logs(caplog)) == 1 assert texts == ["one", "two"] - async def test_regular_agent_does_not_warn(self) -> None: + async def test_regular_agent_does_not_warn(self, caplog: pytest.LogCaptureFixture) -> None: with warnings.catch_warnings(record=True) as captured: warnings.simplefilter("always", DeprecationWarning) server = InvocationsHostServer(_make_agent(response_text="hi")) @@ -714,6 +725,7 @@ async def test_regular_agent_does_not_warn(self) -> None: assert text == "hi" assert _workflow_agent_warnings(captured) == [] + assert _workflow_agent_logs(caplog) == [] # endregion From b1ad718b678b56f2e31f0d06695873330b11b234 Mon Sep 17 00:00:00 2001 From: eavanvalkenburg Date: Wed, 7 Oct 2026 14:58:53 +0200 Subject: [PATCH 3/3] Python: Foundry hosting: limit the Invocations factory workaround to stateless workflows InvocationsHostServer does not restore workflow checkpoints for agents, so a factory that builds a new WorkflowAgent for every request only suits stateless, single-turn workflows there: a workflow that pauses or spans turns loses its state. Say so in the Invocations warning and scope the README's interim guidance to ResponsesHostServer, which does restore checkpoints. --- python/packages/foundry_hosting/README.md | 9 ++++++--- .../agent_framework_foundry_hosting/_invocations.py | 3 ++- .../packages/foundry_hosting/tests/test_invocations.py | 1 + 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/python/packages/foundry_hosting/README.md b/python/packages/foundry_hosting/README.md index 00963610828..3a352eba761 100644 --- a/python/packages/foundry_hosting/README.md +++ b/python/packages/foundry_hosting/README.md @@ -32,9 +32,9 @@ and a request-aware factory instead; see server = ResponsesHostServer(workflow=build_workflow, parse_response=parse_response) ``` -Until you migrate, pass a callable that builds a new workflow, executors, and wrapped agents for every request, and -never return a shared instance. Keep the workflow name and executor IDs stable so later requests can find and restore -its checkpoints: +Until you migrate `ResponsesHostServer`, pass a callable that builds a new workflow, executors, and wrapped agents for +every request, and never return a shared instance. Keep the workflow name and executor IDs stable so later requests can +find and restore its checkpoints: ```python def create_agent(): @@ -44,6 +44,9 @@ def create_agent(): server = ResponsesHostServer(agent=create_agent) ``` +`InvocationsHostServer` does not restore workflow checkpoints for agents, so a per-request factory only suits stateless, +single-turn workflows there. Use `workflow=` for any workflow that pauses or spans turns. + ## Native Responses workflows Use `workflow=` to host a built `Workflow` directly, without `.as_agent()`. diff --git a/python/packages/foundry_hosting/agent_framework_foundry_hosting/_invocations.py b/python/packages/foundry_hosting/agent_framework_foundry_hosting/_invocations.py index 0823ce441f6..6838e601f5e 100644 --- a/python/packages/foundry_hosting/agent_framework_foundry_hosting/_invocations.py +++ b/python/packages/foundry_hosting/agent_framework_foundry_hosting/_invocations.py @@ -507,7 +507,8 @@ def _warn_legacy_workflow(self) -> None: "A WorkflowAgent is stateful and keeps workflow state in memory between runs, so one instance must " "never serve requests from different users or conversations. " "Use workflow=a_request_aware_factory with an explicit parse_request. " - "Until you migrate, pass a factory that builds a new WorkflowAgent for every request. " + "A factory that builds a new WorkflowAgent for every request only suits stateless, " + "single-turn workflows, because the Invocations host does not restore workflow checkpoints. " "Wrapper history, context providers, and event semantics are not automatically unwrapped." ) warnings.warn(message, DeprecationWarning, stacklevel=3) diff --git a/python/packages/foundry_hosting/tests/test_invocations.py b/python/packages/foundry_hosting/tests/test_invocations.py index 3cb52b7978c..9b643290de2 100644 --- a/python/packages/foundry_hosting/tests/test_invocations.py +++ b/python/packages/foundry_hosting/tests/test_invocations.py @@ -701,6 +701,7 @@ async def test_instance_warns_once_per_host(self, caplog: pytest.LogCaptureFixtu assert "should be avoided" in message assert "stateful" in message assert "parse_request" in message + assert "stateless, single-turn" in message assert Path(deprecations[0].filename).name == Path(__file__).name assert len(_workflow_agent_logs(caplog)) == 1 assert texts == ["one", "two"]