Skip to content

[Feature Request] Improve custom function-tool ergonomics and safety in the OpenAI Agents integration #1718

Description

@jaeyoung0509

Is your feature request related to a problem? Please describe.

I am building a durable research agent using temporalio.contrib.openai_agents.

The agent uses custom tools that perform external I/O, such as:

  • Web search through Firecrawl or similar services
  • HTTP API calls
  • Database access
  • Object storage or messaging operations

The integration makes it possible to run the OpenAI Agents Runner inside a Temporal Workflow while routing model invocations through Temporal Activities.

However, custom function tools that perform external I/O still require several separate steps:

  1. Define the operation as a Temporal Activity
  2. Wrap the Activity using openai_agents.workflow.activity_as_tool(...)
  3. Configure Activity timeouts and retry policies
  4. Register the Activity explicitly with the Worker

For example:

@activity.defn
async def search_web(query: str) -> str:
    return await firecrawl.search(query)


search_tool = openai_agents.workflow.activity_as_tool(
    search_web,
    start_to_close_timeout=timedelta(seconds=30),
    retry_policy=RetryPolicy(
        maximum_attempts=3,
    ),
)


agent = Agent(
    name="Researcher",
    tools=[search_tool],
)

This is manageable for one tool, but becomes repetitive when an agent has many external tools.

It is also easy for developers to accidentally use a regular OpenAI Agents @function_tool for an operation that performs network or database I/O:

@function_tool
async def search_web(query: str) -> str:
    return await firecrawl.search(query)

Because the tool appears valid from the OpenAI Agents SDK perspective, it may not be obvious that it can execute inside the Workflow rather than through an Activity.

This creates two problems:

  1. A developer-experience gap between normal OpenAI Agents SDK code and the Temporal integration
  2. A safety risk where non-deterministic or external I/O is accidentally executed inside a Workflow

I understand that the SDK cannot reliably inspect an arbitrary Python function and determine whether it performs I/O. I also understand that Activities require explicit operational semantics such as timeouts, retries, cancellation behavior, and task queues.

The request is therefore not necessarily to convert every function tool into an Activity automatically, but to provide a clearer and less repetitive abstraction for durable tools.

Describe the solution you'd like

I would like the integration to provide an explicit first-class abstraction for an OpenAI Agent tool backed by a Temporal Activity.

For example:

@openai_agents.activity_tool(
    start_to_close_timeout=timedelta(seconds=30),
    retry_policy=RetryPolicy(
        maximum_attempts=3,
    ),
)
async def search_web(query: str) -> str:
    return await firecrawl.search(query)

The resulting object could be used directly as an Agent tool:

agent = Agent(
    name="Researcher",
    tools=[search_web],
)

Ideally, this abstraction would:

  • Define or expose the underlying Temporal Activity
  • Produce an OpenAI Agents-compatible tool
  • Preserve the function name, description, and input schema
  • Allow Activity options such as timeout, retry policy, heartbeat timeout, and task queue
  • Reduce duplication between Activity definitions and Agent tool definitions
  • Make the execution boundary explicit to readers of the code

If automatic Worker registration is not desirable, the decorated tool could expose its Activity separately:

worker = Worker(
    client,
    task_queue="research-agent",
    workflows=[ResearchWorkflow],
    activities=[
        search_web.activity,
    ],
)

Another useful addition would be an optional strict mode for Workflow-local function tools.

For example:

OpenAIAgentsPlugin(
    workflow_function_tools="reject",
)

With this enabled, passing a regular FunctionTool to an Agent running inside a Workflow could fail with an actionable error such as:

FunctionTool 'search_web' will execute inside the Workflow.

Use activity_as_tool() or @activity_tool for tools that perform
network, database, filesystem, or other external I/O.

Explicitly mark the tool as deterministic if Workflow-local execution
is intentional.

A deterministic local tool could then be explicitly allowed:

@openai_agents.workflow_tool
@function_tool
def calculate_score(value: int) -> int:
    return value * 2

The exact API names are only suggestions. The main goals are:

  1. Make Activity-backed Agent tools first-class
  2. Reduce the boilerplate required for each custom tool
  3. Prevent accidental external I/O inside Workflows
  4. Clearly distinguish deterministic Workflow-local tools from durable external tools

Additional context

A common production agent loop looks like this:

Temporal Workflow
└── OpenAI Agents Runner
    ├── Model invocation Activity
    ├── Web search Activity
    ├── Page scraping Activity
    ├── Database Activity
    └── Model invocation Activity

The existing model integration already provides a convenient abstraction for durable model invocations.

Custom tools are often the next major source of external I/O, retries, rate limits, timeouts, and partial failures. Making Activity-backed tools equally explicit and ergonomic would make the integration easier to adopt for production agents.

This is especially relevant for research agents using services such as Firecrawl, Tavily, or custom search APIs. Search and scraping should generally be separate Activity boundaries so they can have independent retries, timeouts, observability, and failure handling.

Clearer documentation would also help. In particular, it would be useful to state prominently that:

  • Model invocations are routed through Temporal Activities by the integration
  • Hosted tools may execute as part of the model invocation
  • Regular custom function tools are not automatically converted into Activities
  • Custom tools performing external I/O should be wrapped with activity_as_tool()
  • Workflow-local function tools should only contain deterministic logic

I would also appreciate guidance on whether this can be implemented entirely in temporalio.contrib.openai_agents, or whether a more general pluggable tool-execution hook would be required from the OpenAI Agents SDK.

Metadata

Metadata

Assignees

No one assigned

    Labels

    ai-sdkRelated to AI integrationsenhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions