Agent Engine SDK OpenAI Agents
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. Hererun_in_parallelhas 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)
| File | Size | Uploaded | |
|---|---|---|---|
| agent_engine_sdk_openai_agents-0.11.9.tar.gz | 91.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 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 logRelease 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