OpenAI Agents SDK in Late 2026: What Is New in Python and TypeScript

TL;DR
OpenAI Agents SDK as of Python 0.24 and JS 0.20: sandbox agents, three guardrail layers, compaction sessions, MCP v2 and new defaults, with type-checked code.
The OpenAI Agents SDK is OpenAI's open-source, code-first agent runtime for Python (openai-agents 0.24.0, released October 15, 2026) and TypeScript (@openai/agents 0.20.0, released October 8, 2026). Since April it has grown from a small loop with handoffs and guardrails into a harness with sandboxed workspaces, compaction-backed sessions, durable approvals and MCP v2 support. It is a good default if you build mostly on OpenAI Responses models and want those primitives without writing them yourself - but pin your version, because minor bumps are where the breaking changes ship.
Last updated: October 17, 2026
This post is a map of the SDK as it stands today, organized by primitive, with the defaults that changed between April and October called out. If you need the basics first (defining tools, structured outputs, streaming), start with the TypeScript practical guide and come back.
Official Sources#
| Resource | What it confirms |
|---|---|
| Python docs | Primitives, sessions, guardrails, sandbox agents, realtime |
| TypeScript docs | JS equivalents, transports, logging config, Zod v4 requirement |
| openai-agents-python CHANGELOG | Python 0.23 and 0.24 changes |
| agents-core CHANGELOG | JS 0.14 to 0.20 changes |
| Python release process | Versioning policy and 0.14 to 0.22 breaking-change and highlight notes |
| OpenAI API changelog | Sandbox launch, TS launch, Agents API beta |
| PyPI / npm | Current versions, upload dates and dependency floors |
Every code sample below was type-checked, not executed: the TypeScript samples with tsc --noEmit (TypeScript 7.0.2, strict, moduleResolution: bundler) against @openai/agents@0.20.0, @openai/agents-core@0.20.0 and zod@4, and the Python samples with python -m py_compile plus an import check on Python 3.13 against openai-agents==0.24.0. No API calls were made.
The 2026 timeline at a glance#
Both SDKs use 0.Y.Z versioning. Per the release docs, a minor (Y) bump signals breaking changes to non-beta public interfaces, and patch (Z) releases carry fixes, new features and beta-feature updates. In JS, all @openai/agents-* packages are version-linked, so agents-core, agents-openai and agents-realtime move together.
| Date (UTC) | Python | TypeScript | What changed |
|---|---|---|---|
| Apr 15 | 0.14.0 | - | Sandbox agents (beta), sandbox memory, inspectable open-source harness |
| May 1 | 0.15.0 | - | Model refusals raise ModelRefusalError (breaking) |
| May 6 | - | sandbox launch | TS gets sandbox agents and the built-in harness |
| May 7 | 0.16.0 | - | Default model gpt-4.1 to gpt-5.4-mini; max_turns=None allowed |
| May 8 | 0.17.0 | - | Sandbox host paths need extra_path_grants (breaking) |
| Jul 7 | 0.18.0 | - | Realtime default model gpt-realtime-2.1 |
| Jul 27-28 | 0.19.0 | 0.14.0 | Programmatic Tool Calling; @tool alias (Py); task/turn spans and redacted debug logs (JS) |
| Aug 11 | 0.20.0 | 0.15.0 | Default model gpt-5.6-luna; MCP v2 negotiation; durable staged input |
| Aug 15 | 0.21.0 | 0.16.0 | Python requires openai v3 and HTTPX2; testing utilities in both; Standard Schema (JS) |
| Aug 19 | 0.22.0 | 0.17.0 | "Output withheld" placeholder for guardrail-blocked tool output (Py); blocked tool outputs and aliases redacted from replay state (JS) |
| Sep 8 | - | 0.17.1 | Server-wide MCP tool guardrails; custom blocked-output messages |
| Sep 10 | - | 0.18.0 | UnixLocal file and editor operations get optional Python-backed protection (auto/required/off); Docker file APIs run inside the running container, with a 10 MB editor source limit |
| Sep 10 | - | - | Managed Agents API public beta (separate hosted product) |
| Oct 1-2 | 0.23.0 / 0.23.1 | - | Configurable MCP listing page limits; gpt-transcribe default STT. 0.23.0 is the CHANGELOG date; 0.23.1 (Oct 2) is the first 0.23 build on PyPI |
| Oct 5 | - | 0.19.0 | Agent-tool streaming callbacks capped at 1024 pending events; started checkpoints without completed input validation rejected; resumed MCP calls bound to their original recipients; optional MCP listing page limits and a compaction rollback item budget |
| Oct 8 | - | 0.20.0 | MCP discovery capped at 64 pages; approval resumes count toward maxTurns; UnixLocal file operations require a trusted Python 3 |
| Oct 15 | 0.24.0 | - | MCP annotations on converted tools; run-wide tool exception formatting |
Python 0.14 to 0.22 changes come from the breaking-change and highlight notes on the release docs page; 0.23 and 0.24 come from the Python CHANGELOG, which starts at 0.23.0. Dates come from PyPI and npm upload times, except 0.23.0, which was never published to PyPI (PyPI goes from 0.22.3 to 0.23.1, so you cannot pin openai-agents==0.23.0) and is dated from the CHANGELOG. The April 15, May 6 and September 10 rows are from the OpenAI API changelog.
One naming trap: the Agents API that went to public beta on September 10 is a hosted product built on a managed Codex harness, where OpenAI runs session orchestration, compaction and recovery. The Agents SDK is a library you run in your own process. The TypeScript guide linked above has the longer comparison.
Mental model: the run loop#
Everything in the SDK hangs off one loop. Runner.run (Python) or run() / runner.run() (TS) calls the model, executes any tool calls, follows handoffs, and repeats until an agent produces final output or the turn limit hits. The default maxTurns is 10 in both SDKs. Python 0.16.0 lets you pass max_turns=None to disable the limit, and JS 0.20.0 now counts model calls made after an approval resume toward maxTurns, so long approval chains can hit the cap where they did not before.
%% title: One Runner.run turn
flowchart TD
A[User input] --> B["Input guardrails (first agent only)"]
B --> C[Model call]
C --> D{Response type}
D -->|tool call| E[Approval check]
E --> F[Tool guardrails]
F --> G[Execute tool]
G --> C
D -->|handoff| H[Switch active agent]
H --> C
D -->|final output| I["Output guardrails (last agent only)"]
I --> J[Write to session]The rest of this post walks each box.
Handoffs vs agents-as-tools#
A handoff is exposed to the model as a tool named transfer_to_<agent_name>. When the model calls it, the active agent switches and the specialist owns the rest of the turn. The handoffs docs list the knobs on handoff(): tool_name_override, tool_description_override, on_handoff, input_type (structured data the model must pass along), input_filter, is_enabled and nest_handoff_history.
Input filters decide what history the next agent sees. The prebuilt handoff_filters.remove_all_tools (Python, in agents.extensions) and removeAllTools (TS, in @openai/agents-core/extensions) strip prior tool calls so the specialist starts from the conversation, not the triage agent's scratch work. Two caveats from the docs:
- Nested handoff history (collapsing prior turns into one summary message) became opt-in in Python 0.7.0 via
RunConfig(nest_handoff_history=True)and is still described as an opt-in beta. - Server-managed conversations (
conversation_id,previous_response_id,auto_previous_response_id) do not support handoff input filters. If you rely on filters, keep history client-side.
The alternative is Agent.as_tool(). The orchestration guide frames the choice simply: use agents-as-tools when a manager should keep control and own the final answer, and use handoffs when the specialist should take over. The two combine fine.
%% title: Handoff vs agent-as-tool
sequenceDiagram
participant U as User
participant T as Triage
participant B as Billing
participant M as Manager
participant S as Specialist tool
Note over U,B: Handoff - specialist owns the turn
U->>T: Refund my order
T->>B: transfer_to_billing
B-->>U: Final answer from Billing
Note over U,S: Agent as tool - manager owns the answer
U->>M: Refund my order
M->>S: call specialist tool
S-->>M: Result
M-->>U: Final answer from ManagerGuardrails now come in three layers#
The guardrails docs describe three levels, and the scoping rules matter more than the API:
- Input guardrails run only for the first agent in the chain. If triage hands off to billing, billing's input guardrails do not fire.
- Output guardrails run only for the agent that produces the final output.
- Tool guardrails run on every guarded function-tool call, including local MCP tools when the server configures them.
Input guardrails default to parallel execution (run_in_parallel=True). That keeps latency low, but the docs are explicit about the cost: the agent may already have consumed tokens and run tools before the tripwire cancels it. If the guardrail exists to stop something expensive or irreversible, set run_in_parallel=False (TS: runInParallel: false) so the agent waits for the check.
Output guardrails picked up a sharper edge in Python 0.22.0. When an output guardrail blocks final output that came directly from a terminal function tool, the stored function_call_output is replaced with Output withheld by an output guardrail., so the blocked payload does not leak into history. You can change the text with RunConfig.output_guardrail_blocked_message. The JS counterpart arrived in two steps: 0.17.0 redacts blocked tool outputs and aliases from replay state, and 0.17.1 added the custom message.
Tool guardrails can now be set once per MCP server. Python lets you put tool_input_guardrails and tool_output_guardrails on a local MCP server, and JS 0.17.1 added server-wide MCP tool guardrails.
What guardrails do not cover, stated plainly in the docs: handoff calls and hosted or built-in execution tools do not go through the tool guardrail pipeline. If you need a policy on a handoff, enforce it in on_handoff or in the receiving agent's instructions and tools.
Sessions and compaction#
Sessions are client-side conversation memory. Pass one to the runner and it loads prior items before each turn and persists new ones after. The two SDKs differ most here.
| Python | TypeScript | |
|---|---|---|
| Built-in stores | SQLiteSession, AsyncSQLiteSession, RedisSession, SQLAlchemySession, MongoDBSession, DaprSession, OpenAIConversationsSession | OpenAIConversationsSession, MemorySession (local dev) |
| Wrappers | OpenAIResponsesCompactionSession, EncryptedSession (encryption + TTL), AdvancedSQLiteSession (branching, analytics) | OpenAIResponsesCompactionSession |
| Custom backends | Subclass the session protocol | Implement the five-method Session interface; optional SessionHistoryTransactionAwareSession and RunContextAwareSession |
Sessions cannot be combined with conversation_id, previous_response_id or auto_previous_response_id in the same run. Pick client-side history or server-managed history, not both.
OpenAIResponsesCompactionSession is the feature most worth knowing. It wraps another session and, after each persisted turn, calls responses.compact to replace long history with a shorter equivalent list, then clears and rewrites the underlying store. The default trigger fires once at least 10 non-user items accumulate. compaction_mode can be 'auto', 'previous_response_id' or 'input'. Three gotchas from the JS sessions guide and Python sessions docs:
- Do not wrap
OpenAIConversationsSession, which uses a different server-managed history flow. - Auto-compaction runs before a streaming run resolves, so
result.completedcan stay pending for a few seconds after the last token. - For low-latency streaming, disable the trigger and compact on your own schedule.
%% title: Compaction session
flowchart LR
A[Turn completes] --> B[Persist items]
B --> C{Trigger met?}
C -->|no| D[Done]
C -->|yes| E[responses.compact]
E --> F[Clear underlying store]
F --> G[Write compacted items]
G --> DHere is the low-latency pattern in TypeScript:
import {
Agent,
MemorySession,
OpenAIResponsesCompactionSession,
run,
} from '@openai/agents';
const agent = new Agent({
name: 'Support',
instructions: 'Answer briefly and keep track of prior context.',
model: 'gpt-5.6-luna',
});
// Swap MemorySession for your own Session implementation in production.
// Never wrap OpenAIConversationsSession.
const session = new OpenAIResponsesCompactionSession({
underlyingSession: new MemorySession(),
// Disable auto-compaction so streams resolve as soon as the last token lands.
shouldTriggerCompaction: () => false,
});
const result = await run(agent, 'Summarize ticket #8472.', {
session,
stream: true,
});
for await (const _event of result) {
// forward events to the client
}
await result.completed;
// Compact between turns, on your own schedule.
await session.runCompaction({ force: true });
The Python equivalent is should_trigger_compaction=lambda _: False plus await session.run_compaction({"force": True}). One Python detail worth checking in your own code: in the 0.24.0 source, the model parameter of OpenAIResponsesCompactionSession.__init__ still defaults to "gpt-4.1" (source), unlike the agent default. Pass model= explicitly if you want compaction on the same model family as your agents.
Human-in-the-loop and durable RunState#
Approvals are where the SDK has improved most quietly. Per the HITL docs:
needs_approval(TS:needsApproval) takes a boolean or an async callable, and works on function tools,Agent.as_tool,ShellToolandApplyPatchTool. Callable rules fail closed on malformed arguments.- Interruptions surface in
result.interruptionsasToolApprovalItems, even when they come from a handoff target or a nested agent-as-tool. - You resume by turning the result into a
RunState, approving or rejecting each item, and calling the runner again with the state.always_approve/always_rejectmake the decision sticky for the rest of the run. - Python 0.20.0 added
RunState.add_input()to stage durable user input before resuming. Staged input survives serialization and runs through input guardrails. JS 0.15.0 added the same capability.
The security rule matters if you serialize state for a reviewer UI: RunState.from_json() and from_string() do not authenticate the snapshot. The docs say to keep approval snapshots in server storage, authenticate and authorize reviewers server-side, and prevent replayed submissions. Never round-trip a serialized RunState through the browser and trust what comes back.
Here is the combined pattern in TypeScript: a blocking input guardrail on triage, a filtered handoff to billing, and a refund tool that only pauses for refunds over $100. The removeAllTools import comes from @openai/agents-core, which @openai/agents pulls in only as a transitive dependency. Install it directly (npm install @openai/agents-core@0.20.0, version-linked with @openai/agents) or the import breaks under pnpm strict mode or Yarn PnP.
import {
Agent,
Runner,
handoff,
tool,
type InputGuardrail,
InputGuardrailTripwireTriggered,
} from '@openai/agents';
import { removeAllTools } from '@openai/agents-core/extensions';
import { z } from 'zod';
// Blocking input guardrail: the agent waits for this to finish.
const noSecrets: InputGuardrail = {
name: 'no_secrets',
runInParallel: false,
execute: async ({ input }) => {
const text = typeof input === 'string' ? input : JSON.stringify(input);
return {
tripwireTriggered: /sk-[A-Za-z0-9]{20,}/.test(text),
outputInfo: { checked: true },
};
},
};
const issueRefund = tool({
name: 'issue_refund',
description: 'Refund an order.',
parameters: z.object({ orderId: z.string(), amountUsd: z.number() }),
// Only refunds over $100 pause for a human.
needsApproval: async (_ctx, { amountUsd }) => amountUsd > 100,
execute: async ({ orderId, amountUsd }) =>
`Refunded $${amountUsd} on ${orderId}`,
});
const billing = new Agent({
name: 'Billing',
instructions: 'Resolve billing issues. Use issue_refund when warranted.',
tools: [issueRefund],
});
const triage = Agent.create({
name: 'Triage',
instructions: 'Route billing questions to Billing.',
inputGuardrails: [noSecrets],
handoffs: [handoff(billing, { inputFilter: removeAllTools })],
});
const runner = new Runner();
try {
let result = await runner.run(triage, 'Refund $250 on order A-1009, it arrived broken.');
while (result.interruptions?.length) {
for (const item of result.interruptions) {
// In production: persist result.state.toString() server-side and wait for a reviewer.
result.state.approve(item);
}
result = await runner.run(triage, result.state);
}
console.log(result.finalOutput);
} catch (err) {
if (err instanceof InputGuardrailTripwireTriggered) {
console.log('Blocked before the model ran.');
} else {
throw err;
}
}
The Python version adds a compaction-backed SQLite session and turns off task/turn spans and sensitive trace data. It uses the @tool alias from agents.decorators that arrived in 0.19.0.
import asyncio
import re
from typing import Any
from agents import (
Agent,
GuardrailFunctionOutput,
InputGuardrailTripwireTriggered,
OpenAIResponsesCompactionSession,
RunConfig,
RunContextWrapper,
Runner,
SQLiteSession,
TResponseInputItem,
handoff,
input_guardrail,
)
from agents.decorators import tool
from agents.extensions import handoff_filters
@input_guardrail(run_in_parallel=False) # block the agent until this finishes
async def no_secrets(
ctx: RunContextWrapper[None], agent: Agent, input: str | list[TResponseInputItem]
) -> GuardrailFunctionOutput:
text = input if isinstance(input, str) else str(input)
leaked = re.search(r"sk-[A-Za-z0-9]{20,}", text) is not None
return GuardrailFunctionOutput(output_info={"leaked": leaked}, tripwire_triggered=leaked)
async def over_100(ctx: RunContextWrapper[Any], params: dict[str, Any], call_id: str) -> bool:
return float(params.get("amount_usd", 0)) > 100
@tool(needs_approval=over_100)
def issue_refund(order_id: str, amount_usd: float) -> str:
"""Refund an order."""
return f"Refunded ${amount_usd} on {order_id}"
billing = Agent(
name="Billing",
instructions="Resolve billing issues. Use issue_refund when warranted.",
tools=[issue_refund],
)
triage = Agent(
name="Triage",
instructions="Route billing questions to Billing.",
input_guardrails=[no_secrets],
handoffs=[handoff(billing, input_filter=handoff_filters.remove_all_tools)],
)
session = OpenAIResponsesCompactionSession(
session_id="ticket-8472",
underlying_session=SQLiteSession("ticket-8472", "conversations.db"),
model="gpt-5.6-luna", # pass explicitly; see the note above
)
run_config = RunConfig(
trace_include_sensitive_data=False, # defaults to True
tracing={"include_task_and_turn_spans": False},
)
async def main() -> None:
try:
result = await Runner.run(
triage,
"Refund $250 on order A-1009, it arrived broken.",
session=session,
run_config=run_config,
)
while result.interruptions:
state = result.to_state()
for item in result.interruptions:
# In production: store state.to_json() server-side, wait for a reviewer.
state.approve(item)
result = await Runner.run(triage, state, session=session, run_config=run_config)
print(result.final_output)
except InputGuardrailTripwireTriggered:
print("Blocked before the model ran.")
if __name__ == "__main__":
asyncio.run(main())
Python 0.24.0 also fixed several resume edge cases, including deferring automatic compaction while approvals are pending and persisting deferred approval history across resumes (CHANGELOG). If you shipped approvals on an earlier 0.2x release, that alone is a reason to upgrade.
Sandbox agents and memory#
Sandbox agents are the April headline and still the biggest surface. A SandboxAgent is a normal agent plus three things: a Manifest describing the workspace (local files and directories, git repos, storage mounts), a list of capabilities that become sandbox-native tools, and a sandbox client chosen at run time. Per the Python sandbox quickstart, the main choices are default_manifest, instructions, base_instructions (an escape hatch that replaces the SDK's sandbox prompt), capabilities, run_as, and on the run side the client plus session, session_state or snapshot for reconnecting to prior work. In both SDKs, Capabilities.default() returns Filesystem, Shell and Compaction.
%% title: Sandbox run lifecycle
flowchart TD
A[Manifest] --> B[Materialize workspace]
B --> C[Agent turn]
C --> D[Shell and apply_patch tools]
D --> C
C --> E[Save session state or snapshot]
E --> F{Next step}
F -->|resume| B
F -->|memory on| G[Generate memories]Clients. UnixLocalSandboxClient and DockerSandboxClient ship in both SDKs. Hosted clients cover Blaxel, Cloudflare, Daytona, E2B, Modal and Vercel in both languages (in JS under @openai/agents-extensions/sandbox/*, most with their own npm peer dependency; Cloudflare instead talks to a deployed bridge Worker), and Runloop in Python. The docs' warning about the local client is the most important line in the sandbox section. In Python, UnixLocalSandboxClient adds no OS-level confinement to commands on Linux, and on macOS it applies filesystem restrictions through sandbox-exec but no network isolation. The JS docs say the JS client runs commands as host processes and adds no OS-level filesystem or network isolation on either platform. Anything that runs commands influenced by untrusted input belongs in Docker, a hosted sandbox, or behind external isolation. The sandbox architecture guide covers how to pick between those.
Path grants (Python 0.17.0, breaking). LocalFile and LocalDir sources must stay within the materialization base_dir unless you grant them through Manifest.extra_path_grants with a SandboxPathGrant. The docs recommend read-only grants.
Memory. Sandbox Memory() is separate from session history. It distills lessons from prior runs into files under memories/ in the workspace, and reads them with progressive disclosure: a small memory_summary.md is injected into context, the agent searches MEMORY.md when it needs more, and only opens rollout_summaries on demand (memory docs). Memory(generate=None) makes it read-only and Memory(read=None) makes it generate-only. Reading requires Shell(), and live updates (on by default) require Filesystem(). Generation makes its own model calls; in the 0.24.0 package the phase-one and phase-two defaults are gpt-5.4-mini and gpt-5.5, so budget for them.
Python:
import asyncio
from pathlib import Path
from agents import Runner
from agents.run import RunConfig
from agents.sandbox import Manifest, SandboxAgent, SandboxRunConfig
from agents.sandbox.capabilities import Capabilities, Memory
from agents.sandbox.entries import LocalDir
from agents.sandbox.sandboxes.unix_local import UnixLocalSandboxClient
REPO = Path(__file__).resolve().parent / "repo"
agent = SandboxAgent(
name="Repo fixer",
model="gpt-5.6-luna",
instructions=(
"Read repo/README.md first. Make the smallest change that fixes the "
"failing test and report the command you ran."
),
default_manifest=Manifest(entries={"repo": LocalDir(src=REPO)}),
capabilities=Capabilities.default() + [Memory()], # Filesystem, Shell, Compaction + Memory
)
async def main() -> None:
# Trusted local development only: no OS-level confinement on Linux.
result = await Runner.run(
agent,
"Find and fix the failing unit test in repo/.",
run_config=RunConfig(sandbox=SandboxRunConfig(client=UnixLocalSandboxClient())),
)
print(result.final_output)
if __name__ == "__main__":
asyncio.run(main())
TypeScript, staging a git repo instead of a local directory. The JS sandbox docs list Node.js 22 or higher as a prerequisite, and the host also needs Python 3: JS 0.20.0 requires a trusted Python 3 for UnixLocalSandboxClient file operations and rejects disabled protection (agents-core CHANGELOG). The SDK looks for python3 in /usr/bin, /usr/local/bin and /opt/homebrew/bin; on a slim Node container without one, set OPENAI_AGENTS_PYTHON to an absolute executable path or the file tools fail.
import { run } from '@openai/agents';
import { Capabilities, Manifest, SandboxAgent, gitRepo, memory } from '@openai/agents/sandbox';
import { UnixLocalSandboxClient } from '@openai/agents/sandbox/local';
const agent = new SandboxAgent({
name: 'Repo fixer',
model: 'gpt-5.6-luna',
instructions:
'Read repo/README.md first. Make the smallest change that fixes the failing test and report the command you ran.',
defaultManifest: new Manifest({
entries: {
repo: gitRepo({ repo: 'your-org/your-repo', ref: 'main' }),
},
}),
capabilities: [...Capabilities.default(), memory()],
});
// Trusted local development only: UnixLocalSandboxClient runs host processes.
const result = await run(agent, 'Find and fix the failing unit test in repo/.', {
sandbox: { client: new UnixLocalSandboxClient() },
});
console.log(result.finalOutput);
For a first-person account of running the April release in production, see the Agents SDK evolution writeup.
MCP#
Python offers four ways to attach MCP servers, per the MCP docs:
HostedMCPTool: the Responses API calls a public MCP server on the model side.MCPServerStreamableHttp,MCPServerSseandMCPServerStdio: your process connects to the server, optionally coordinated byMCPServerManager.
Local servers support require_approval, static and dynamic tool filtering, cache_tools_list and invalidate_tools_cache(). Python 0.13.0 added list_resources(), list_resource_templates(), read_resource() and MCPServerStreamableHttp.session_id, and 0.24.0 exposes MCP annotations on converted tools.
The protocol work landed in the 0.20 / 0.15 releases. Python 0.20.0 supports MCP Python SDK v2 while keeping v1 (mcp>=1.19.0,<3); with v2 installed it uses mcp.Client(mode='auto') to negotiate the newest protocol and falls back to the legacy initialize handshake. JS 0.15.0 added MCP 2026-07-28 negotiation with legacy fallback.
The change most likely to bite on upgrade: JS 0.20.0 caps automatic MCP tool discovery at 64 pages. Servers that expose very large tool catalogs need maxListPages raised. Python 0.23 made listing page limits configurable as well.
import { Agent, MCPServerStreamableHttp, Runner } from '@openai/agents';
const docs = new MCPServerStreamableHttp({
name: 'internal-docs',
url: 'http://localhost:8000/mcp',
cacheToolsList: true, // skip re-listing tools on every turn
maxListPages: 128, // JS 0.20 default caps discovery at 64 pages
});
await docs.connect();
try {
const agent = new Agent({
name: 'Docs assistant',
instructions: 'Answer from the internal-docs tools only.',
mcpServers: [docs],
});
const runner = new Runner({
// Trace capture of inputs and outputs defaults to true; turn it off explicitly.
traceIncludeSensitiveData: false,
tracing: { includeTaskAndTurnSpans: false },
});
const result = await runner.run(
agent,
'How do we rotate the staging database password?',
{ maxTurns: 20 },
);
console.log(result.finalOutput);
} finally {
await docs.close();
}
As a negative control, misspelling maxListPages in that file fails tsc with "Object literal may only specify known properties", so the option name is real in 0.20.0.
Tracing and logging defaults#
Tracing is on by default and goes to the OpenAI traces dashboard. You can turn it off with OPENAI_AGENTS_DISABLE_TRACING=1, set_tracing_disabled(True), or RunConfig.tracing_disabled. It is unavailable for Zero Data Retention organizations. In JS, tracing is on by default in Node.js, Deno and Bun, and off by default in browsers and when NODE_ENV=test (JS tracing guide).
Three settings are worth checking, and only the first one changed this year:
- Task and turn spans (changed). Default tracing now wraps each runner invocation in a task span and each model turn in a turn span (JS 0.14.0 added this with a per-run opt-out). If your trace backend or dashboards keyed off the old span tree, turn them off with
RunConfig(tracing={'include_task_and_turn_spans': False})in Python ortracing: { includeTaskAndTurnSpans: false }in JS. - Sensitive data in traces (unchanged, on by default in both). Python's
trace_include_sensitive_data(also settable viaOPENAI_AGENTS_TRACE_INCLUDE_SENSITIVE_DATA) and JS'straceIncludeSensitiveDataboth default totrue, so traces capture model and tool inputs and outputs unless you set the option tofalsein your run config, as both samples above do. - Sensitive data in debug logs (JS only). This is a separate system from tracing. JS 0.14.0 started redacting model and tool data, including related error details, from the SDK's
debuglogs by default. To see that data while debugging, callsetSensitiveDataLoggingEnabled(true)from@openai/agents, or setOPENAI_AGENTS_DONT_LOG_MODEL_DATA=0/OPENAI_AGENTS_DONT_LOG_TOOL_DATA=0(JS config guide). The programmatic setting takes precedence over the environment variables.
The practical rule: a JS app with default settings keeps model and tool data out of its logs but still sends it in traces. If traces should not hold inputs and outputs, turn trace capture off explicitly in both languages.
For other backends, both SDKs accept custom trace processors, documented on the Python tracing page.
Realtime and voice#
The two SDKs diverge most on voice. Python realtime agents are server-side: WebSocket by default, plus a SIP attach flow (OpenAIRealtimeSIPModel); the Python SDK provides no browser WebRTC transport (Python realtime transport guide). The Python repo ships a Twilio SIP example and a Twilio Media Streams example over the WebSocket path. The JS SDK offers OpenAIRealtimeWebRTC (the default in browsers), OpenAIRealtimeWebSocket and OpenAIRealtimeSIP, plus Cloudflare and Twilio extension transports (JS transport guide). Browser apps connect with a short-lived ek_ token your server mints via POST /v1/realtime/client_secrets (JS voice agents quickstart), so the real API key never reaches the client.
Model defaults followed the platform. Python 0.18.0 made gpt-realtime-2.1 the realtime default; OpenAI released GPT-Realtime-2.1 and 2.1 mini on July 6 with better alphanumeric recognition, noise handling and interruption behavior. For Realtime input transcription settings, Python 0.20.0 recognizes gpt-transcribe, gpt-live-transcribe and gpt-realtime-whisper.
Separately, Python still has the older Voice pipeline (speech-to-text, then your agent workflow, then text-to-speech), which is a different feature from realtime agents. Python 0.23 made gpt-transcribe the default voice STT model.
Programmatic Tool Calling and other newer tools#
Programmatic Tool Calling (Python 0.19.0, JS 0.14.0) lets supported Responses models write a short JavaScript program that calls the tools you allow, instead of making one tool call per model round trip. GPT-5.6 introduced the API feature on July 9. Per the tools docs:
- The program runs in a fresh hosted V8 environment with no Node.js APIs, filesystem or network access.
- Tools opt in with
allowed_callers=['programmatic'], or['direct', 'programmatic']to stay callable both ways. - An agent can have one
ProgrammaticToolCallingTool, and Chat Completions models reject it.
It is most useful when a task fans out into many small tool calls whose intermediate results the model does not need to see.
Hosted container shell. ShellTool(environment={'type': 'container_auto', 'network_policy': {'type': 'disabled'}, 'skills': [...]}) runs commands in an OpenAI-managed container, and container_reference reuses one. It is a lighter option than a full sandbox agent when shell is one occasional tool.
Codex tool (experimental). Python's codex_tool wraps the Codex CLI so an agent can delegate a bounded workspace task (shell, file edits, MCP tools) inside a single tool call. The default tool name is codex, and auth uses CODEX_API_KEY (preferred) or OPENAI_API_KEY.
Upgrade checklist#
If you are on Python 0.14-0.19 or JS 0.13 or earlier, these are the behavior changes most likely to bite, as opposed to new options. It is not the complete list: the JS minor releases in particular carry more hardening than fits here, so read the agents-core CHANGELOG for your exact range. Each item names the versions it affects, so skip the ones you are already past:
- Pin the model explicitly (Python before 0.20, JS before 0.15). The default went
gpt-4.1togpt-5.4-mini(Py 0.16) togpt-5.6-luna(Py 0.20, JS 0.15), withreasoning.effort='none'andverbosity='low'. Explicit agent or run models andOPENAI_DEFAULT_MODELtake precedence. Also passmodel=to Python's compaction session. - Migrate HTTPX (Python before 0.21). Python 0.21.0 requires
openaiv3 and moves OpenAI HTTP integrations to HTTPX2. If you passhttp_client=toAsyncOpenAI, switch fromhttpxtohttpx2. - Handle refusals (Python before 0.15). Since Python 0.15.0, refusals raise
ModelRefusalErrorinstead of returning empty output. Catch it, or registererror_handlers={'model_refusal': ...}. - Grant host paths (Python before 0.17). Since Python 0.17.0, sandbox
LocalFile/LocalDirsources outsidebase_dirneedManifest.extra_path_grants. - Check MCP page caps (JS before 0.20). JS 0.20.0 stops discovery at 64 pages unless you raise
maxListPages. - Recheck turn limits (JS before 0.20). JS 0.20.0 counts post-approval model calls toward
maxTurns(default 10). Raise it if approval-heavy flows start hitting the max-turns error. - Install Python 3 on sandbox hosts (JS before 0.20, sandbox users). JS 0.20.0 requires a trusted Python 3 for
UnixLocalSandboxClientfile operations and rejects disabled protection. SetOPENAI_AGENTS_PYTHONto an absolute path ifpython3is not in/usr/bin,/usr/local/binor/opt/homebrew/bin. JS 0.18.0 had already added the optional protection and moved Docker file APIs inside the running container, with a 10 MB editor source limit. - Recheck resume and streaming edge cases (JS before 0.19). JS 0.19.0 caps agent-tool streaming callbacks at 1024 pending events (adjust with
onStreamMaxPendingEvents), rejects started checkpoints that lack completed input validation, and binds resumed MCP calls to their original recipients. Old snapshots may need a fresh run. - Recheck debug logs (JS before 0.14). JS 0.14.0 redacts model and tool data from debug logs by default; opt back in with
setSensitiveDataLoggingEnabled(true)only where logs are handled securely. - Install Zod v4 (all JS). The JS SDK requires Zod v4:
npm install @openai/agents @openai/agents-core zod(add@openai/agents-coreif you import from@openai/agents-core/extensions, and keep it on the same version as@openai/agents). JS 0.16.0 also accepts Standard Schema validators for tool inputs and structured outputs. - Use the testing utilities (Python 0.21+, JS 0.16+). Python 0.21.0 and JS 0.16.0 (
@openai/agents/testing) ship scripted model, sandbox and Realtime test helpers, which make upgrade regressions cheaper to catch.
Where it fits#
What the SDK does well, on documented capabilities: the primitive set is still small (agents, tools, handoffs, guardrails, sessions), the two languages are at parity on most features, tracing works with zero setup, and the sandbox harness saves you from writing file staging, shell tools, snapshots and provider glue. The release cadence is fast, and the changelogs are detailed enough to plan upgrades from.
The tradeoffs are worth stating neutrally. Programmatic Tool Calling, hosted MCP, compaction and the Conversations session are tied to the OpenAI Responses API. The 0.x versioning means a minor bump can and does break things, as the checklist above shows. Python has a broader set of built-in session stores and the Voice pipeline. JS has the browser WebRTC transport plus Cloudflare and Twilio extension transports. Both SDKs support SIP for telephony. The docs do describe non-OpenAI providers: LiteLLM and AnyLLM adapters in Python, and an AI SDK adapter in JS.
If you are deciding between this and another stack, the OpenAI Agents SDK vs Claude Agent SDK comparison covers that ground on documented capabilities. If you are leaving Agent Builder before its November 30, 2026 shutdown, OpenAI points you to this SDK, and the migration plan in Continue Reading below covers the prompts and evals side.
FAQ#
What version of the OpenAI Agents SDK is current?#
As of October 17, 2026, the Python package is openai-agents 0.24.0 (released October 15, requires Python 3.10+ and openai>=3,<4), and the TypeScript package is @openai/agents 0.20.0 (published October 8, depends on openai ^7.2.0 with a zod ^4 peer dependency). Minor bumps can include breaking changes, so pin exact versions.
What is the difference between the Agents SDK and the Agents API?#
The Agents SDK is an open-source library you run in your own process, with your own storage and infrastructure. The Agents API, in public beta since September 10, 2026, is a hosted product built on a managed Codex harness where OpenAI runs session orchestration, compaction and recovery. Choose based on whether you want to own the runtime or rent it.
Does the Agents SDK put my prompts and tool outputs in traces?#
By default, yes, in both languages. trace_include_sensitive_data (Python) and traceIncludeSensitiveData (JS) default to true, so set them to false in your run config if traces should not hold inputs and outputs. The JS SDK's debug logs are a separate system and redact model and tool data by default since 0.14.0.
Are sandbox agents safe to run locally?#
Only for trusted work. In Python, UnixLocalSandboxClient adds no OS-level confinement on Linux, and on macOS it restricts the filesystem through sandbox-exec but does not isolate the network. The JS client adds no OS-level filesystem or network isolation on either platform. For commands influenced by untrusted input, the docs recommend Docker, a hosted sandbox client, or external isolation.
Does the Agents SDK work without OpenAI models?#
Yes, with limits. The docs describe LiteLLM and AnyLLM adapters in Python and an AI SDK adapter in JS for other providers. Features built on the Responses API, such as Programmatic Tool Calling, hosted MCP, hosted tools and Responses compaction, need supported OpenAI models.
Sources#
- https://openai.github.io/openai-agents-python/
- https://openai.github.io/openai-agents-python/release/
- https://openai.github.io/openai-agents-python/handoffs/
- https://openai.github.io/openai-agents-python/multi_agent/
- https://openai.github.io/openai-agents-python/guardrails/
- https://openai.github.io/openai-agents-python/sessions/
- https://openai.github.io/openai-agents-python/human_in_the_loop/
- https://openai.github.io/openai-agents-python/sandbox_agents/
- https://openai.github.io/openai-agents-python/sandbox/memory/
- https://openai.github.io/openai-agents-python/mcp/
- https://openai.github.io/openai-agents-python/tracing/
- https://openai.github.io/openai-agents-python/tools/
- https://openai.github.io/openai-agents-python/realtime/quickstart/
- https://openai.github.io/openai-agents-python/realtime/transport/
- https://openai.github.io/openai-agents-js/
- https://openai.github.io/openai-agents-js/guides/release/
- https://openai.github.io/openai-agents-js/guides/config/
- https://openai.github.io/openai-agents-js/guides/sessions/
- https://openai.github.io/openai-agents-js/guides/tracing/
- https://openai.github.io/openai-agents-js/guides/sandbox-agents/
- https://openai.github.io/openai-agents-js/guides/sandbox-agents/clients/
- https://openai.github.io/openai-agents-js/guides/voice-agents/transport/
- https://openai.github.io/openai-agents-js/guides/voice-agents/quickstart/
- https://github.com/openai/openai-agents-python/blob/main/CHANGELOG.md
- https://github.com/openai/openai-agents-python/blob/main/src/agents/run_config.py
- https://github.com/openai/openai-agents-python/blob/main/src/agents/memory/openai_responses_compaction_session.py
- https://github.com/openai/openai-agents-python/tree/main/examples/realtime/twilio_sip
- https://github.com/openai/openai-agents-js/blob/main/packages/agents-core/CHANGELOG.md
- https://github.com/openai/openai-agents-js/blob/main/packages/agents-core/src/run.ts
- https://pypi.org/project/openai-agents/
- https://www.npmjs.com/package/@openai/agents
- https://developers.openai.com/api/docs/changelog
- https://developers.openai.com/api/docs/deprecations
Continue Reading#
- OpenAI Agents SDK for TypeScript: A Practical Guide - the basics this post skips: tools, structured outputs and streaming.
- OpenAI Agents SDK vs Claude Agent SDK: Building Agents on the Two Big Platforms - how the two platform SDKs compare when you are choosing a stack.
- OpenAI Agent Builder and Evals Are Shutting Down: Move the Agent Stack Into Code - the plan for moving off Agent Builder before November 30, 2026.
Get the next deep dive like this in your inbox
One email a week on OpenAI and the rest of the AI dev stack. Free.
Read next on AI coding tools
OpenAI Agents SDK for TypeScript: A Practical Guide
OpenAI Agents SDK for TypeScript, explained with code: agents, Zod tools, structured outputs, multi-agent handoffs, streaming and human approval steps.
10 min readOpenAI Agents SDK Evolution: What Ships in Production
Configurable memory, sandbox-aware orchestration, Codex-like filesystem tools. Here is how the new Agents SDK actually behaves in prod.
10 min readOpenAI Agents SDK vs Claude Agent SDK: Building Agents on the Two Big Platforms
OpenAI Agents SDK vs Claude Agent SDK compared - handoffs vs subagents, guardrails vs hooks, sandboxing, MCP support and how to choose a platform for your team.
9 min readNew here? Start with
Technical content at the intersection of AI and development. Building with AI agents, Claude Code, and modern dev tools - then showing you exactly how it works.








