OpenAI Agents SDK skill for Codex
Few agents, bounded runs, guardrails that block. Codex designs Agents SDK workflows with the fewest agents that do the job, picks handoffs only when a specialist should own the conversation, and runs input guardrails before side-effecting tools can fire. Every run gets a turn limit, one state strategy and a named trace, so loops and leaks show up in tests instead of production.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- openai-agents-sdk Installs to ~/.agents/skills/openai-agents-sdk/ for every project on this machine.
curl -fsSL codexskills.dev/cli.mjs -o codex-skills.mjsnode codex-skills.mjs install openai-agents-sdk The CLI also searches, updates and removes skills. Run it with no arguments to pick from a list. Needs Node 20.12+.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- --project openai-agents-sdk Installs to .agents/skills/openai-agents-sdk/ in the current repository. Commit it and everyone who runs Codex there gets it.
curl -fsSLO codexskills.dev/downloads/openai-agents-sdk.zipunzip -o openai-agents-sdk.zip -d ~/.agents/skills/ The archive holds the skill folder with its generated agents/openai.yaml. Unzip into .agents/skills/ to scope it to a repo.
& ([scriptblock]::Create((irm codexskills.dev/install.ps1))) openai-agents-sdk PowerShell 5.1 or 7. Installs to %USERPROFILE%\.agents\skills\. Add -Project for the current repo.
104-line POSIX script. No sudo, no telemetry. Read install.sh
Use it in Codex
Start a prompt with $openai-agents-sdk, or pick it from /skills.
You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.
What Codex reads before loading it
Builds agent workflows with the OpenAI Agents SDK (openai-agents for Python, @openai/agents for TypeScript) — Agent definitions, tools, handoffs versus agents-as-tools, input/output/tool guardrails, sessions, max_turns, tracing and typed outputs. Use when creating a triage or multi-specialist agent, adding guardrails or human approval to an agent, debugging handoff loops or MaxTurnsExceeded, or deciding whether a plain Responses API loop is enough.
Use it when
- Building a multi-specialist assistant on OpenAI models
- Adding input, output or tool guardrails to an agent
- Persisting agent conversations with sessions
- Debugging handoff loops or MaxTurnsExceeded errors
Use something else when
- A single model call with one tool is enough: use openai-responses-api
- You need a checkpointed state graph with resumable human-in-the-loop steps: use langgraph-agents
How to check the result
Scenario tests assert the final agent and tool calls for each input, adversarial inputs trip the guardrail, and a low max_turns run proves MaxTurnsExceeded is caught.
The mistake it prevents
Leaving input guardrails in parallel mode on an agent with side-effecting tools, so the refund runs before the tripwire fires.
Done well means: the smallest number of agents that covers the job, each with a narrow
instruction set and tools; control flow (handoff vs. agent-as-tool) chosen deliberately;
guardrails that block before side effects; every run bounded by max_turns; and traces
named so a failed run can be found in minutes.
First decide if you need it
| Situation | Use |
|---|---|
| One model call, maybe one tool | Responses API directly (openai-responses-api) |
| One agent, several tools, a loop | Agents SDK single Agent (saves writing the loop) |
| Distinct specialists that should own the conversation | Agents SDK with handoffs |
| A coordinator that calls specialists and composes results | Agents SDK with agent.as_tool(...) |
| Durable, resumable graph with checkpoints across days | Consider langgraph-agents |
Before writing code
- Read
AGENTS.md; check whether the repo uses Python (openai-agents) or Node (@openai/agents). Install needs network:pip install openai-agentsornpm install @openai/agents zod. - Check the installed version. The SDK moves quickly; current Python docs use
from agents.decorators import tool, while older releases usefunction_toolfromagents. Match whatever the installed version exports — check withpython -c "import agents.decorators as d; print(dir(d))". - Model: set it per agent from config, or globally with
RunConfig(model=...). Check the models page; do not hard-code a name copied from an old example.
Core pattern (Python)
import os
from pydantic import BaseModel
from agents import Agent, Runner, RunConfig, RunContextWrapper
from agents.decorators import tool # older SDKs: from agents import function_tool
MODEL = os.environ["OPENAI_MODEL"]
@tool
async def lookup_order(ctx: RunContextWrapper, order_id: str) -> str:
"""Fetch an order's status.
Args:
order_id: Order id such as ORD-12345.
"""
order = await ctx.context.db.get_order(order_id) # app deps travel via context
return order.model_dump_json() if order else "not_found"
class Answer(BaseModel):
reply: str
escalate: bool
support = Agent(
name="Support",
instructions="Answer order questions. Never promise refunds.",
tools=[lookup_order],
output_type=Answer,
model=MODEL,
)
result = await Runner.run(
support,
"Where is ORD-12345?",
context=app_ctx, # your dataclass; never sent to the model
max_turns=8,
run_config=RunConfig(workflow_name="support-bot"),
)
answer: Answer = result.final_output
Docstrings become tool descriptions and Args: become parameter descriptions — write
them for the model. The context object is local dependency injection (DB handles, user
id); the model never sees it.
Handoffs vs. agents as tools
from agents import handoff
from agents.extensions import handoff_filters
from agents.extensions.handoff_prompt import RECOMMENDED_PROMPT_PREFIX
billing = Agent(name="Billing", handoff_description="Invoices, charges, payment methods",
instructions=f"{RECOMMENDED_PROMPT_PREFIX}\nResolve billing questions.")
refunds = Agent(name="Refunds", handoff_description="Refund requests and status",
instructions=f"{RECOMMENDED_PROMPT_PREFIX}\nProcess refund requests.")
triage = Agent(
name="Triage",
instructions=f"{RECOMMENDED_PROMPT_PREFIX}\nRoute to the right specialist.",
handoffs=[billing, handoff(refunds, input_filter=handoff_filters.remove_all_tools)],
)
# Coordinator keeps control and calls a specialist like a function:
translator = Agent(name="Translator", instructions="Translate to Spanish.")
writer = Agent(name="Writer", tools=[translator.as_tool(
tool_name="translate_to_spanish",
tool_description="Translate the given text to Spanish")])
- Handoff: the receiving agent takes over the conversation and produces the final output.
Use when the specialist should talk to the user directly.
result.last_agenttells you who answered. as_tool: the caller keeps control and receives the specialist’s output as a tool result. Use for composition, fan-out, or when you need one consistent output type.- Give every handoff target a
handoff_description; triage routes on it.
Guardrails
from agents import GuardrailFunctionOutput, InputGuardrailTripwireTriggered
from agents.decorators import input_guardrail
class Screen(BaseModel):
off_topic: bool
screener = Agent(name="Screen", instructions="Is this unrelated to our product?",
output_type=Screen, model=MODEL)
@input_guardrail(run_in_parallel=False) # block BEFORE the main agent spends tokens or calls tools
async def on_topic(ctx, agent, input):
r = await Runner.run(screener, input, context=ctx.context)
return GuardrailFunctionOutput(output_info=r.final_output,
tripwire_triggered=r.final_output.off_topic)
support.input_guardrails = [on_topic]
try:
await Runner.run(support, user_text, max_turns=8)
except InputGuardrailTripwireTriggered:
reply = "I can only help with orders."
Facts that bite:
- Input guardrails run only for the first agent in the run; output guardrails only for the agent that produces the final output. Specialists reached by handoff are not re-screened on input.
- Input guardrails default to running in parallel with the agent, so tools may already
have run when the tripwire fires. Use
run_in_parallel=Falsewhen the agent has side-effecting tools. If your installed version lacks the parameter, run the check beforeRunner.runyourself. - Tool guardrails (
tool_input_guardrail,tool_output_guardrail) wrap individual function tools and run on every call — use them for argument validation and secret redaction. They do not cover hosted tools or handoffs.
Sessions and state
from agents import SQLiteSession
session = SQLiteSession(f"user-{user_id}", "agents.db") # omit path for in-memory
await Runner.run(support, "Where is my order?", session=session)
await Runner.run(support, "And when will it arrive?", session=session)
Use exactly one of: session=, conversation_id=, previous_response_id=, or manual
result.to_input_list(). They are mutually exclusive. Production options include
SQLAlchemy, Redis and encrypted sessions under agents.extensions.memory.
Tracing
- Tracing is on by default and exports to the OpenAI traces dashboard. Set
RunConfig(workflow_name=..., group_id=<thread id>)so runs are findable. - Wrap multi-run workflows in
with trace("Nightly triage"):to get one trace. - PII: set
trace_include_sensitive_data=FalseinRunConfig(or envOPENAI_AGENTS_TRACE_INCLUDE_SENSITIVE_DATA=false) to drop model/tool payloads. - Disable entirely with
OPENAI_AGENTS_DISABLE_TRACING=1; tracing is unavailable for Zero Data Retention organizations. Add your own exporter withadd_trace_processor().
Gotchas
- No
max_turns→ a handoff ping-pong or tool retry loop burns budget until the SDK default limit. Set it explicitly and catchMaxTurnsExceeded. - Tool exceptions are by default returned to the model as error text, which can loop.
Return structured error strings the model can act on, or pass
failure_error_function=Noneto re-raise. - Side-effecting tools (refunds, emails, deletes) need a human approval step; the SDK
supports approval on tools (
needs_approval) in recent versions — confirm in the installed version, otherwise gate in your own code. - Agents that share one long instruction blob route poorly; keep each under a screen.
output_typemust be JSON-schema friendly (Pydantic model or dataclass); a root union is not allowed in strict structured output.
Verification
- Unit-test each tool function directly, without the model.
- Scenario tests: a list of
(input, expected_last_agent, expected_tool_calls)run against the real model behind an opt-in flag (needs network and spend). - Assert guardrail tripwires on 5–10 adversarial inputs.
- Open one trace and confirm span names, tool args and no leaked PII.
- Run with
max_turnslow (e.g. 3) to prove the exception path is handled.
Output contract
**Agents:** Triage → Billing, Refunds (handoffs); Writer uses Translator as tool
**Tools:** lookup_order (read), issue_refund (write, approval required)
**Guardrails:** on_topic (input, blocking), redact_secrets (tool output)
**State:** SQLiteSession keyed by user id
**Limits:** max_turns=8; tracing workflow_name="support-bot", sensitive data off
**Tests:** <files and results> What the zip contains
SKILL.md plus the interface file below. Nothing runs on install.
agents/openai.yaml
Gives the Codex app a display name, a one-line description and a starter prompt for this skill.
interface:
display_name: "OpenAI Agents SDK"
short_description: "Few agents, bounded runs, guardrails that block"
default_prompt: "$openai-agents-sdk Build a triage agent that hands off to billing and refunds specialists" About this skill
Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.