A minimal Python extraction of Codex's main agent loop
Project description
pycodex
English README. Chinese version: README_ZH.md
PyPI distribution name: python-codex
Import path and CLI command remain pycodex.
This repository extracts the core Codex agent loop from upstream Codex
(https://github.com/openai/codex) into a deliberately small Python version,
while preserving the two most important layers:
submission_loop: sequentially consumes submitted operations.run_turn: keeps executingmodel sample -> tool call -> feed tool result back into the modelinside a single turn until a final answer is reached.
Relevant Rust reference points:
codex-rs/core/src/codex.rs->submission_loopcodex-rs/core/src/codex.rs->run_turncodex-rs/core/src/codex.rs->run_sampling_requestcodex-rs/core/src/tools/router.rs->ToolRoutercodex-rs/core/src/stream_events_utils.rs->handle_output_item_done
Quick Start
Install dependencies first:
uv sync
Try the real entry points:
uv run pycodex "Reply with exactly OK."
uv run pycodex
Design Tradeoffs
This is not a 1:1 port of the Rust implementation. The current goal is a minimal reusable kernel that converges on the upstream behavior over time:
- Use a thin
ModelClientprotocol to abstract the model side. - Use
ToolRegistryto manage tool specs and executors. - Use
AgentLoopto implement the core closed loop. - Use
AgentRuntimeto preserve the outer submission queue so it can keep converging toward Rust'ssubmission_looplater.
Intentionally not included yet:
- TUI / streaming incremental rendering
- MCP / connectors / sandbox / approvals
- memory / compact / review mode
- a full production OpenAI adapter surface
All of those can be layered on later. For now, the project is focused on nailing the core tool-augmented reasoning loop first.
Layout
pycodex/protocol.py: minimal conversation item / prompt / event protocolpycodex/model.py: model client protocol and Responses API adapterpycodex/cli.py: single-turn and interactivepycodexCLI entry pointspycodex/tools/base_tool.py:BaseTool,ToolRegistry,ToolContextpycodex/tools/: concrete tool implementationspycodex/agent.py: inner turn looppycodex/runtime.py: outer submission queuetests/test_agent.py: core behavior tests
Current Alignment Status
Current progress is easiest to read in layers:
- prompt/context alignment:
- on the non-interactive
execpath,instructionsandinputalready match upstream Codex; - this layer is now mainly handled by
pycodex/context.pyplus vendored prompt data.
- on the non-interactive
- turn-loop semantic alignment:
AgentLoopno longer uses a fixed 12-iteration cap by default;- like upstream, it now converges naturally based on whether there is still follow-up work or tool handoff to do;
- the local iteration-limit parameter is gone.
- request-level alignment:
- the non-interactive
execrequest body is mostly aligned; - the default CLI non-exec first request now also follows the upstream
codex-tui+<collaboration_mode>path; - the default CLI two-turn main-thread request/header behavior has also been
captured and aligned, including omitting
workspaceson later turns; - the remaining work is now more about outer behavior branches than this already-compared request/header path.
- the non-interactive
- tool round-trip alignment:
- the Default-mode unavailable path for
request_user_inputis aligned to real upstream captures; - the Plan-mode happy path is also aligned at the tool/protocol layer based
on upstream source: it forces
isOther=true, requires non-emptyoptions, and returns structured answers as a JSON string plussuccess=true; - there is now a deterministic round-trip comparison helper,
tests/compare_request_user_input_roundtrip.py, built on the proxy mode intests/fake_responses_server.py; against the locally installedcodex-cli 0.115.0, the only remaining Plan-mode live-capture schema difference is thatpycodexincludessuccess=trueinfunction_call_output.
- the Default-mode unavailable path for
See docs/ALIGNMENT.md for more detailed notes.
Live Model Integration
If this machine already has a Codex CLI configuration, pycodex can reuse the
model, model_provider, base_url, and env_key from
~/.codex/config.toml directly:
from pycodex import ResponsesModelClient
client = ResponsesModelClient.from_codex_config()
The current implementation uses the streaming OpenAI-compatible /responses
endpoint. This path has already been validated against the local
~/.codex/config.toml setup.
When launched through the CLI, pycodex also loads .env from the same
configuration directory before reading config (typically ~/.codex/.env), so
provider keys and similar environment variables can live there. To match
upstream Codex, variables starting with CODEX_ are not imported from .env.
pycodex CLI
pycodex now defaults to a minimal interactive entry point. Internally it uses
AgentRuntime to drive the turn submission loop and reuses
~/.codex/config.toml by default:
pycodex
pycodex "Summarize this repo in one sentence."
printf 'Reply with exactly OK.' | pycodex
pycodex --json "Reply with exactly OK."
pycodex --profile model_proxy "Reply with exactly OK."
pycodex --vllm-endpoint http://127.0.0.1:18000 "Reply with exactly OK."
pycodex --put @127.0.0.1:5577
pycodex --put /data/.codex/@127.0.0.1:5577
pycodex --call SECRET-CALLID@127.0.0.1:5577 "Reply with exactly OK."
pycodex doctor
Current behavior:
- with no argv prompt and a TTY stdin, enter interactive mode
- with an argv prompt or piped stdin, run a single turn
- interactive mode supports
/exitand/quit - interactive mode shows a compact event stream for user-visible phases such as tool execution and model follow-up after tool results
- assistant text is printed from streaming deltas directly
- interactive mode supports
/history,/title,/model,/resume, and/compact /model <name>switches the model used by later turns in the current interactive session;/modelshows the current model and available choices/resumewith no argument lists the currently resumable sessions by their first user-message preview;/resume 1resumes the first listed session/resume <number>replaces the in-memory history with the selected recorded Codex rollout fromCODEX_HOME/sessions/compactsynthesizes a local handoff summary, replaces the in-memory conversation history with the compacted view, and appends a compacted-history entry to the rollout so later/resumesees the same state- new sessions are now recorded under
CODEX_HOME/sessions/.../rollout-*.jsonlwith a stable session/thread id and per-item append+flush semantics so/resumereads back the same rollout format - if
TURN_HOOK.mdexists in the workspace root and is non-empty, each completed turn also forks the just-finished history into a temporary, non-persisted follow-up session and submits the file contents as the next user instruction; this is intended for side-effect follow-ups such as Feishu notifications - steer is enabled by default in interactive mode: normal input goes into the
runtime steer path, the current request stops at the next safe boundary, and
later steer text is appended to the next model request's
inputin order; for explicit queueing, use/queue <message>, which prints[steer] queued: ...and later[steer] inserted: ... - the default built-in tool subset currently exposed as local tools is:
shell,shell_command,exec_command,write_stdin,exec,wait,web_search,update_plan,request_user_input,request_permissions,spawn_agent,send_input,resume_agent,wait_agent,close_agent,apply_patch,grep_files,read_file,list_dir,view_image --vllm-endpoint http://host:portautomatically launches a localresponses_servercompatibility layer; when the URL path is empty it is normalized to/v1, and/responsesrequests are still forwarded to the downstream/v1/chat/completionsendpoint. Formodel_provider = "vllm", reasoning is now preserved across this path: chat chunks withreasoningorreasoning_contentare translated back into Responsesreasoningitems, and historicalreasoningitems are replayed into downstream assistant messages via thereasoningfield. Streaming token usage is also requested from vLLM and forwarded to the finalresponse.completed.response.usagepycodex doctorchecks config,.env, API keys, DNS, TCP/TLS, and an optional live Responses API request
Current primary uses:
- verify provider / model / auth configuration
- debug
ResponsesModelClient - run minimal single-turn and multi-turn smoke tests
doctor examples:
pycodex doctor
pycodex doctor --skip-live
pycodex doctor --json
Portable Mode
Portable Mode is the quickest way to bring your usual pycodex setup into a
fresh machine, container, or debug image.
Use it like this:
pycodex --put @127.0.0.1:5577
pycodex --put /data/.codex/@127.0.0.1:5577
--putprints a reusableSECRET-CALLID@host:portplus a final one-linepycodex --call ...command- on the new environment or image, run that printed
--callcommand directly - quickly restoring your usual
config.toml,.env,AGENTS.md, andskills/into a clean debug environment - keeping a new image focused on the bug you are debugging instead of spending time rebuilding local Codex setup by hand
- bootstrapping
pycodexeven when the target environment does not already have a populated~/.codex - bare
--putuses the current user's~/.codex --put /path/.codex/@host:portlets you publish a different Codex home
Example
import asyncio
from pycodex import (
AgentLoop,
BaseTool,
ContextManager,
ResponsesModelClient,
ToolRegistry,
)
class EchoTool(BaseTool):
name = "echo"
description = "Echo the provided text."
input_schema = {
"type": "object",
"properties": {"text": {"type": "string"}},
"required": ["text"],
}
async def run(self, context, args):
del context
return args["text"]
async def main() -> None:
model = ResponsesModelClient.from_codex_config()
context_manager = ContextManager.from_codex_config()
tools = ToolRegistry()
tools.register(EchoTool())
agent = AgentLoop(model, tools, context_manager)
result = await agent.run_turn(
["Call the echo tool with text=hello, then tell me what it returned."]
)
print(result.output_text)
asyncio.run(main())
Alignment Checklist
See docs/ALIGNMENT.md for more detail. This section keeps a high-level
checklist for quick status scanning.
Tool Alignment
Official upstream tools:
-
shell- run shell commands in argv form. -
shell_command- run shell scripts in string form. -
exec_command- start long-running commands with a session. -
write_stdin- write stdin to an existing execution session or poll output. -
web_search- expose provider-native web search capability. -
update_plan- update the task plan and maintain step status. -
request_user_input- ask the user structured questions and wait for an answer. -
request_permissions- request extra permissions before continuing. -
spawn_agent- create and start a sub-agent. -
send_input- continue feeding input to an existing sub-agent. -
resume_agent- reopen a closed sub-agent. -
wait_agent- wait for a sub-agent to reach a terminal state. -
close_agent- close a sub-agent that is no longer needed. -
apply_patch- edit files precisely with a freeform patch. -
grep_files- search file contents by pattern. -
read_file- read file slices while preserving line-number semantics. -
list_dir- list directory tree slices. -
view_image- turn a local image into model-visible input.
Upstream low-frequency / special-mode tools not yet modeled separately:
-
wait_infinite- long blocking wait for external events or later input. -
spawn_agents_on_csv- create sub-agent jobs in bulk from CSV. -
report_agent_job_result- report batch agent job results. -
js_repl- JavaScript REPL / code-mode primary entry point. -
js_repl_reset- resetjs_replstate. -
artifacts- generate or manage structured artifact outputs. -
list_mcp_resources- list MCP resources. -
list_mcp_resource_templates- list MCP resource templates. -
read_mcp_resource- read MCP resource contents. -
multi_tool_use.parallel- parallel wrapper around multiple developer tool calls.
Repository-specific compatibility / transition tools:
-
exec- current local approximation of code mode. -
wait- current local approximation of code-mode waiting behavior.
Behavior Alignment
-
AgentLoop/AgentRuntimemain loop skeleton - turn loop and submission queue are in place. - non-interactive
execinstructionsalignment - base instructions match upstream. - non-interactive
execinputalignment - prompt input matches upstream. - developer/contextual-user message shape alignment - message/content shape matches upstream.
-
AGENTS.md+<environment_context>injection alignment - context assembly order matches upstream. - non-interactive
exectool subset alignment - the model-visible tool set has converged. -
include = ["reasoning.encrypted_content"]- reasoning include field is aligned. -
prompt_cache_key- request-level prompt cache key is implemented. -
x-client-request-id- request id header is implemented. -
x-codex-turn-metadata- turn id / sandbox header is implemented. -
originator- mode-aware originator header is implemented. - exact
user-agentstring alignment - aligned on the non-interactiveexecpath. - field-by-field exec-mode tool schema alignment - currently reuses the upstream snapshot directly through the tool layer.
- full interactive-mode and non-
execbehavior alignment - the non-exec first-turn context is now on thecodex-tuipath, but continuous REPL multi-turn behavior is not fully verified yet. - sandbox / approvals / compact / memory and other outer behavior alignment
- these systems are still in later scope.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file python_codex-0.1.4.tar.gz.
File metadata
- Download URL: python_codex-0.1.4.tar.gz
- Upload date:
- Size: 246.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b1dd2cf544d02cb9cf9c568af0ac8bceab870d6a1864afef25391125271823d8
|
|
| MD5 |
21f4a82f04effddc2b90850bc2633306
|
|
| BLAKE2b-256 |
965708f8ff6e7dff655ef83f71220831567cce12706a76cd58b622ad7e28bb87
|
Provenance
The following attestation bundles were made for python_codex-0.1.4.tar.gz:
Publisher:
publish.yml on Randomizez/pycodex
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
python_codex-0.1.4.tar.gz -
Subject digest:
b1dd2cf544d02cb9cf9c568af0ac8bceab870d6a1864afef25391125271823d8 - Sigstore transparency entry: 1254741538
- Sigstore integration time:
-
Permalink:
Randomizez/pycodex@6bf9e43d124e4528d8a3a6f867747c4794e95e88 -
Branch / Tag:
refs/tags/v0.1.4 - Owner: https://github.com/Randomizez
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@6bf9e43d124e4528d8a3a6f867747c4794e95e88 -
Trigger Event:
push
-
Statement type:
File details
Details for the file python_codex-0.1.4-py3-none-any.whl.
File metadata
- Download URL: python_codex-0.1.4-py3-none-any.whl
- Upload date:
- Size: 196.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ead4ce110f5fca3c7b394d148ae0b0c6e61c9f1d1423c7f2c3ef1930c3de7582
|
|
| MD5 |
830f59b2d9d924d5517060da03d7f727
|
|
| BLAKE2b-256 |
f09de7906feaa6c19f02d9e7adcfe751d990dfe3168a8258711517ea3fde8af2
|
Provenance
The following attestation bundles were made for python_codex-0.1.4-py3-none-any.whl:
Publisher:
publish.yml on Randomizez/pycodex
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
python_codex-0.1.4-py3-none-any.whl -
Subject digest:
ead4ce110f5fca3c7b394d148ae0b0c6e61c9f1d1423c7f2c3ef1930c3de7582 - Sigstore transparency entry: 1254741605
- Sigstore integration time:
-
Permalink:
Randomizez/pycodex@6bf9e43d124e4528d8a3a6f867747c4794e95e88 -
Branch / Tag:
refs/tags/v0.1.4 - Owner: https://github.com/Randomizez
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@6bf9e43d124e4528d8a3a6f867747c4794e95e88 -
Trigger Event:
push
-
Statement type: