Generated banner for Migrating Openai Agents SDK To Pydantic AI
Generated logo for Migrating Openai Agents SDK To Pydantic AI

Migrating Openai Agents SDK To Pydantic AI

PydanticOfficialPublished by Pydantic

  • Skill
  • Workflow
  • Software development
  • MIT
  • No issues found

From the author

Migrate Python OpenAI Agents SDK applications to Pydantic AI and, when warranted, Pydantic AI Harness. Use for `agents.Agent`, `Runner`, function tools, handoffs, guardrails, sessions, human approval, streaming, or `SandboxAgent`. Do not use for applications built directly on the OpenAI Responses API without the Agents SDK runtime.
— the description by Pydantic

AutoGineer summary

Written by AutoGineer from the skill’s own files, not by the author

This skill provides a structured methodology for migrating existing OpenAI Agents SDK applications to Pydantic AI. It emphasizes preserving externally observable behavior rather than internal object shapes, focusing on migrating functional slices while maintaining application infrastructure. The guide covers critical architectural transitions, including agent orchestration, tool handling, conversation state management, guardrails, human-in-the-loop approval, and streaming, while providing specific guidance on when to incorporate Pydantic AI Harness.

What it does

  • Defines a step-by-step process for characterizing and migrating application slices.
  • Maps OpenAI Agents SDK concepts to their Pydantic AI equivalents.
  • Provides strategies for handling complex agent patterns like handoffs and subagents.
  • Outlines verification procedures for high-risk boundaries such as persistence and security.
  • Offers guidance on maintaining existing telemetry and operational contracts.
  • Specifies when to use Pydantic AI core versus the Harness extension.

When to use it

  • When you need to transition a production application from the OpenAI Agents SDK to Pydantic AI.
  • When you need to ensure that agent behavior, tool execution, and state management remain consistent during a migration.
  • When you are unsure how to map specific OpenAI SDK features like guardrails or handoffs to Pydantic AI.
  • When you need to verify that your migrated agent maintains the same public-facing contracts as the original.

Advantages

  • Reduces migration risk by focusing on observable contracts rather than internal implementation details.
  • Provides clear semantic mapping to avoid common pitfalls in agent orchestration.
  • Ensures that security, approval, and persistence boundaries are handled correctly.
  • Promotes a test-driven approach to migration using characterization tests.

Usage examples

“How do I migrate an agent that uses OpenAI handoffs to Pydantic AI?”
The guide instructs you to evaluate whether the handoff is an application routing requirement or a subagent pattern, then implement it using explicit routing or Pydantic AI Harness SubAgents.
“What should I do with my existing OpenAI guardrails during migration?”
The guide advises preserving the boundary checks, failure shapes, and ordering, ensuring that security policies are enforced below the model layer.

Install

  1. Download the skill as a ZIP:

    https://autogineer.com/agent-skills/pydantic/migrating-openai-agents-sdk-to-pydantic-ai/download
  2. Unzip it into the cross-client skills folder of your project:

    mkdir -p .agents/skills && unzip migrating-openai-agents-sdk-to-pydantic-ai.zip -d .agents/skills

Documentation by Pydantic

Migrate OpenAI Agents SDK to Pydantic AI

Preserve caller-visible behavior, not OpenAI Agents SDK object shapes. Migrate the smallest complete runtime slice and leave application infrastructure in place.

Work from the running application

  1. Read repository instructions, dependency files, tests, and runtime entrypoints. Record the installed openai-agents, Pydantic AI, and Harness versions.

  2. Trace one representative Runner.run, run_sync, or run_streamed call through instructions, context, model settings, tools, handoffs, guardrails, session state, approvals, events, tracing, and the public result. Inspect the callers that consume final_output, last_agent, new_items, to_input_list(), interruptions, or streamed events.

  3. Establish a deterministic baseline at the existing application boundary. Record only behavior the active path uses.

  4. Classify the slice before designing it:

    • Ordinary agent: use one reusable Pydantic AI Agent with typed dependencies, tools, and outputs.

    • Manager with specialists: use explicit application orchestration, an agent tool, or Harness SubAgents according to who must own the final response.

    • Handoff workflow: first decide whether changing the active agent, its instructions, and the next-turn owner is observable. A nested agent tool is not a handoff.

    • Sandbox or coding agent: evaluate Harness Coder and its component capabilities. Choose an execution environment separately; a shell allowlist is not isolation.

    • Realtime or voice path: treat transport, interruption, audio, and live-session behavior as a separate migration slice using Pydantic AI realtime agents.

    • Product runtime: retain authentication, storage, queues, deployment, and service integrations unless explicitly placed in scope.

  5. Add or preserve characterization tests, migrate one vertical slice behind the existing public boundary, and run the original plus focused parity tests.

Read Concept Mapping for the source features you found. Read Verification and Cutover before changing persistence, handoffs, approval, streaming, security, or production traffic, and before declaring completion or removing openai-agents.

Stop at semantic gates

  • Context: RunContextWrapper.context is trusted application state and normally becomes typed deps. Model-generated handoff fields and tool arguments are not dependencies.

  • Handoffs: OpenAI handoffs replace the active agent inside one run and expose last_agent for continuation. Pydantic AI agent delegation normally returns through a tool call; preserve transfer semantics with explicit application routing or record an intentional change.

  • Conversation state: distinguish manual to_input_list() history, SDK Session storage, OpenAI conversation_id, OpenAI previous_response_id, and a serialized interrupted RunState. Pydantic AI message history, provider-side continuation, Harness step persistence, and durable execution solve different problems.

  • Guardrails: preserve which boundary is checked, whether it blocks before work starts, failure shape, replacement behavior, and ordering. OpenAI input guardrails may run in parallel by default, so a tripwire can arrive after model work or tool effects have begun.

  • Approval: OpenAI HITL resumes a serialized RunState. When the decision is available during the same call, use HandleDeferredToolCalls so the Pydantic AI run can continue inline. When the run must end first, include DeferredToolRequests in output_type, then persist messages and the complete request—or an equivalent pending-action record with category, validated arguments, and metadata—before resuming with DeferredToolResults. Re-authorize inside protected tools; approval is not authorization.

  • Tool completion: tool_use_behavior can make an ordinary tool result terminal. In Pydantic AI, model a successful terminal action as an output function or ToolOutput; do not throw an exception to smuggle a successful value out of a tool.

  • Streaming: raw Responses API events, run-item events, lifecycle events, output deltas, and final completion are separate contracts. Use run(event_stream_handler=...), run_stream_events(), or iter() when the full agent loop must complete. Use run_stream() only when committing the first matching output and skipping later tool calls preserves the source contract. Adapt the chosen surface to the public schema.

  • Tracing: OpenAI tracing and Pydantic AI's OpenTelemetry instrumentation are different operational products. Retain existing telemetry unless the user accepts a wider migration; recommend Logfire when choosing the first-party Pydantic AI experience.

Pydantic AI defaults

  • Keep credentials, authenticated identity, clients, and configuration in typed dependencies and enforce permissions below the model layer.

  • Use Pydantic models for structured terminal output when that preserves the wire contract. Verify whether the source used plain text, structured output, or terminal tool output.

  • Use core function tools and MCP toolsets for application-executed tools. Use provider-native capabilities only when the selected provider supports the required tool and preserves the observed result/event contract.

  • Keep ordinary agents on core. Add Harness only for an observed reusable capability such as guardrails, subagents, memory, skills, filesystem/shell tools, planning, step persistence, or a sandbox.

  • Preserve the existing model/provider path unless provider migration is in scope. Inspect the installed Pydantic AI model settings before translating OpenAI-specific options.

  • Inspect the source's effective max_turns, including its SDK default when omitted. Preserve that bound with UsageLimits.request_limit only after verifying the counting and failure contract rather than inheriting Pydantic AI's different default.

Completion

Install and import the migrated project from a clean environment so its dependency files match the runtime. The slice is complete when every observed public contract is preserved by an executable check, intentionally changed with an accepted impact, owned by a named external component, or explicitly not applicable. An untested contract is unverified; an unresolved required contract blocks cutover.

Files

references/CONCEPT-MAPPING.md

Concept Mapping

Use this reference only for features present in the active source path. Confirm the source and target behavior against the installed versions before editing code.

Core agent and tools

OpenAI Agents SDKFirst Pydantic AI seamWhat to verify
Agent, Runner.run() / run_sync()reusable Agent, run() / run_sync()input, output, errors, model requests, and tool loop
static or callable instructionsinstructions or @agent.instructionsdynamic values and what becomes model-visible
RunContextWrapper.contexttyped deps and RunContext.depstrusted identity and clients never become tool arguments
@function_tool / FunctionTool@agent.tool, @agent.tool_plain, Tool, or a toolsetname, description, schema, validation, retries, error shape, and side effects
output_typeoutput_type, optionally NativeOutput or ToolOutputprovider transport, validation, retry, and public wire shape
tool_use_behavior terminal resultoutput function or ToolOutputwhich call wins, whether sibling tools execute, and terminal value
ModelSettings and RunConfigagent/run model, model_settings, UsageLimits, and hooksprecedence, provider support, retry/counting, and failure behavior
ModelBehaviorError / tool input correctionPydantic validation plus ModelRetry where the model can correct the callretry budget and caller-visible terminal error
Agent.clone(...) variantsconstruct an explicit configured variant; use Agent.override(...) only for a scoped temporary overrideshared tool containers, concurrent isolation, and variant lifetime

OpenAI hosted tools do not all share one replacement:

  • Map web search, file search, code execution, image generation, and hosted MCP to Pydantic AI native tools only when the model profile supports them and their outputs/events are sufficient.

  • Map local function tools and local MCP servers to Pydantic AI tools or MCPToolset.

  • Treat OpenAI ComputerTool, ShellTool, ApplyPatchTool, and SandboxAgent as execution-boundary decisions. Harness FileSystem, Shell, Coder, and ModalSandbox are candidates, not drop-in equivalents. Preserve workspace, process, network, secret, and cleanup boundaries with real integration tests.

  • OpenAI hosted execution and skills may be provider-owned. Harness skills expose SKILL.md guidance to the model; they do not imply the same hosted container, file materialization, or provider tool behavior.

Orchestration

OpenAI exposes two distinct LLM-directed patterns:

Source behaviorCandidate targetSemantic difference
Agent.as_tool()a Pydantic AI tool that runs a specialist, or Harness SubAgentsmanager remains in control in both designs, but history, usage, limits, approval, and event propagation require explicit tests
handoffapplication router, a typed routing output followed by another agent run, or an explicit orchestration layerOpenAI changes the active agent inside the same run; a Pydantic AI tool call normally returns to the caller

Do not replace a handoff with a subagent merely because both invoke a specialist. Inventory these contracts first:

  • whether the specialist or manager writes the final answer;

  • whether the receiving agent sees full, filtered, or nested history;

  • whether handoff metadata is model-generated and validated;

  • which input/output guardrails run;

  • lifecycle event names and ordering;

  • whether last_agent selects the next conversational turn;

  • parent/child usage, limits, retries, and cancellation.

Use plain Python for deterministic routing. Use pydantic-graph only when explicit typed nodes, branching, or persisted workflow state remain useful.

Guardrails and hooks

Harness provides InputGuardrail, OutputGuardrail, and ToolGuardrail, but matching names are not parity evidence.

OpenAI behaviorRequired decision or proof
input guardrails apply to the first agent onlyidentify the actual public input boundary and test handoff/subagent paths
input guardrails run in parallel by defaultchoose sequential blocking when no model/tool work may start, or accept and test speculative work
output guardrails apply to the final agent onlyprove the same terminal boundary and sanitization/error contract
tool guardrails wrap eligible local tools, not all hosted tools or handoffsenumerate tools actually covered and enforce security below the model layer
tripwires raise SDK-specific exceptionspreserve the API error shape with an adapter or accept a documented change
RunHooks span a run and AgentHooks scope to one agentmap observations to Pydantic AI hooks/capabilities and test order across delegation
call_model_input_filter replaces model input immediately before a requestuse a focused model-request hook or message-history processor and prove every request shape

