Skip to content

Python: [Bug]: Nested WorkflowExecutor response resume drops request-scoped runtime tools #8344

Description

Description

A nested WorkflowExecutor forwards request-scoped runtime tools to its child workflow during the initial invocation and nested cancellation, but currently drops the tools when the child is resumed from a response.

I reproduced this on the current canonical main at:

3c670707766a8455da6491a9049cc9d575e019f0

The affected path is WorkflowExecutor._handle_response(), which currently resumes the child with:

await self.workflow.run(responses={request_id: response})

As a result, when the parent continuation is invoked with responses=... and tools=..., the resumed child response handler observes None from ctx.get_runtime_tools().

Expected behavior

Request-scoped runtime tools supplied to the parent continuation should be available to the resumed nested child.

This matches the behavior of the initial nested invocation and nested cancellation paths, which already forward the active runtime tools.

Actual behavior

The parent installs the continuation tools on its own runner context, but the nested child starts a separate Workflow.run() without those tools, leaving the child's runtime-tool context empty.

Code Sample

import asyncio
from typing import Any

from agent_framework import (
    Executor,
    WorkflowBuilder,
    WorkflowContext,
    WorkflowExecutor,
    handler,
    response_handler,
)


async def main() -> None:
    captured_tools: list[Any] = []

    class RequestingExecutor(Executor):
        @handler
        async def start(self, input_data: str, ctx: WorkflowContext) -> None:
            del input_data
            await ctx.request_info(
                "Continue?",
                str,
                request_id="child-request",
            )

        @response_handler
        async def resume(
            self,
            request: str,
            response: str,
            ctx: WorkflowContext,
        ) -> None:
            del request, response
            captured_tools.append(ctx.get_runtime_tools())

    child = WorkflowBuilder(
        start_executor=RequestingExecutor(id="child-requester")
    ).build()

    parent = WorkflowBuilder(
        start_executor=WorkflowExecutor(
            child,
            id="child",
            propagate_request=True,
        )
    ).build()

    first_result = await parent.run("start")
    assert [
        event.request_id
        for event in first_result.get_request_info_events()
    ] == ["child-request"]

    runtime_tool = object()

    await parent.run(
        responses={"child-request": "yes"},
        tools=[runtime_tool],
    )

    assert captured_tools == [[runtime_tool]]


asyncio.run(main())

Error Messages / Stack Traces

No framework exception is raised.

The continuation succeeds and the child response handler executes, but the request-scoped runtime-tool context is lost:


assert [None] == [[<object object at 0x...>]]

At index 0 diff: None != [<object object at 0x...>]

Package Versions

agent-framework-core: 1.18.0 (editable source checkout at 3c67070; not separately reproduced against the published 1.18.0 wheel)

Python Version

Python 3.12.12

Additional Context

This appears to be a regression of previously supported behavior.

PR #7776 added tools=ctx.get_runtime_tools() to this exact nested response-continuation call site.

That runtime-tool argument is no longer present after the nested workflow invocation-kwargs changes in #7963.

Runtime tools are request-scoped, cleared after each workflow run, and are not checkpointed, so there is no persisted mechanism that supplies the parent continuation's tools to the resumed child.

I have a minimal fix and provider-free regression coverage ready locally. I verified the regression RED before the change and GREEN afterward, and the patched checkout passes the relevant workflow tests, the full core suite, syntax/format checks, source typing, test typing, aggregate core checks, and the core build.

The proposed change is intentionally narrow:

  • forward the active continuation's runtime tools when resuming the nested child;
  • leave stored function_invocation_kwargs and client_kwargs behavior unchanged;
  • leave nested cancellation behavior unchanged;
  • leave checkpoint state and serialization unchanged;
  • make no public API, exception-handling, or response-correlation changes.

This is a functional-correctness issue; I am not making a security-impact claim.

I would like to take this on and contribute the implementation. The minimal tested patch is already ready locally.

Per the repository guidance for approval/resume-related changes, I am checking with the core team while making the concrete implementation available as a draft for review.

Activity

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

Metadata

Metadata

Labels

pythonUsage: [Issues, PRs], Target: PythonreproducedUsage: [Issues], Target: all issues that can be reproduced by the triage workflowworkflowsUsage: [Issues, PRs], Target: Workflows

Type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions