A bare-metal starting point for building an AI agent on the OpenAI Agents SDK, with a Codex-OAuth proxy so you can use a ChatGPT subscription as the model backend instead of a paid API key. No framework lock-in, no messaging SDK -- copy it, swap the tools and system prompt, and you have a new agent.
agent.py the agent: model backend + tools + persona + CLI entry
server/codex_proxy.py Codex-OAuth -> OpenAI Responses proxy (the reusable part)
pyproject.toml deps: openai-agents, httpx, fastapi, uvicorn
.env.example backend / model configuration
deploy/launchd.plist.example durable macOS run (survives crashes + reboots)
agent.py picks its model from LLM_BACKEND:
| Backend | What it uses | When |
|---|---|---|
codex (default) |
ChatGPT subscription via the local proxy (Responses API, gpt-5.5) | free with a ChatGPT plan; no API key |
openai |
any chat-completions endpoint (OPENAI_BASE_URL + OPENAI_API_KEY) |
api.openai.com, OpenRouter, a local model |
Everything else in agent.py is backend-agnostic -- build_model() is the only
place that branches.
uv syncThe proxy reads your Codex credentials from ~/.codex/auth.json. Create it once by
logging in with the Codex CLI:
codex login # produces ~/.codex/auth.jsonThen start the proxy (leave it running in its own terminal):
uv run uvicorn server.codex_proxy:app --port 8088Sanity check:
curl -s http://localhost:8088/health
# {"ok": true, "account": "abcd1234...", "model": "gpt-5.5"}Only one proxy on :8088 is needed per machine -- multiple agents can share it.
uv run python agent.py "your question here"
uv run python agent.py # default demo promptUse a real API key instead of the proxy:
LLM_BACKEND=openai OPENAI_API_KEY=sk-... MODEL=gpt-4o-mini uv run python agent.py "hi"Edit three things in agent.py:
- Tools -- each
@function_toolis a capability. The docstring and type hints are the spec the model sees, so write them for the model. The includedhttp_getis just an example; delete it. INSTRUCTIONS-- the system prompt: role, tone, rules.TOOLS-- the list wired into the agent.
The harness (build_model / build_agent / main) usually needs no changes.
Runner.run(..., max_turns=12) caps the tool loop; raise it for deeper tasks.
That's the whole surface. For anything bigger (a web server, a message listener,
a scheduler), import build_agent and call Runner.run from your own code.
deploy/launchd.plist.example keeps a long-running agent alive on macOS across
crashes and reboots, independent of any terminal. Copy it to
~/Library/LaunchAgents/, fill in your name + paths, and
launchctl bootstrap gui/$(id -u) <plist>. If you use the codex backend, give the
proxy its own plist (a second one running uvicorn on :8088) so the whole stack
survives a reboot.
- Reads the Codex OAuth token from
~/.codex/auth.json, decodes the JWT, and auto-refreshes it when within 5 minutes of expiry (writing the new token back atomically). - The Codex backend only streams (SSE). The Agents SDK's non-streaming path posts
{stream: false}and expects oneResponseJSON, so the proxy streams upstream and aggregates the SSE events into that final object. - Strips fields the gpt-5.x / Codex backend rejects (
temperature,top_p, ...) and, because it runs withstore: false, drops echoed-back reasoning items that would otherwise 404 on multi-turn tool loops. - Tunables via env:
CODEX_MODEL(defaultgpt-5.5),CODEX_REASONING(minimal|low|medium|high, defaultlow).
The codex proxy reuses a ChatGPT subscription token outside the Codex CLI. It is
unofficial, ToS-gray, and may break if OpenAI changes the backend. Personal use
only. For anything production or shared, use LLM_BACKEND=openai with a real key.