How to Use OpenAI Agents SDK for Coding Tasks
I was building a code review tool last month and hit a wall. I had a script that could analyze Python files, but I needed it to actually do things — run tests, check linting, fix issues, and hand off complex refactoring to a more capable model. Wiring all that together with raw API calls was turning into a mess of callback chains and state management. That's when I stumbled onto the OpenAI Agents SDK.
The SDK gave me a clean way to define agents with specific roles, give them tools, and let them hand work to each other. Here's what I learned setting it up for coding workflows.
The Problem I Was Solving
My team had a simple PR review bot, but it could only leave comments. I wanted it to:
- Read changed files
- Run linting and tests in isolation
- Suggest actual fixes (not just point out problems)
- Apply simple fixes automatically for things like import ordering
Doing this with a single LLM call was unreliable. The model would hallucinate test results or try to fix things it shouldn't touch. I needed separate agents with clear boundaries.
Getting Started
First, install the SDK:
pip install openai-agents
You'll need your API key set up:
export OPENAI_API_KEY="sk-..."
The SDK is provider-agnostic — it supports OpenAI's Responses and Chat Completions APIs, plus over 100 other LLMs through integrations. But for coding tasks, I stuck with GPT-4o since it handles code well.
Building My First Coding Agent
I started simple — an agent that reads a file and suggests improvements:
from agents import Agent, Runner
code_reviewer = Agent(
name="code-reviewer",
instructions="""You are a senior Python developer reviewing code.
Focus on:
- Bug risks and logic errors
- Performance issues
- Style violations (PEP 8)
- Missing error handling
Be specific. Reference line numbers. Suggest actual fixes, not just problems.""",
model="gpt-4o",
)
result = Runner.run_sync(code_reviewer, input="Review this code:\n\ndef get_user(id):\n db = connect()\n user = db.query(f'SELECT * FROM users WHERE id = {id}')\n return user")
print(result.final_output)
This worked immediately. The agent caught the SQL injection vulnerability and suggested parameterized queries. But it couldn't do anything about it — just talk.
Adding Tools So Agents Can Act
The real power comes from giving agents tools. I defined functions the agent could call:
from agents import Agent, Runner, function_tool
@function_tool
def read_file(path: str) -> str:
"""Read a file and return its contents."""
with open(path, "r") as f:
return f.read()
@function_tool
def run_linter(path: str) -> str:
"""Run ruff linter on a file and return the output."""
import subprocess
result = subprocess.run(
["ruff", "check", path],
capture_output=True,
text=True
)
return result.stdout or "No linting issues found."
@function_tool
def write_file(path: str, content: str) -> str:
"""Write content to a file. Use with caution."""
with open(path, "w") as f:
f.write(content)
return f"Successfully wrote to {path}"
reviewer_with_tools = Agent(
name="code-reviewer",
instructions="""You are a code review agent.
1. Read the file using read_file
2. Run the linter using run_linter
3. Analyze both the code and lint output
4. For simple fixes (import order, unused vars), apply them with write_file
5. For complex issues, just report them
Always read a file before writing to it. Never write without reading first.""",
tools=[read_file, run_linter, write_file],
model="gpt-4o",
)
result = Runner.run_sync(
reviewer_with_tools,
input="Review and fix /home/me/projects/myapp/utils.py"
)
The first surprise: the agent actually followed the instructions in order. It read the file, ran the linter, then made targeted fixes. I expected it to jump straight to writing, but the explicit step-by-step instructions kept it on track.
Mistake I made early on: I didn't include type hints and docstrings on my tool functions. The SDK uses those to tell the model what each tool does and what parameters it expects. When I left them off, the agent would call tools with wrong arguments — passing a list instead of a string, or missing required parameters entirely.
Handoffs: Building a Multi-Agent Workflow
Single agents are fine for simple tasks, but my review bot needed specialization. I wanted one agent for quick fixes and another for deep analysis. The SDK's handoff feature lets agents delegate to each other:
from agents import Agent, Runner, handoff
quick_fixer = Agent(
name="quick-fixer",
instructions="""You handle simple, mechanical code fixes:
- Import ordering (isort style)
- Removing unused imports
- Trailing whitespace
- Missing trailing newlines
Read the file, apply fixes, write it back. Don't overthink it.""",
tools=[read_file, write_file],
model="gpt-4o-mini", # Cheaper model for simple tasks
)
deep_analyst = Agent(
name="deep-analyst",
instructions="""You handle complex code review:
- Logic bugs and race conditions
- Security vulnerabilities
- Architecture concerns
- Performance bottlenecks
Read the file, analyze thoroughly, and provide a detailed report.
Do NOT modify files — only report findings.""",
tools=[read_file, run_linter],
model="gpt-4o", # Better model for complex analysis
)
orchestrator = Agent(
name="review-orchestrator",
instructions="""You triage code review requests.
- For simple style/formatting issues, hand off to quick-fixer
- For bugs, security issues, or architecture concerns, hand off to deep-analyst
- If both apply, hand off to deep-analyst first, then quick-fixer
Start by reading the file and running the linter to assess what's needed.""",
tools=[read_file, run_linter],
handoffs=[handoff(quick_fixer), handoff(deep_analyst)],
model="gpt-4o",
)
result = Runner.run_sync(
orchestrator,
input="Review /home/me/projects/myapp/auth/login.py"
)
This was the "aha" moment. The orchestrator read the file, decided it had both a SQL injection bug (complex) and import ordering issues (simple), and handed off to the deep analyst first, then the quick fixer. Each agent did its part without stepping on the other.
One thing that tripped me up: handoffs create a new agent run, so state doesn't automatically carry over. If the deep analyst finds something the quick fixer needs to know about, you need to pass that context explicitly through the handoff. I solved this by having the orchestrator summarize findings before each handoff.
Sandbox Agents for Safe Code Execution
The SDK recently added sandbox agents — agents preconfigured to work within containers for long-running tasks. This is huge for coding because you don't want an agent accidentally running rm -rf on your actual filesystem.
from agents import SandboxAgent, Runner
sandboxed_coder = SandboxAgent(
name="sandboxed-coder",
instructions="""You are a coding agent working in a sandbox.
You can run commands, edit files, and execute code safely.
Run tests after making any changes to verify your work.""",
model="gpt-4o",
)
result = Runner.run_sync(
sandboxed_coder,
input="Fix the failing test in /workspace/tests/test_auth.py"
)
I haven't used sandbox agents extensively yet — the setup requires Docker and some configuration that wasn't immediately obvious from the docs. But for any workflow where the agent runs arbitrary code, this is the right approach.
Guardrails: Preventing Agents From Going Rogue
I learned about guardrails the hard way. My quick-fixer agent once "fixed" a file by rewriting 300 lines when only 2 needed changing. Guardrails let you validate input and output:
from agents import Agent, GuardrailFunctionOutput, input_guardrail
@input_guardrail
def check_file_path(ctx, agent, input_data):
"""Ensure the agent only works on files in allowed directories."""
# Extract file path from input
if "/etc/" in str(input_data) or "/root/" in str(input_data):
return GuardrailFunctionOutput(
output_info="Blocked: path outside allowed directories",
tripwire_triggered=True,
)
return GuardrailFunctionOutput(
output_info="Path OK",
tripwire_triggered=False,
)
safe_reviewer = Agent(
name="safe-reviewer",
instructions="Review and fix code in the project directory only.",
tools=[read_file, write_file, run_linter],
input_guardrails=[check_file_path],
model="gpt-4o",
)
When a guardrail triggers, the agent stops immediately. This saved me from an agent that tried to "helpfully" edit my .bashrc.
Tracing: Debugging Agent Workflows
The built-in tracing was unexpectedly useful. When my multi-agent workflow produced weird results, I could see exactly what happened:
from agents import trace
with trace("code-review-workflow"):
result = Runner.run_sync(
orchestrator,
input="Review /home/me/projects/myapp/api/handlers.py"
)
The trace shows every tool call, handoff, and model response. I caught a bug where the deep analyst was handing back to the orchestrator instead of terminating, creating an infinite loop. The trace made it obvious within seconds.
Practical Tips From My Experience
Use cheaper models for mechanical tasks. My quick-fixer runs on gpt-4o-mini and handles 80% of the work at 1/10th the cost. Only the deep analyst needs gpt-4o.
Be extremely specific in instructions. "Fix code" leads to chaos. "Read the file, run ruff, apply only the fixes ruff suggests, write the file back" works reliably.
Always read before writing. I put this in every agent's instructions now. Without it, agents sometimes write files based on their training data rather than the actual file contents.
Type-hint your tools. The SDK uses type annotations to generate tool schemas for the model. Missing types = confused agents.
Test with simple files first. I started testing on a 2000-line module and couldn't tell if failures were the agent's fault or my tool definitions. Small test files made debugging much faster.
Honest Limitations
The SDK is lightweight, which is both a strength and a weakness. There's no built-in file diffing — when my agent writes a file, it overwrites the whole thing. I had to add git integration separately so I could review changes before accepting them.
Context management across handoffs is manual. If agent A discovers something agent B needs to know, you have to structure your instructions to pass that information along. The SDK doesn't automatically share conversation history between agents in a workflow.
The documentation is still sparse in places. I spent an hour figuring out the correct way to structure handoff prompts because the examples didn't cover my use case. The examples directory on GitHub is more helpful than the docs.
Sandbox agent setup requires Docker knowledge that isn't well-documented yet. If you're not comfortable with containers, stick with regular agents and strict guardrails.
Despite these rough edges, the SDK has replaced about 400 lines of hand-rolled orchestration code in my project with something more reliable and easier to modify. The multi-agent pattern — orchestrator triaging to specialists — maps well onto real coding workflows in a way that single-prompt approaches never did for me.