This repository provides a Python SDK-style wrapper for Codex non-interactive local execution (codex exec).
- Contributing guide:
CONTRIBUTING.md - Security policy:
SECURITY.md - License:
LICENSE(MIT)
- Sync and async APIs for Codex execution
- JSONL event parsing and live stream handles (sync + async)
- Resumable sessions (
codex exec resume) with named session persistence - Retry/backoff engine with jitter and timeout-aware retry controls
- Sync timeout controls on run/resume/start methods
- Structured observability hooks (
CodexClientEvent) - Schema-constrained output support (
--output-schema) - Unit tests plus real CLI integration test scaffolding
Package: codex_local_sdk
Published distribution: codex-local-sdk-python
pip install codex-local-sdk-pythonThe Python import package remains:
from codex_local_sdk import CodexLocalClientAfter install, the package also includes:
codex-sdk skillto copy.agents/skills/codex-local-sdk-usagecodex-sdk docsto copycodex-sdk-documentation/
codex-sdk --help
codex-sdk skill
codex-sdk docs- Python 3.10+
- Codex CLI installed (
codex) and authenticated (or passCODEX_API_KEYper call) - On Windows, prefer
codex_bin="codex.cmd"when creatingCodexLocalClient
Official docs:
- Non-interactive mode: https://developers.openai.com/codex/noninteractive/
- CLI reference (
codex exec): https://developers.openai.com/codex/cli/reference/#codex-exec
Windows example:
from codex_local_sdk import CodexLocalClient
client = CodexLocalClient(codex_bin="codex.cmd")from codex_local_sdk import CodexExecRequest, CodexLocalClient, SandboxMode
client = CodexLocalClient()
result = client.run(
CodexExecRequest(
prompt="Summarize this repo in 5 bullets.",
model="gpt-5.3-codex",
reasoning_effort="medium",
sandbox=SandboxMode.READ_ONLY,
),
timeout_seconds=120,
)
print(result.final_message)from codex_local_sdk import CodexLocalClient
client = CodexLocalClient()
# Convenience wrapper forwards to CodexExecRequest fields.
client.run_prompt(
"Draft a migration plan.",
model="gpt-5.3-codex",
reasoning_effort="high",
)
session, _ = client.start_thread("Start a new task thread", json_output=True)
# Resume APIs expose reasoning and extra CLI passthrough args too.
follow_up = client.resume(
prompt="Continue with implementation checklist.",
session_id=session.session_id,
last=False,
json_output=True,
reasoning_effort="medium",
extra_args=("--config", "codex.toml"),
)import asyncio
from codex_local_sdk import CodexExecRequest, CodexLocalClient
async def main() -> None:
client = CodexLocalClient()
result = await client.run_async(
CodexExecRequest(prompt="Summarize this repository."),
timeout_seconds=120,
)
print(result.final_message)
live = await client.run_live_async(
CodexExecRequest(prompt="Stream event types", json_output=True)
)
async for event in live.iter_events():
print(event.type)
final = await live.wait()
print(final.turn_status)
asyncio.run(main())from codex_local_sdk import CodexExecRequest, CodexLocalClient, RetryPolicy
client = CodexLocalClient(
retry_policy=RetryPolicy(
max_attempts=3,
initial_backoff_seconds=0.5,
backoff_multiplier=2.0,
max_backoff_seconds=4.0,
retry_on_exit_codes=None, # retry any non-zero exit code
jitter_ratio=0.2, # +/-20% jitter
max_total_retry_seconds=10.0, # cap total retry window
retry_on_timeouts=True,
)
)
result = client.run(CodexExecRequest(prompt="Do work"), timeout_seconds=30)from codex_local_sdk import CodexLocalClient, JsonFileSessionStore
store = JsonFileSessionStore(".codex_sessions.json")
client = CodexLocalClient(session_store=store)
session, _ = client.start_thread(
"Create an initial plan",
session_name="repo-plan",
timeout_seconds=120,
)
result = client.resume(
"Continue with concrete tasks",
session_name="repo-plan",
last=False,
json_output=True,
timeout_seconds=120,
)
record = client.get_session_record("repo-plan")
print(session.session_id, result.turn_status, record.turn_count if record else None)from codex_local_sdk import CodexClientEvent, CodexExecRequest, CodexLocalClient
def on_event(event: CodexClientEvent) -> None:
print(event.type, event.operation, event.attempt, event.return_code)
client = CodexLocalClient(event_hook=on_event)
client.run(CodexExecRequest(prompt="Analyze repo"), timeout_seconds=60)schema = {
"type": "object",
"properties": {"project_name": {"type": "string"}},
"required": ["project_name"],
"additionalProperties": False,
}
result = client.run_with_schema(
prompt="Extract project metadata.",
schema=schema,
output_json_path="project_metadata.json",
timeout_seconds=120,
)examples/run_simple.pyexamples/run_json_events.pyexamples/run_live_stream.pyexamples/run_thread_session.pyexamples/run_with_schema.pyexamples/run_async.pyexamples/run_persistent_session_store.py
python3 -m unittest discover -s tests -p "test_*.py"export CODEX_INTEGRATION=1
export CODEX_API_KEY=your_key_here # optional if local Codex auth session already exists
python3 -m unittest discover -s tests/integration -p "test_*.py"CI workflows:
.github/workflows/unit.ymlruns on push/PR.github/workflows/integration.ymlis manual (workflow_dispatch) and secret-gated
This repository supports PyPI publishing via GitHub Actions trusted publishing (.github/workflows/publish-pypi.yml).
- Update version in:
codex-local-sdk-python/pyproject.tomlcodex-local-sdk-python/src/codex_sdk_python/__init__.py
- Commit and push to
main. - Create and push a version tag:
git tag v0.1.2
git push origin v0.1.2- GitHub Actions builds and publishes automatically to PyPI.
If trusted publishing is not configured yet, set it in PyPI project settings:
- Owner:
maestromaximo - Repository:
codex-local-sdk-python - Workflow:
publish-pypi.yml - Environment:
pypi