Skip to main content

Agent Engine SDK OpenAI Agents

License

OpenAI Agents SDK adapter for the MongoDB Atlas Agent Engine SDK.

Status: under construction. The CLI and the build service accept framework: openai-agents; a platform build needs an environment that publishes the Python Lite runner base.

The adapter runs a native OpenAI Agents Agent through the SDK's own Runner while the Orchestration Engine owns durable execution. The adapter keeps no session database and never uses OpenAI Sessions, Conversations, or previous_response_id as continuation state.

Building an agent

from agents import Agent, ModelSettings
from agent_engine_sdk_openai_agents import App

app = App(app_name="support")


@app.tool()
def lookup_order(order_id: str) -> str:
    """Look up an order."""
    ...


@app.entrypoint
def build() -> Agent:
    return Agent(
        name="support",
        instructions="Help with orders.",
        model=app.llm("gpt-4.1", settings=ModelSettings(temperature=0)),
        tools=app.tools(),
    )

Use the model returned by app.llm(...) and the tools returned by app.tools(); anything else fails the turn before any model or tool work. Pass a configured agents.Model to app.llm to use a custom OpenAI client. Tool parameters must be JSON values that can be passed by name (no positional-only or *args parameters, and none named tool_call_id), because remote tools receive their arguments as named JSON values. Set a tool's timeout with @app.tool(timeout=...); a native timeout_seconds on an issued tool fails the turn. A non-string tool result, including nested models, dataclasses, enums, and datetimes, reaches the model as JSON text. The text is canonical, so a replayed turn sends the model the same text: keys are sorted and a whole-number float such as 164.0 is written 164. An integer beyond 2**53 in a tool's result or arguments fails the call, because the durable record cannot keep it exactly; pass such a value as a string.

Each invocation runs one native Runner turn and commits it to the Orchestration Engine before returning the result. A failed tool call fails the turn with the error the platform's tool path reports, such as a tool execution error carrying the tool's message or a policy denial; a cancelled or user-stopped call fails it too. Nothing is committed in any of these cases. When the model calls several tools at once, they run concurrently. Each call keeps the Orchestration Engine activity of its call id, and their results reach the model in the order the model made the calls, whichever finishes first. If one of them waits for approval, the calls that ran before the wait keep their outputs ahead of the approved call's, as the SDK records them. When one call fails, the turn fails once the calls still running beside it have finished. Agent hooks, hosted prompts, dynamic instructions, and a custom tool_use_behavior are not supported yet. An agent's own input and output guardrails run as described under Input and output guardrails. A project's guardrail policies apply to every model and tool call; see Guardrail policies.

MCP tools

List a remote MCP server under mcp.servers in agent.yaml, with the same keys every Agent Engine framework uses. Its tools are discovered when the agent starts and app.tools() returns them beside the registered functions, named <server>__<tool>:

mcp:
  servers:
    atlas:
      url: https://mcp.example.com/mcp
      allowed_tools: [find]
      auth:
        type: bearer_env
        token_env: ATLAS_MCP_TOKEN

auth names the environment variable that holds the token; the token itself belongs in the workspace's secrets, or in .env for local development.

A call to one of these tools is an Orchestration Engine tool activity like any other and runs in the Tool Pod, which holds the server's credentials and network access. allowed_tools limits which of the server's tools the agent gets. A failed call or an MCP error result fails the turn. So does a structured result holding an integer at or beyond 2**53, which a durable record cannot keep exactly; have the server return such a value as a string.

A server that cannot be reached when the agent starts stops it from starting; timeout_seconds bounds each request of that discovery, as it bounds a call.

Only the agent runtime lists the server's tools, when it starts; the Tool Pod finds the server for a call from agent.yaml when the call arrives. The Tool Pod also runs the entrypoint, to register the agent's models, and there app.tools() returns the registered functions only. Pass MCP tools to an agent without looking them up by name, for example [tool for tool in app.tools() if tool.name.startswith("atlas__")], so the entrypoint builds in both.

The SDK's own Agent.mcp_servers is refused: the SDK would call the server from the agent's process, outside the Tool Pod. needs_approval on an MCP tool is not supported yet.

Handoffs

Use the SDK's own handoffs=[...], with other agents or agents.handoff(...). The runtime reads the whole agent graph the entrypoint returns before each turn and holds every reachable agent to the same rules as the first: its model comes from app.llm(...) (give each agent's model its own llm_id) and its tools from app.tools(), from which each agent can select its own by name. Agent names must be unique.

A handoff changes which agent is active; the turn stays one execution. The committed state records the active agent, so the next turn, also after a restart, starts with the agent that ended the last one. An approval requested by a handed-off agent suspends and resumes in that agent.

agents.handoff(...) may override the tool name and description. Agents may hand off to each other in a cycle. An input_filter, nest_handoff_history, an on_handoff callback, an input_type, a callable is_enabled, and a hand-built Handoff fail the turn before any model or tool work. So does a handoff on any agent whose tool name matches another handoff's or one of that agent's tools: the SDK would otherwise keep one and drop the other.

Agents as tools

Use the SDK's own Agent.as_tool(...) and put the result in another agent's tools. The nested agent follows the same rules as every other agent in the graph. Each call runs the nested agent as its own operation: its model and tool calls are Orchestration Engine activities under a path named after the tool, and repeated or concurrent calls to the same agent tool stay distinct, in the order the model made them.

The calling agent's model sees only the tool's answer, and only the calling conversation is committed; a replacement attempt replays the nested run from its recorded activities. The caller's stream carries the nested agent's text between subagent_start and subagent_end events, tagged with the tool name and the call id, apart from the calling agent's reply. A nested run that fails fails the turn.

A tool a nested agent uses can need approval, and so can the agent tool itself. The turn suspends as for any other approval, and the wait is recorded under the nested agent's path. In one turn, waits from the calling agent and from nested agents are answered together. Tool call ids must be unique within a turn. A nested agent that stops for an approval sends no subagent_end; the resumed stream opens it again with subagent_start before the rest of its text, and does not send again the text the caller saw before the wait.

max_turns, custom_output_extractor, and structured input are supported. A replacement attempt calls the extractor and a structured input's input_builder again, so both must return the same value for the same input and have no side effects; OE fails the turn when a replayed model call differs from the recorded one.

An on_stream callback, hooks, a run_config, a session, provider continuation ids, a callable needs_approval or is_enabled, tool guardrails, input or output guardrails on the nested agent or an agent it hands off to, a native timeout_seconds, and an agent that can reach itself through its own agent tool fail the turn before any model or tool work.

Approvals

Register a tool with @app.tool(needs_approval=True) to have a person approve each call. The argument is the SDK's own function_tool(needs_approval=...), passed through to the FunctionTool that app.tools() returns; setting needs_approval on that returned tool works too. When the model calls a tool that needs approval, the turn suspends: the invocation returns status: suspended with one interrupt per call waiting for approval, in the order the model made the calls, each naming the tool and its arguments. Calls in the same response that need no approval run first. Resume through the normal invoke endpoint on the same session, with resume_map answering every interrupt id at once:

{"resume_map": {"<interrupt id>": {"confirmed": true}}}

interrupts lists every pending approval. suspend_payload carries only the first one's value, for callers that read a single payload.

{"confirmed": false, "rejection_message": "..."} rejects the call; the model sees the rejection message instead of a tool result, as with the SDK's own reject(..., rejection_message=...). Without a rejection_message the model sees the SDK's default rejection text; an empty one is refused. An approval carries no message. The Orchestration Engine records the wait and its answer. Resuming replays the turn from the recorded model calls, applies the answer through the SDK's own RunState, and runs only what remains, so a tool never runs before its approval, prefix output is not repeated, and a restart while waiting loses nothing. A resume that leaves any interrupt unanswered is refused; needs_approval must be True or False, not a callable.

Input and output guardrails

Declare guardrails on an agent as the SDK documents, with input_guardrails and output_guardrails:

from agents import Agent, GuardrailFunctionOutput, Runner, input_guardrail


@app.entrypoint
def build() -> Agent:
    judge = Agent(
        name="topic judge",
        instructions="Answer yes if the message is about an order, else no.",
        model=app.llm("gpt-4.1-mini", llm_id="judge"),
    )

    @input_guardrail
    async def on_topic(context, agent, input) -> GuardrailFunctionOutput:
        verdict = await Runner.run(judge, input, context=context.context)
        return GuardrailFunctionOutput(
            output_info=None,
            tripwire_triggered=verdict.final_output.strip().lower() != "yes",
        )

    return Agent(
        name="support",
        model=app.llm("gpt-4.1"),
        tools=app.tools(),
        input_guardrails=[on_topic],
    )

A guardrail that trips fails the turn as a policy denial: the execution ends with error_code policy_denied and the platform's own message for that class. The denial's reason names the guardrail and leaves out its output_info; it is in the runtime's log and in the metadata of the live stream's error event, not in the stored execution. The turn commits nothing, so the conversation continues from the turn before it. Tools the turn already ran are not undone.

Because the denial names the guardrail, every guardrail needs a name. One declared with @input_guardrail or @output_guardrail, or built from a function, takes the function's; one built from a functools.partial or a callable object has none, so pass name=. A guardrail without a name is refused before any model call.

Three things differ from the SDK's own Runner:

  • Input guardrails finish before the first model call. The SDK's default, run_in_parallel=True, starts them beside the model call, which is then made, and paid for, whether or not a guardrail trips. Here run_in_parallel has no effect.
  • An agent's guardrails run one at a time, in the order the agent lists them, and the ones after a trip do not run. The SDK starts them together.
  • An agent with output guardrails does not stream its text. The SDK streams an answer before the output guardrails see it and withholds only the run's final value. Here the caller receives the answer whole, in the turn's result, once the guardrails pass. Agents without output guardrails stream as before.

As in the SDK, input guardrails belong to the agent that starts the turn and check the conversation so far with the new message; output guardrails belong to the agent that gives the turn's final answer. After a handoff the agent handed to starts the following turns, so its input guardrails check those.

A replacement attempt runs the turn again from its start, guardrails included, and a turn that waits for an approval runs them again when it resumes. A guardrail must therefore return the same verdict for the same input and change nothing outside the process. A guardrail that asks a model does so through an agent whose model comes from app.llm(...), called while the entrypoint builds the app, as judge is above: the judge's model call is then an Orchestration Engine activity under the project's policies, and a replay returns its recorded response instead of asking again. The run needs context=context.context, which carries the turn. A judge with a plain model name has no provider credentials in the runtime and fails the turn. Tools, approvals, and handoffs in a judge agent are not qualified. A project policy that holds a judge's response for review fails the turn: only an agent of the app's graph can wait for a reviewer. A guardrail that calls an outside service directly is not supported: the call would be made again on every replay, outside the platform's network policy.

Tool guardrails, and guardrails on an agent used as a tool, are not supported yet.

Guardrail policies

A project's guardrail policies are enforced by the Orchestration Engine on every model and tool call of the turn, including those of an agent used as a tool, and need no application code. A policy that logs, redacts, or blocks changes only what the call returns.

A policy that requires review of a model response suspends the turn as an approval does: the response is withheld, and the invocation returns status: suspended with an interrupt whose value names the review under guardrail_review. It waits beside any tool approval of the same turn, and one resume_map answers them together:

{"resume_map": {"<interrupt id>": {"decision": "approve", "reviewer_notes": "checked"}}}

Approving releases the held response, tool calls included, without another provider call. Denying fails the turn and releases nothing. The Orchestration Engine holds the response and records the decision; the adapter never sees the held text before it is approved.

To stop the Runner the adapter gives every agent a tool named agent_engine_guardrail_review, which no model is offered. The stop is not conversation content, so neither a later model call nor the committed history carries it. The Runner's own result for a run still lists it, so a custom_output_extractor of an agent used as a tool sees the call and its empty output among that run's items when a response of that agent was reviewed; they name the review, never the held response. Each review takes one of the run's max_turns. A tool or handoff with that name, or a model response that calls it, fails the turn.

Durable state

Each committed turn stores the Runner's complete input list under the __agent_engine_openai_agents__ state property, together with the app and session it belongs to and the name of the agent that ended the turn. The next turn continues from that list with that agent. Committed messages are an explicit-field projection for history readers; they are not read back.

A reader of Orchestration Engine history sees that projection: user and assistant messages, each tool call, and each output. A handoff and a call to an agent used as a tool appear as ordinary calls with their outputs. A nested agent's own conversation is not committed, so it is not in history; its model and tool calls are in the execution's activity records.

Supported items are text messages, function calls, and text function outputs. The calls one model response made come together, followed by exactly one output per call before anything else. Any other item, any state that belongs to a different app or session, and an active agent the app no longer has fail the turn before new model or tool work starts.

Sessions and branches

The Orchestration Engine's committed state is the only conversation store. The adapter makes the Runner call itself with that state as input, so SDK Session objects are not used, and provider-hosted continuation (previous_response_id, a conversation id, or a hosted prompt, whether passed to the Runner, to Agent.as_tool, or in a registered model's extra_body or extra_args) fails before any model work.

