OpenAI Agents SDK: stop a tool from running twice on retry or resume

How to make function tools in the OpenAI Agents SDK once-only across retries, resumed runs and approvals, with a runnable example.

Where repeats come from

The OpenAI Agents SDK can pause a run for human approval (needs_approval=True), store the run as JSON with state.to_json(), and resume it later with RunState.from_json(...) and Runner.run(agent, state). Its documentation also asks you to "only deserialize snapshots from trusted storage". If your process stops after a tool ran but before you stored the new state, resuming from the older state can run the tool again. Retries after timeouts have the same effect.

Once-only function tools

The Call4Agents adapter wraps a function tool. The SDK's decorator goes on top. The context argument is left out of the key (it changes on every call), and the run id comes from your context or from c4a.run(...):

from agents import Agent, Runner, RunContextWrapper, function_tool
import call4agents as c4a
from call4agents.adapters import openai_agents as c4a_oa

c4a.init()

@function_tool
@c4a_oa.effect                     # once per session, tool and exact arguments
def send_email(ctx: RunContextWrapper[dict], to: str, body: str) -> str:
    """Send an email."""
    return mailer.send(to=to, body=body)

agent = Agent(name="mailer", tools=[send_email])
await Runner.run(agent, "email bob", context={"session_id": "session-1"})

A runnable version with a fake model (no API key) prints {'emails_sent': 1}; the same code without the kit sends 2:

pip install call4agents openai-agents
python -m call4agents.examples.openai_agents_tool

For approvals, @c4a_oa.effect(approval=True) with c4a.approve(...) binds the "yes" to the exact arguments and lets it be used once.

Related guides