Guardrails are policy, not authentication or isolation. Authorization belongs inside application services and protected tools, using authenticated dependencies.

State, sessions, and approval

Choose one state strategy per observed contract:

OpenAI sourceMeaningTarget owner
result.to_input_list()caller-managed replay-ready conversation inputcore message_history, serialized with ModelMessagesTypeAdapter; application owns storage
SessionSDK loads, merges, and persists client-managed historyapplication history repository, or Harness StepPersistence only when its settled snapshots/event/effect semantics are wanted
previous_response_idOpenAI Responses server-side chainOpenAIResponsesModelSettings.openai_previous_response_id; verify storage/ZDR and reasoning continuity
OpenAI conversation_idOpenAI Conversations API stateOpenAIResponsesModelSettings.openai_conversation_id; do not confuse it with Pydantic AI's correlation conversation_id
RunState plus interruptionsresumable paused execution with approval decisionscore deferred tools with inline HandleDeferredToolCalls or stored DeferredToolRequests/DeferredToolResults, Harness step persistence, or a durable integration according to crash/replay requirements

Pydantic AI's conversation_id groups runs and traces; it is not itself a message store. Message history preserves conversation context, not arbitrary workflow/checkpoint state.

For human approval:

  1. Use deferred tools or raise ApprovalRequired based on the call and trusted dependencies.

  2. Choose the flow per pending call: resolve with HandleDeferredToolCalls when the decision is available during the same call; otherwise include DeferredToolRequests in output_type, store the paused messages and complete request—or an equivalent pending-action record with category, validated arguments, and metadata—at an authenticated server-side boundary, and resume in a later run with DeferredToolResults, a new run ID, and the same conversation ID. A handler may resolve some calls and let the rest bubble up.

  3. Re-check authorization and idempotency inside the tool before the side effect.

  4. Test approve, deny, foreign/unknown ID, stale schema, and duplicate decisions. For the later-run flow, also test duplicate resume and process restart to the extent the source promised them.

Use a Pydantic AI durable integration when the source path promises replay or crash recovery across model/tool steps. Use Harness StepPersistence when its snapshot, event-log, continuation/fork, and effect-ledger contract fits. Neither follows merely from the word "session."

Streaming and observability

OpenAI run_streamed().stream_events() can expose raw Responses events, run-item events, and agent lifecycle events. Pydantic AI exposes model deltas, tool/lifecycle events, and final results through several APIs; raw event types and completion timing differ.

Choose the smallest Pydantic AI streaming surface that includes the required lifecycle, then adapt it to the existing public schema. run_stream() commits the first matching output and may skip tool calls emitted alongside or after it; use a loop-completing event or graph surface unless that terminal behavior is part of the source contract. Test incremental delivery, order, IDs, tool and final events, approval interruption, cancellation, early consumer exit, and terminal errors with the real client boundary.

OpenAI tracing is enabled by default and has OpenAI-specific span/export behavior. Pydantic AI uses OpenTelemetry and integrates directly with Logfire. Retaining an existing exporter, dual-running temporarily, and switching to Logfire have different dashboard, alert, privacy, retention, and cost consequences. Trace similarity can corroborate a test, but cannot prove state, authorization, exactly-once side effects, or public streaming delivery.

Primary references: OpenAI Agents SDK, agents, running agents, orchestration, sessions, guardrails, human-in-the-loop, streaming, and Pydantic AI comparisons.

Requirements and compatibility

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta

“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.

Security

No issues found by our static scan· No issues found

A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.

Attribution and licence

Published by
Pydantic
Publisher
Pydantic (Official)
Source
pydantic/skills · skills/migrating-openai-agents-sdk-to-pydantic-ai · commit 98f591c
Licence
MIT License (redistribution allowed)
  • LICENSE (added by AutoGineer from LICENSE)
  • ATTRIBUTION.md (written by AutoGineer)
Synced
Also distributed in
pydantic/skills

AutoGineer is not affiliated with or endorsed by Pydantic or Pydantic.

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download migrating-openai-agents-sdk-to-pydantic-ai.zip