The PyPI package is codexcw (in bindings/python), a native extension backed
by the Rust core.
pip install codexcwThe codex executable must be on PATH, authenticated, and new enough to
support codex exec --json. Defaults are automation-friendly: read-only sandbox,
approval never, ephemeral sessions, color off, git-check skipped.
The package ships both: a synchronous API in codexcw and an asyncio API in
codexcw.aio with the same shapes. Every recipe below is shown in both.
# Synchronous
from codexcw import Runner, Request
runner = Runner()
result = runner.run(Request(prompt="diga oi"))
print(result.final_message)# Asynchronous (codexcw.aio)
import asyncio
from codexcw import Request
from codexcw.aio import Runner
async def main():
runner = Runner()
result = await runner.run(Request(prompt="diga oi"))
print(result.final_message)
asyncio.run(main())# Sync
from codexcw import Runner, Request
runner = Runner()
session = runner.start(Request(prompt="resuma este repo"))
for event in session.events():
if event.type == "item.completed" and event.item.type == "agent_message":
print(event.item.text)
result = session.wait()
print("tokens:", result.usage.total_tokens)# Async
from codexcw import Request
from codexcw.aio import Runner
runner = Runner()
session = await runner.start(Request(prompt="resuma este repo"))
async for event in session.events():
if event.type == "item.completed" and event.item.type == "agent_message":
print(event.item.text)
result = await session.wait()
print("tokens:", result.usage.total_tokens)on_event runs for every event. Raising cancels the run.
# Sync
def on_event(event):
if event.type == "item.completed" and event.item.type == "command_execution":
print("$", event.item.command)
runner.run(Request(prompt="trabalhe"), on_event)# Async — the callback may be a plain function or a coroutine.
async def on_event(event):
if event.type == "item.completed" and event.item.type == "command_execution":
print("$", event.item.command)
await runner.run(Request(prompt="trabalhe"), on_event)# A callback that aborts on the first command execution.
from codexcw import CodexcwError
def stop_on_command(event):
if event.type == "item.started" and event.item.type == "command_execution":
raise RuntimeError("stop")
try:
runner.run(Request(prompt="..."), stop_on_command)
except RuntimeError as err:
print("cancelled:", err)# Sync
first = runner.run(Request(prompt="crie um arquivo TODO.md"))
thread_id = first.thread_id
second = runner.run(Request(prompt="agora adicione 3 itens ao TODO.md", resume_id=thread_id))
print(second.final_message)# Async
first = await runner.run(Request(prompt="crie um arquivo TODO.md"))
second = await runner.run(Request(prompt="continue", resume_id=first.thread_id))# Resume the most recent thread, or disable cwd filtering with resume_all:
runner.run(Request(prompt="continue", resume_last=True))
runner.run(Request(prompt="continue", resume_id=thread_id, resume_all=True))Resume runs reject
dir,add_dirs, andprofile— raised as aCodexcwErrorwithkind == "invalidRequest".
# Read-only is the default. Let Codex write inside the workspace:
runner.run(Request(prompt="refatore o pacote foo", sandbox="workspace-write"))
await runner.run(Request(prompt="refatore o pacote foo", sandbox="workspace-write"))
# Remove sandbox filesystem restrictions:
runner.run(Request(prompt="...", sandbox="danger-full-access"))# Defaults to "never". The safer interactive middle ground:
req = Request(prompt="...", sandbox="workspace-write", approval="on-request")
runner.run(req) # sync
await runner.run(req) # asyncDanger.
dangerously_bypass_sandboxruns Codex with--dangerously-bypass-approvals-and-sandbox: no sandbox, no approval prompts. Only use this in a disposable, fully-trusted environment.
runner.run(Request(prompt="...", dangerously_bypass_sandbox=True))
await runner.run(Request(prompt="...", dangerously_bypass_sandbox=True))
# Run enabled hooks without persisted trust:
runner.run(Request(prompt="...", dangerously_bypass_hooks=True))# Sync
group = runner.run_many(
[Request(prompt="review A"), Request(prompt="review B"), Request(prompt="review C")],
max_concurrent=2,
)
for run_event in group.events():
print(f"[{run_event.index}] {run_event.event.type}")
for r in group.wait():
if r.error is not None:
print(f"[{r.index}] failed:", r.error.kind, r.error)
else:
print(f"[{r.index}] {r.result.final_message}")# Async
group = await runner.run_many(
[Request(prompt="review A"), Request(prompt="review B"), Request(prompt="review C")],
max_concurrent=2,
)
async for run_event in group.events():
print(f"[{run_event.index}] {run_event.event.type}")
results = await group.wait()from codexcw import ConfigOverride
req = Request(prompt="...", config=[
ConfigOverride(key="model_reasoning_effort", value='"high"'),
ConfigOverride(key="tools.web_search", value="true"),
])
runner.run(req)
await runner.run(req)import json
schema = json.dumps({
"type": "object",
"properties": {"summary": {"type": "string"}},
"required": ["summary"],
})
req = Request(prompt="resuma o repo como JSON", output_schema=schema, output_last_message_path="out.json")
result = runner.run(req) # sync
print(json.loads(result.final_message))
result = await runner.run(req) # asyncreq = Request(prompt="...", dir="/work/project", add_dirs=["/work/shared", "/work/vendor"])
runner.run(req)
await runner.run(req)req = Request(prompt="...", model="o3", profile="work")
runner.run(req)
await runner.run(req)# Prompt via stdin only:
runner.run(Request(stdin="diga oi"))
await runner.run(Request(stdin="diga oi"))
# Prompt plus extra stdin context (wrapped in <stdin> markers):
runner.run(Request(prompt="resuma o diff abaixo", stdin=large_diff))runner = Runner(executable="/opt/codex/bin/codex", env={"CODEX_HOME": "/tmp/codex-home"})Failures raise a typed CodexcwError whose kind discriminates the cause.
from codexcw import CodexcwError
try:
result = runner.run(Request(prompt="...")) # or: await runner.run(...)
print(result.final_message)
except CodexcwError as err:
if err.kind == "exit":
print(f"codex exited {err.code}: {err.stderr}")
elif err.kind == "codex":
print("codex reported:", err)
elif err.kind == "decode":
print(f"bad JSONL on line {err.line}")
elif err.kind == "promptRequired":
print("prompt or stdin is required")
else:
print(err.kind, err)# Sync
session = runner.start(Request(prompt="..."))
session.cancel() # stops the child process
session.wait()
# Async
session = await runner.start(Request(prompt="..."))
session.cancel()
await session.wait()See the README for the cross-language overview and AGENTS.md for the project guide.