A branch is a new session that starts from another session's committed state. Its first turn continues with the source's conversation and active agent, commits under its own session, and shares nothing with the source afterwards; approvals work in it as in any turn. A conversation can be branched at the end of a completed turn, because the adapter commits once per turn. The platform branches only from a committed step, so a turn waiting for an approval offers nothing to branch from until it completes.

Creating a branch is a platform operation, not an SDK one. The adapter adds no branching API, and the SDK's own session branching (AdvancedSQLiteSession.create_branch_from_turn) is not available, so an app cannot branch its own conversation; the platform's branch API does.

Model calls

Every model call the native Runner makes becomes an Orchestration Engine LLM activity. The agent's model answers from the recorded activity, and the model itself runs in the Tool Pod. A replacement attempt replays the recorded response and gives the Runner identical output items.

Only per-call settings the platform carries cross that boundary: max_tokens, top_p, frequency_penalty, presence_penalty, timeout, parallel_tool_calls, reasoning.effort, verbosity, and a named tool_choice. Provider settings such as temperature belong to the model you register, and the Tool Pod applies them. A registered model cannot carry a tool_choice or timeout (set them on the agent), or extra_body / extra_args fields that make the provider continue a stored conversation (previous_response_id, conversation, conversation_id, prompt) or that override what the adapter builds for each call (such as tools, input, instructions, or the output format), or that set reasoning, reasoning_effort, or verbosity as raw fields (use ModelSettings.reasoning and ModelSettings.verbosity). Native tracing, provider-hosted continuation, and stop sequences fail before the call is sent.

Reasoning

Set reasoning=Reasoning(effort=...) and verbosity on the registered model, on the agent, or both; per-call values win over the registration. Prefer the registration. The SDK cannot tell an agent's explicit settings from its own defaults: when Agent(model_settings=...) equals the SDK's default settings (currently Reasoning(effort="none") with verbosity="low"), the SDK clears them at construction and the registered values apply. To use exactly that pair per call, assign it after construction (agent.model_settings = ...). Other reasoning settings, such as reasoning.summary, fail before the call is sent. Reasoning-token counts are reported in the model call's usage. Providers may limit these settings per model and API: for example, a Chat Completions reasoning model can refuse function tools unless the effort is none. The provider's refusal fails the model call.

A durable run does not carry a model's hidden reasoning from one model call to the next. The provider's encrypted reasoning items are dropped in the Tool Pod and never stored, so each call, including each step of a tool loop, reasons from the durable conversation alone. If an agent needs a plan that persists across calls, it should record the plan explicitly, for example in its own messages or in Memory, rather than rely on the model's hidden reasoning.

The Tool Pod retries transient provider failures before any output streams. The SDK's own ModelSettings.retry is rejected: on the agent it would make the Runner issue extra model activities, and on a registered model it would have no effect. A model stream that ends without a completed response fails the call rather than being recorded as a truncated answer.

Metadata

Release files for agent-engine-sdk-openai-agents 0.11.9

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for agent-engine-sdk-openai-agents 0.11.9
File Size Uploaded
agent_engine_sdk_openai_agents-0.11.9.tar.gz 91.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for agent-engine-sdk-openai-agents 0.11.9
File Interpreter ABI Platform
agent_engine_sdk_openai_agents-0.11.9-py3-none-any.whl Python 3 none any Details

Total release size: 165.3 kB

Release files / agent_engine_sdk_openai_agents-0.11.9.tar.gz

Download URL agent_engine_sdk_openai_agents-0.11.9.tar.gz
Size 91.9 kB
Tags Source
SHA-256 checksum
How to use checksums
4d50f5f315bbcce9304be62ab1b2bc6a514fc5849ed42b780eef6048dd9bd0ba
BLAKE2b-256 checksum
How to use checksums
2b6fa82fe2eaa38655cc4658b028b58070a7dcef30fa8e59acbf7eccf5caf2e9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 14, 2026.

Transparency log

Release files / agent_engine_sdk_openai_agents-0.11.9-py3-none-any.whl

Download URL agent_engine_sdk_openai_agents-0.11.9-py3-none-any.whl
Size 73.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
b9ad38482998d82356db501e48d20d97c7c4e8e88bac1ad3e532a2fc08386ccc
BLAKE2b-256 checksum
How to use checksums
67f305963815faaa425b4c7e5dae8d92bf567d1df6b7061c9719880faa4c79cd
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 14, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.11.9 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page