python-codex 0.4.8__py3-none-any.whl → 0.5.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,193 @@
1
+ Metadata-Version: 2.1
2
+ Name: python-codex
3
+ Version: 0.5.0
4
+ Summary: A minimal Python extraction of Codex's main agent loop
5
+ License-File: LICENSE
6
+ Requires-Python: >=3.6.2
7
+ Requires-Dist: dataclasses>=0.8; python_version < '3.7'
8
+ Requires-Dist: importlib-metadata>=4.8.3; python_version < '3.8'
9
+ Requires-Dist: loguru>=0.7.3
10
+ Requires-Dist: prompt-toolkit>=3.0.36
11
+ Requires-Dist: pyyaml>=6.0
12
+ Requires-Dist: requests>=2.27.1
13
+ Requires-Dist: starlette<0.20,>=0.19.1; python_version < '3.7'
14
+ Requires-Dist: starlette>=0.19.1; python_version >= '3.7'
15
+ Requires-Dist: tomli<2,>=1.2.3; python_version < '3.11'
16
+ Requires-Dist: typing-extensions>=4.1.1; python_version < '3.8'
17
+ Requires-Dist: uvicorn<0.17,>=0.16.0; python_version < '3.7'
18
+ Requires-Dist: uvicorn>=0.16.0; python_version >= '3.7'
19
+ Description-Content-Type: text/markdown
20
+
21
+ # pycodex
22
+
23
+ [中文](README_ZH.md)
24
+
25
+ pycodex brings the [Codex](https://github.com/openai/codex) agent loop to Python,
26
+ with a CLI, reusable Agent API and browser workspace. It aims to follow upstream
27
+ behavior; supported features and deliberate differences are documented in
28
+ [Alignment](docs/ALIGNMENT.md).
29
+
30
+ | Package | Provides | Python |
31
+ | --- | --- | --- |
32
+ | `python-codex` | Agent, runtime, CLI, tools, Responses compatibility server and portable storage | 3.6.2+ |
33
+ | `pycodex-ws` | Browser workspace, Canvas, file/diff viewers and optional Feishu integration | 3.7+ |
34
+
35
+ The workspace package installs the matching core version. Python imports use
36
+ `pycodex`; the commands are `pycodex` and `pycodex-ws`.
37
+
38
+ ## Install and run
39
+
40
+ Install the core CLI:
41
+
42
+ ```bash
43
+ python3 -m pip install python-codex
44
+ pycodex
45
+ ```
46
+
47
+ Or install the browser workspace, which includes core:
48
+
49
+ ```bash
50
+ python3 -m pip install pycodex-ws
51
+ pycodex-ws --help
52
+ ```
53
+
54
+ Use `python3 -m pip install --upgrade pycodex-ws` to upgrade both packages
55
+ together. If a command is missing from `PATH`, use `python3 -m pycodex` or
56
+ `python3 -m workspace_server` with the interpreter used for installation.
57
+
58
+ ## Model configuration
59
+
60
+ pycodex reads `~/.codex/config.toml` and the adjacent `.env`. Configure `model`,
61
+ `model_provider`, and the provider's `base_url` and `env_key` as needed.
62
+ The built-in `openai` provider uses `OPENAI_API_KEY`. Authentication uses API
63
+ keys; ChatGPT sign-in credentials from `auth.json` or the OS keychain are not
64
+ loaded.
65
+
66
+ ```bash
67
+ pycodex --profile my_profile "Summarize this repository."
68
+ pycodex --config /path/to/config.toml
69
+ pycodex doctor
70
+ ```
71
+
72
+ Models use the streaming Responses API. For a Chat Completions backend, set
73
+ `use_chat_completion = true` in its provider table. Other backend options include:
74
+
75
+ ```bash
76
+ pycodex --vllm-endpoint http://127.0.0.1:18000
77
+ pycodex --profile my_profile --use-messages
78
+ ```
79
+
80
+ See [context and configuration](docs/CONTEXT.md) and the
81
+ [Responses compatibility server](docs/responses_server/README.md).
82
+
83
+ ## CLI
84
+
85
+ Run `pycodex` without a prompt for an interactive session, or supply a prompt
86
+ for a single turn:
87
+
88
+ ```bash
89
+ pycodex "Summarize this repository in one sentence."
90
+ printf 'Reply with exactly OK.' | pycodex
91
+ pycodex --json "Reply with exactly OK."
92
+ ```
93
+
94
+ | Command | Purpose |
95
+ | --- | --- |
96
+ | `/model`, `/effort` | Show or change the model and reasoning effort |
97
+ | `/cd <directory>` | Change the session's working directory while idle |
98
+ | `/title`, `/history` | Manage the title and inspect conversation history |
99
+ | `/resume` | List saved sessions; pass an index or session UUID to resume |
100
+ | `/compact` | Summarize history to free context |
101
+ | `/fork` | Continue the current history under a new session ID |
102
+ | `/queue <message>` | Wait for the active turn to finish before submitting |
103
+
104
+ Ordinary input during a turn steers at the next safe sampling boundary.
105
+ Sessions are saved under `CODEX_HOME/sessions`. Set
106
+ `model_auto_compact_token_limit` in the configuration to compact automatically.
107
+
108
+ Ctrl+D or Ctrl+C closes the CLI after accepted work finishes. A second Ctrl+C
109
+ during closing exits immediately. Use `pycodex --help` for startup options.
110
+
111
+ ## Browser workspace
112
+
113
+ Create `workspaces.json`:
114
+
115
+ ```json
116
+ {
117
+ "workspaces": [
118
+ {"id": "demo", "work_dir": ".", "state_path": "demo", "enable_board": true}
119
+ ]
120
+ }
121
+ ```
122
+
123
+ ```bash
124
+ pycodex-ws --listen 127.0.0.1:6007 --workspace-config workspaces.json
125
+ ```
126
+
127
+ Open `http://127.0.0.1:6007`. The workspace combines conversations with a live
128
+ HTML Canvas and read-only file/diff viewers. Click file links in replies to open
129
+ them, and use **Insert into chat** to add selected code to an editable draft.
130
+ Keep the server's stdin open: EOF initiates shutdown.
131
+ The gear button starts an Agent conversation for customizing the interface
132
+ through files in `~/.pycodex/frontend/`.
133
+
134
+ See the [workspace guide](packages/pycodex-ws/README.md) for session controls,
135
+ storage and Feishu setup. The separately distributed
136
+ [VS Code extension](packages/pycodex-ws/extensions/vscode/README.md) uses the same
137
+ workspace backend, including on Remote SSH hosts.
138
+
139
+ ## Python API
140
+
141
+ Use the standard model and tool setup from Python:
142
+
143
+ ```python
144
+ from pycodex.bootstrap import build_agent, build_model
145
+
146
+ agent = build_agent(build_model())
147
+ try:
148
+ result = agent.ask("Summarize this repository in one sentence.")
149
+ print(result.output_text)
150
+ finally:
151
+ agent.shutdown()
152
+ ```
153
+
154
+ For custom integrations, `Agent` accepts a model client, `ToolRegistry` and
155
+ `ContextConfig`. `await agent.run_turn([...])` runs an asynchronous turn;
156
+ `AgentRuntime` adds input queues, commands and frontend subscriptions.
157
+ See [runtime contracts and examples](docs/RUNTIME.md).
158
+
159
+ ## Portable configuration
160
+
161
+ Portable mode transfers an encrypted bundle of your configuration, instructions
162
+ and skills to another machine. Start a storage service in a separate terminal:
163
+
164
+ ```bash
165
+ python3 -m pycodex.portable.server --root .tmp/pycodex_storage --host 127.0.0.1 --port 5577
166
+ ```
167
+
168
+ Upload the default Codex home, or specify another directory:
169
+
170
+ ```bash
171
+ pycodex --put @127.0.0.1:5577
172
+ pycodex --put /path/to/.codex/@127.0.0.1:5577
173
+ ```
174
+
175
+ After verifying a download round trip, the command prints a reusable
176
+ `pycodex --call ...` command. Run it on the target machine using a storage
177
+ address reachable from that machine.
178
+
179
+ ## Development and reference
180
+
181
+ ```bash
182
+ uv sync --dev
183
+ uv run pycodex
184
+ uv run --dev pytest
185
+ ```
186
+
187
+ - [Test environments and coverage](tests/TESTS.md)
188
+ - [Architecture, package layout and runtime contracts](docs/RUNTIME.md)
189
+ - [Prompts, configuration and context](docs/CONTEXT.md)
190
+ - [Upstream alignment](docs/ALIGNMENT.md)
191
+ - [Responses compatibility server](docs/responses_server/README.md)
192
+
193
+ Release notes and implementation details live in `docs/`.
@@ -83,8 +83,8 @@ responses_server/trajectory_dump.py,sha256=5_Wy6tslszrqx3j1JeP_boyTbzIicKy8No84P
83
83
  responses_server/tools/__init__.py,sha256=ivsBSEy0SBUhY-Uea5v1XMLXShkwHdCVl0id-1FwdZg,150
84
84
  responses_server/tools/custom_adapter.py,sha256=SnIiNUdomff3APtTriURAC9YCeEdhCDjzGMEYusfUdk,8340
85
85
  responses_server/tools/web_search.py,sha256=pnKH9JyTScRehWpZS3HpXRvjWsQvC0QZqxjEOCT4ndg,8936
86
- python_codex-0.4.8.dist-info/METADATA,sha256=OXHvFKPrz7ORC-G8Z-2VkiAzoiSFAyzgvE5ifGVO67E,48951
87
- python_codex-0.4.8.dist-info/WHEEL,sha256=KGYbc1zXlYddvwxnNty23BeaKzh7YuoSIvIMO4jEhvw,87
88
- python_codex-0.4.8.dist-info/entry_points.txt,sha256=sNUVakoVuTrzJH505ZgRTQxmtRRPUHV_EH0i6EbYTyM,45
89
- python_codex-0.4.8.dist-info/licenses/LICENSE,sha256=0X8ifk312hYAORM4hlzg8wVSEXYKNmiPgWlB1YIy2Nw,10926
90
- python_codex-0.4.8.dist-info/RECORD,,
86
+ python_codex-0.5.0.dist-info/METADATA,sha256=Y_z_aUh-nRYLRGhB6_kEkOia9hOenpz87ECOWNAcsVs,6541
87
+ python_codex-0.5.0.dist-info/WHEEL,sha256=KGYbc1zXlYddvwxnNty23BeaKzh7YuoSIvIMO4jEhvw,87
88
+ python_codex-0.5.0.dist-info/entry_points.txt,sha256=sNUVakoVuTrzJH505ZgRTQxmtRRPUHV_EH0i6EbYTyM,45
89
+ python_codex-0.5.0.dist-info/licenses/LICENSE,sha256=0X8ifk312hYAORM4hlzg8wVSEXYKNmiPgWlB1YIy2Nw,10926
90
+ python_codex-0.5.0.dist-info/RECORD,,
@@ -1,916 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: python-codex
3
- Version: 0.4.8
4
- Summary: A minimal Python extraction of Codex's main agent loop
5
- License-File: LICENSE
6
- Requires-Python: >=3.6.2
7
- Requires-Dist: dataclasses>=0.8; python_version < '3.7'
8
- Requires-Dist: importlib-metadata>=4.8.3; python_version < '3.8'
9
- Requires-Dist: loguru>=0.7.3
10
- Requires-Dist: prompt-toolkit>=3.0.36
11
- Requires-Dist: pyyaml>=6.0
12
- Requires-Dist: requests>=2.27.1
13
- Requires-Dist: starlette<0.20,>=0.19.1; python_version < '3.7'
14
- Requires-Dist: starlette>=0.19.1; python_version >= '3.7'
15
- Requires-Dist: tomli<2,>=1.2.3; python_version < '3.11'
16
- Requires-Dist: typing-extensions>=4.1.1; python_version < '3.8'
17
- Requires-Dist: uvicorn<0.17,>=0.16.0; python_version < '3.7'
18
- Requires-Dist: uvicorn>=0.16.0; python_version >= '3.7'
19
- Description-Content-Type: text/markdown
20
-
21
- # pycodex
22
-
23
- English README. Chinese version: `README_ZH.md`
24
-
25
- 0.4.0 core/workspace package split and dependency cleanup: [release notes](docs/RELEASE_0.4.0.md).
26
-
27
- 0.3.3 workspace viewers and reasoning stream fix: [release notes](docs/RELEASE_0.3.3.md).
28
-
29
- 0.3.2 startup fixes: [release notes](docs/RELEASE_0.3.2.md).
30
-
31
- 0.3.0 release preparation and Python API migration: `docs/RELEASE_0.3.0.md`.
32
-
33
- PyPI distributions:
34
-
35
- - Core package: `python-codex`
36
- - Workspace and integrations: `pycodex-ws`
37
-
38
- The import path remains `pycodex`; the CLI commands are `pycodex` and
39
- `pycodex-ws`.
40
-
41
- `python-codex` contains the Agent, runtime, CLI, standard tools, Responses
42
- compatibility server and portable storage support. `pycodex-ws` depends on the
43
- exact matching core version and contains the browser workspace, viewer assets
44
- and optional Feishu integration. It owns the
45
- `workspace_server` and `pycodex_ws` modules and the `pycodex-ws` command.
46
-
47
- The Feishu SDK is not installed automatically. Install it in the workspace's
48
- Python environment with `python3 -m pip install lark-oapi` to enable `/link`.
49
- If `lark_oapi` cannot be imported, `/link` reports that it is unavailable.
50
-
51
- Core does not require Pillow. Without it, `view_image` sends the original image
52
- bytes with `detail="original"`, including when `high` is requested. With Pillow
53
- installed, the default path retains resizing to a maximum dimension of 2048.
54
- `pycodex-ws` installs Pillow for image preview validation.
55
-
56
- Portable encryption uses a bundled pure Python AES-GCM subset of pyaes and
57
- tlslite-ng, under MIT and BSD licenses. Core does not require a crypto package
58
- or compiler and ships a `py3-none-any` wheel. Existing `PCX1` bundles and call
59
- tokens remain compatible; large bundles take longer to encrypt and decrypt.
60
- See the [source notes](pycodex/utils/_aes_gcm/README.md).
61
-
62
- The Responses compatibility server and workspace HTTP server use Starlette
63
- and Uvicorn directly. Neither package requires FastAPI, Pydantic or
64
- `pydantic-core`. Python 3.6 uses Starlette 0.19.x; newer interpreters resolve
65
- a compatible Starlette release through its Python version metadata.
66
-
67
- This repository extracts the core Codex agent loop from upstream Codex
68
- (`https://github.com/openai/codex`) into a deliberately small Python version,
69
- while preserving the two most important layers:
70
-
71
- - `submission_loop`: sequentially consumes submitted operations.
72
- - `run_turn`: keeps executing `model sample -> tool call -> feed tool result
73
- back into the model` inside a single turn until a final answer is reached.
74
-
75
- Relevant Rust reference points:
76
-
77
- - `codex-rs/core/src/codex.rs` -> `submission_loop`
78
- - `codex-rs/core/src/codex.rs` -> `run_turn`
79
- - `codex-rs/core/src/codex.rs` -> `run_sampling_request`
80
- - `codex-rs/core/src/tools/router.rs` -> `ToolRouter`
81
- - `codex-rs/core/src/stream_events_utils.rs` -> `handle_output_item_done`
82
-
83
- ## Quick Start
84
-
85
- Install the core CLI, or install the workspace package for the browser frontend:
86
-
87
- ```bash
88
- python3 -m pip install python-codex
89
- python3 -m pip install pycodex-ws
90
- ```
91
-
92
- The core supports Python 3.6.2 or newer. The workspace package requires Python
93
- 3.7 or newer. Commands are installed into
94
- that interpreter's scripts directory. On a machine with multiple Python
95
- installations, select the same interpreter explicitly for startup:
96
-
97
- ```bash
98
- python3 -m pycodex
99
- python3 -m workspace_server --help
100
- ```
101
-
102
- If `pycodex` is missing from `PATH` or resolves to an older installation, use these
103
- module entry points. `python3 -m pip --version` shows which Python environment
104
- received the installation.
105
-
106
- For an existing Web installation, use `python3 -m pip install --upgrade pycodex-ws`
107
- to upgrade the workspace and its matching core together. Installing only
108
- `python-codex` provides the CLI and core API.
109
-
110
- For development from this repository, install dependencies first:
111
-
112
- ```bash
113
- uv sync --dev
114
- ```
115
-
116
- Try the real entry points:
117
-
118
- ```bash
119
- uv run pycodex "Reply with exactly OK."
120
- uv run pycodex
121
- ```
122
-
123
- Tests are split by package. `uv run --dev pytest` runs both suites;
124
- `uv run --dev pytest packages/pycodex-ws/tests` runs workspace and integration
125
- tests. To check core without installing workspace, use a separate
126
- environment:
127
-
128
- ```bash
129
- UV_PROJECT_ENVIRONMENT=.tmp/core-tests uv sync --group test
130
- UV_PROJECT_ENVIRONMENT=.tmp/core-tests uv run --group test pytest tests
131
- ```
132
-
133
- See [test environments and coverage](tests/TESTS.md). Core tests support Python
134
- 3.6.2+; workspace tests require Python 3.7+.
135
-
136
- A VS Code frontend is available in [`packages/pycodex-ws/extensions/vscode`](packages/pycodex-ws/extensions/vscode/README.md).
137
- It belongs to the workspace frontend and is packaged separately as a VSIX.
138
- It reuses an existing `pycodex-ws>=0.4.0` installation. On first use, missing
139
- or older backends are installed/upgraded with pip in the selected Python
140
- environment on the workspace host; compatible versions need no network check.
141
- It starts a dedicated `pycodex-ws` workspace on the project's host and opens a
142
- chat in the left sidebar using VS Code's stable WebviewView API. Each chat
143
- creates a workspace session with no code context attached. You can insert code
144
- from editors, diffs or file-tree selections as editable tokens among your words.
145
- Each token expands at its position when you send the message.
146
- Previous chats fold into live status entries; each can be
147
- reopened or closed, and models can be changed with `/model`.
148
- Remote workspaces run Python on the remote host. A stdin pipe ties each backend
149
- to its extension host; normal closure or host failure triggers draining and exit.
150
- `pycodex-ws` always shuts down on stdin EOF, after accepted work finishes.
151
- Keep stdin open while running the server; redirecting it from `/dev/null`
152
- causes the server to shut down.
153
- Workspace configuration uses a temporary JSON file passed to `--workspace-config`;
154
- normal backend exit removes the temporary directory.
155
- The extension uses that host's `~/.codex/config.toml` by default.
156
- The chat settings menu selects a project venv or another Python interpreter
157
- and edits the Codex profile. Changes offer a window reload when a backend is
158
- already running.
159
- Its context ring shows the same remaining budget and usage details as the
160
- workspace web frontend.
161
- See the extension README for building a VSIX, server setup and Remote SSH usage.
162
-
163
- ## Design Tradeoffs
164
-
165
- This is not a 1:1 port of the Rust implementation. The current goal is a
166
- minimal reusable kernel that converges on the upstream behavior over time:
167
-
168
- 1. Use a thin `ModelClient` protocol to abstract the model side.
169
- 2. Use `ToolRegistry` to manage tool specs and executors.
170
- 3. Use `AgentLoop` to implement the core closed loop.
171
- 4. Use `AgentRuntime` to preserve the outer submission queue so it can keep
172
- converging toward Rust's `submission_loop` later.
173
-
174
- Intentionally not included yet:
175
-
176
- - TUI / streaming incremental rendering
177
- - MCP / connectors / sandbox / approvals
178
- - memory / compact / review mode
179
- - a full production OpenAI adapter surface
180
-
181
- All of those can be layered on later. For now, the project is focused on
182
- nailing the core tool-augmented reasoning loop first.
183
-
184
- ## Layout
185
-
186
- The top level of `pycodex/` contains the core components and package entry points.
187
- Supporting services live in `tools/`, `portable/` and `utils/`.
188
-
189
- - `pycodex/agent.py`: inner turn loop
190
- - `pycodex/runtime.py`: session commands, submission queues and frontend event subscriptions
191
- - `pycodex/model.py`: model client protocol and Responses API adapter
192
- - `pycodex/context.py`: configuration, prompt assembly and instruction loading
193
- - `pycodex/protocol.py`: minimal conversation item / prompt protocol
194
- - `pycodex/events.py`: typed events, plain-text views and stateful presentation
195
- - `pycodex/bootstrap.py`: frontend-independent model, tools, Agent and session assembly
196
- - `pycodex/cli.py`: single-turn/interactive entry points, terminal I/O executor and input loop
197
- - `pycodex/tools/base_tool.py`: `BaseTool`, `ToolRegistry`, `ToolContext`
198
- - `pycodex/tools/runtime_services.py`: plan, sub-agent, user-input and permissions services
199
- - `pycodex/tools/`: concrete tools and their execution managers
200
- - `pycodex/portable/`: Codex home storage client and server, both included in core
201
- - `pycodex/utils/compat.py`: supported-Python compatibility helpers
202
- - `pycodex/utils/doctor.py`: configuration and connectivity diagnostics
203
- - `pycodex/utils/model_metadata.py`: vendored model metadata lookup
204
- - `pycodex/utils/debug.py`: debug paths and the `pycodex.debug()` implementation
205
- - `pycodex/utils/event_helpers.py`: stateless text, color, summary and result-formatting helpers
206
- - `pycodex/utils/image_utils.py`: image loading, resizing and data-URL preparation
207
- - `pycodex/prompts/`: vendored prompts and model metadata
208
- - `packages/pycodex-ws/workspace_server/`: Web board, API and file/diff viewers
209
- - `packages/pycodex-ws/pycodex_ws/`: Feishu and workspace assembly
210
- - `packages/pycodex-ws/extensions/vscode/`: workspace's VS Code frontend, built separately as a VSIX
211
- - `tests/`: core tests
212
- - `packages/pycodex-ws/tests/`: workspace and cross-frontend integration tests
213
-
214
- See [package layout and import paths](docs/RUNTIME.md#package-layout).
215
-
216
- ## Current Alignment Status
217
-
218
- Current progress is easiest to read in layers:
219
-
220
- - prompt/context alignment:
221
- - the 2026-09-23 audit uses Codex CLI 0.153.4: shared context matches for
222
- `gpt-5.4` first/resume/tool-follow-up requests and suffixed Astra first/resume
223
- requests, with explicit exclusions recorded in `docs/CONTEXT.md`;
224
- - this layer is now mainly handled by `pycodex/context.py` plus vendored
225
- prompt data; model lookup uses the longest matching slug prefix without
226
- rewriting the requested model name.
227
- - turn-loop semantic alignment:
228
- - `AgentLoop` no longer uses a fixed 12-iteration cap by default;
229
- - like upstream, it now converges naturally based on whether there is still
230
- follow-up work or tool handoff to do;
231
- - the local iteration-limit parameter is gone.
232
- - request-level alignment:
233
- - full raw request parity is **not** claimed: client-generated message/result
234
- IDs, the tool catalog, new upstream telemetry, and some permission profiles
235
- still differ;
236
- - the default CLI keeps the `codex-tui` client identity, but intentionally
237
- omits collaboration-mode developer instructions;
238
- - `User-Agent` and rollout `cli_version` use the fixed upstream alignment
239
- version `0.153.4`; session creation does not run `codex --version`;
240
- - `tests/compare_context_requests.py` keeps raw differences separate from
241
- shared-context exclusions; older interactive captures are not a fresh
242
- certification of 0.153.4 interactive behavior.
243
- - structured user input:
244
- - `request_user_input` remains declared, but calls return
245
- `request_user_input is unavailable in Default mode` without opening a question;
246
- - attaching a frontend or input handler does not enable it. The generic runtime
247
- input and permission services remain available;
248
- - local tool-result `success` metadata is omitted from API requests, including
249
- when replaying answers from older rollouts.
250
-
251
- See `docs/ALIGNMENT.md` for more detailed notes.
252
-
253
- `PYCODEX_DUMP` records downstream `stream_completed` and `stream_error_type`
254
- alongside usage, finish reason, and exact token IDs. Failed attempts remain
255
- in the dump, including failures before the first chunk. Chat SSE `error`
256
- payloads propagate as `response.failed`, never as an empty completed response.
257
- Chained failures also retain `stream_error_cause_type` and, for HTTP errors,
258
- `stream_error_http_status`; exception messages, headers and URLs are not
259
- copied into these diagnostic fields.
260
-
261
- Downstream errors include elapsed time, timeout, failure stage, response-header
262
- and first-chunk timing, and received chunk count directly in the error message.
263
-
264
- ## Live Model Integration
265
-
266
- If this machine already has a Codex CLI configuration, `pycodex` can reuse the
267
- `model`, `model_provider`, `base_url`, and `env_key` from
268
- `~/.codex/config.toml` directly:
269
-
270
- ```python
271
- from pycodex import ResponsesModelClient
272
-
273
- client = ResponsesModelClient.from_codex_config()
274
- ```
275
-
276
- `model_provider` is optional and defaults to `openai`. The built-in provider
277
- does not require a `[model_providers.openai]` table: it uses
278
- `https://api.openai.com/v1` and reads `OPENAI_API_KEY`. Set `openai_base_url` to
279
- override this address. Explicit provider tables retain their configured
280
- `base_url` and optional `env_key`; unknown provider names raise a configuration
281
- error. The model still comes from `model` in the configuration or selected
282
- profile.
283
-
284
- Authentication currently uses API keys from environment variables or `.env`;
285
- Codex desktop/CLI ChatGPT sign-in credentials in `auth.json` or the OS keychain
286
- are not loaded.
287
-
288
- The current implementation uses the streaming OpenAI-compatible `/responses`
289
- endpoint. This path has already been validated against the local
290
- `~/.codex/config.toml` setup.
291
-
292
- When launched through the CLI, `pycodex` also loads `.env` from the same
293
- configuration directory before reading config (typically `~/.codex/.env`), so
294
- provider keys and similar environment variables can live there. To match
295
- upstream Codex, variables starting with `CODEX_` are not imported from `.env`.
296
-
297
- ## pycodex CLI
298
-
299
- `pycodex` now defaults to a minimal interactive entry point. Internally it uses
300
- `AgentRuntime` to drive the turn submission loop and reuses
301
- `~/.codex/config.toml` by default:
302
-
303
- ```bash
304
- pycodex
305
- pycodex "Summarize this repo in one sentence."
306
- printf 'Reply with exactly OK.' | pycodex
307
- pycodex --json "Reply with exactly OK."
308
- pycodex --profile model_proxy "Reply with exactly OK."
309
- pycodex --profile opus --use-messages "Reply with exactly OK."
310
- pycodex --vllm-endpoint http://127.0.0.1:18000 "Reply with exactly OK."
311
- pycodex --put @127.0.0.1:5577
312
- pycodex --put /data/.codex/@127.0.0.1:5577
313
- pycodex --call SECRET-CALLID@127.0.0.1:5577 "Reply with exactly OK."
314
- pycodex doctor
315
- pycodex-ws --listen 0.0.0.0:6007 --workspace-config ./workspaces.json
316
- pycodex-ws --listen 0.0.0.0:6007 --workspace-config ./workspaces.json --password 12345
317
- ```
318
-
319
- Current behavior:
320
-
321
- - with no argv prompt and a TTY stdin, enter interactive mode
322
- - with an argv prompt or piped stdin, run a single turn
323
- - `pycodex-ws` starts the standalone browser workspace manager and serves each
324
- workspace with a board pane and a pycodex session pane
325
- - the left pane is the board, containing an optional live HTML Canvas tab and closable read-only file/diff tabs that
326
- scroll together in one toolbar; a mouse wheel scrolls overflowing tab strips
327
- horizontally.
328
- Click Markdown file links such as `[source](src/app.py:42)` or
329
- `[source](/data/project/app.py#L42)` to open a view. Git-style diff links use
330
- `[changes](diff:HEAD:src/app.py)` for working changes,
331
- `[versions](diff:HEAD~1..HEAD:src/app.py)` for two revisions, and
332
- `[files](diff:old.py..new.py)` for two local files. Append `#L42` or `#R42`
333
- to a diff link to select a 1-based line on the left or right.
334
- Views open when the user clicks; model replies alone do not create tabs.
335
- Reopening the same file or comparison selects its existing tab in that
336
- browser, including when the line changes. Other pages keep their selection.
337
- Tabs show filenames first, adding a short directory suffix
338
- for duplicate names. Git versions use short commit IDs in a secondary label.
339
- Long labels use the same width limit as session tabs, shortening the secondary
340
- information before the filename;
341
- hover titles retain the complete paths and revisions. Text,
342
- Markdown, HTML, common raster images and text diffs are supported. Monaco
343
- provides syntax highlighting, find, folding and character-level diffs.
344
- Right-click code and choose **Insert into chat**, or select text and use the
345
- toolbar button, to insert context at the current chat draft's cursor.
346
- Context appears as a separate block among your words. Click it to edit or
347
- remove it; Backspace and Delete also remove a block as one element.
348
- Context identifies the full view label, including both revisions of a diff.
349
- Selections include only the selected text and range; otherwise it records the
350
- cursor location. Diff context also identifies the selected side.
351
- The draft stays editable until you send it.
352
- Its code and styles load only when a code or diff view opens, so Canvas,
353
- Markdown and HTML previews do not load the editor. Viewer assets use gzip
354
- when supported and revalidate cached content with ETags.
355
- Narrow content panes automatically use a unified diff; targeting the left
356
- side keeps both sides visible. Markdown and HTML links default to previews;
357
- line targets open their source. Clicking a link without a line returns that
358
- same tab to preview. Manual tab switches preserve the chosen mode and reading state.
359
- All views remain
360
- read-only, with text selection and copying available. Local files update
361
- automatically, including both sides of file diffs and the working side of
362
- Git diffs; Git revisions stay fixed. Updates keep the active editor and
363
- reading state without replaying an earlier line request.
364
- Canvas is for explanations and custom visuals; source and diffs use links.
365
- `[Canvas](workspace://canvas)` returns to the live page; an HTML file link opens
366
- a preview tab, and `page.html:1` opens its source. File links work even with an empty `--toolset`.
367
- Canvas and HTML previews share the same origin and script permissions;
368
- the browser loads their resources with the workspace login cookie.
369
- Open views survive reloads and server restarts. See
370
- [workspace content views](docs/RUNTIME.md#workspace-content-views) for the
371
- complete syntax and limits.
372
- - interactive mode exits through Ctrl+D on an empty prompt or a single Ctrl+C;
373
- accepted work finishes before cleanup and normal exit.
374
- A `[closing]` message explains the wait. Press Ctrl+C again while closing
375
- to immediately exit the CLI with status 130. This skips remaining cleanup;
376
- external tool processes may keep running.
377
- - interactive mode shows a compact event stream for user-visible phases such as
378
- tool execution and model follow-up after tool results
379
- - startup shows a welcome, model, effort, endpoint and available commands.
380
- - assistant text is printed from streaming deltas directly
381
- - interactive mode supports `/history`, `/title`, `/cd`, `/model`, `/effort`, `/resume`,
382
- `/compact`, and `/fork`
383
- - `/model <name>` switches the model used by later turns in the current
384
- interactive session; `/model` shows the current model and available choices
385
- - `/cd <directory>` changes the current session's working directory while idle;
386
- `/cd` shows it. Relative paths and `~` are supported; paths containing spaces
387
- can be entered directly without quotes. Later tools and context use the new directory.
388
- - `/effort low` switches effort while idle; `/effort` shows the current value,
389
- and `/effort default` restores the model default. Frontend labels show
390
- `model:effort`.
391
- - `/resume` with no argument lists the currently resumable sessions by their
392
- first user-message preview; `/resume 1` resumes the first listed session
393
- - `/resume <session_id>` accepts a full session UUID, including sessions older
394
- than the displayed list. Both forms search `CODEX_HOME/sessions` and
395
- `CODEX_HOME/archived_sessions` and replace the in-memory history with the
396
- selected recorded Codex rollout. Tool calls without recorded results
397
- are omitted during restore; later saved messages and completed tools remain
398
- available, and the source file is left intact.
399
- - `/compact` synthesizes a local handoff summary, replaces the in-memory
400
- conversation history with the compacted view, and appends a compacted-history
401
- entry to the rollout so later `/resume` sees the same state. The handoff remains
402
- model context, but is omitted from frontend conversation blocks and `/history`;
403
- real replies after compaction remain visible, including after resume or fork.
404
- - `/fork` allocates a new Agent/provider session id and lazy rollout while
405
- preserving current history and the workspace tab; the original rollout stays intact
406
- - workspace tabs follow the fork's rollout once it is written. Before that,
407
- restart restores the source recording and creates a fresh fork, preserving
408
- history and title without appending to the source file.
409
- - `model_auto_compact_token_limit = <tokens>` in `config.toml` enables the same
410
- compaction path automatically when the latest reported usage reaches that
411
- threshold before a follow-up sampling request or the next user turn
412
- - `service_tier = "fast"` enables Fast mode for models whose vendored metadata
413
- advertises the `priority` service tier; pycodex follows upstream Codex by
414
- sending `service_tier = "priority"` on the Responses request, while
415
- `service_tier = "default"` or unsupported tiers are omitted
416
- - if a model request fails with `context_length_exceeded`, pycodex now treats
417
- the provider-reported requested token count as a failed-request usage sample,
418
- triggers the same compact path immediately, and retries the request once; if
419
- the compact request is also over the limit, it repeatedly drops the oldest
420
- tool response plus its matching tool call before retrying compact
421
- - new sessions are now recorded under `CODEX_HOME/sessions/.../rollout-*.jsonl`
422
- with a stable session/thread id and per-item append+flush semantics so
423
- `/resume` reads back the same rollout format
424
- - if `TURN_HOOK.md` exists in the workspace root and is non-empty, each
425
- completed turn also forks the just-finished history into a temporary,
426
- non-persisted follow-up session and submits the file contents as the next
427
- user instruction; this is intended for side-effect follow-ups such as
428
- Feishu notifications
429
- - `/link <feishu-email|open_id|chat_id>` in a workspace session attaches it
430
- to a Feishu card when the optional `lark-oapi` SDK can be imported;
431
- multiple sessions in the same workspace process share
432
- one Feishu long-connection listener and route card actions by message id
433
- - Feishu user OAuth stores its refresh token only in
434
- `~/.codex/.feishu_refresh_token` (mode `0600`). Run
435
- `python3 -m pycodex_ws.feishu_oauth` after installing `pycodex-ws` to authorize.
436
- From this repository, use `env -u VIRTUAL_ENV uv run --dev python -m pycodex_ws.feishu_oauth`.
437
- App credentials can stay in `~/.codex/.env`. Each refresh reads
438
- the latest token from the dedicated file and saves its replacement there.
439
- `FEISHU_REFRESH_TOKEN` in `.env` or the environment is no longer used and
440
- can be removed. Refresh is triggered by API calls, with no idle keepalive.
441
- - `pycodex-ws --workspace-config workspaces.json` serves workspace boards from
442
- one process. The JSON file can be either a list or
443
- `{"workspaces": [...]}`; each entry uses `state_path` and `work_dir`, with optional
444
- display/URL `id` and `enable_board` (default `true`), and is exposed at `/w/<id>/`.
445
- `state_path` is a filename prefix: `/data/demo` uses
446
- `/data/demo.pycodex-ws.json` for the session index,
447
- `/data/demo.pycodex-views.json` for file views and `/data/demo.html` for Canvas.
448
- Suffixes are appended, so prefixes containing dots remain distinct.
449
- The resolved `state_path` is the workspace identity; duplicates are rejected
450
- regardless of name or Canvas setting. Relative `state_path` and `work_dir` paths
451
- resolve from the config file directory. The root path `/` shows a workspaces
452
- management page with create/delete controls; omitted names become `workspace-1`,
453
- `workspace-2`, etc. Omitted `state_path` when creating a workspace generates a
454
- writable `/tmp/pcws-*` prefix. `enable_board: false` hides Canvas and disables its
455
- routes, polling and context injection; sessions and file views still persist.
456
- Existing `board: "demo.html"` and `board: false` configurations load with their
457
- original state paths and are saved in the new format. See the
458
- [workspace configuration example](packages/pycodex-ws/README.md).
459
- Add/delete actions and later session-state saves refresh the JSON file.
460
- Each session starts in `work_dir` and can switch independently with `/cd`.
461
- A small folder indicator appears inside the composer only when the session
462
- directory differs from `work_dir`; hover for the full path. Directories persist
463
- across workspace restarts, including for empty sessions. Relative chat/Canvas file links use the selected
464
- session's directory; Canvas and workspace state files stay at their configured paths.
465
- Board HTML can reference local
466
- images beside the board (including nested paths) with relative URLs; only
467
- `image/*` files contained by the board directory are served.
468
- Assistant Markdown supports KaTeX formulas with `$...$`, `$$...$$`,
469
- `\(...\)`, and `\[...\]` delimiters.
470
- `--password <value>` enables a password-only login page for workspace pages,
471
- APIs, and websocket connections. After login, the browser returns to the
472
- requested workspace path with its query parameters.
473
- Questions and permission requests appear below the conversation only while
474
- waiting for input; they disappear when answered, cancelled, or timed out,
475
- leaving the continuing assistant output at the bottom.
476
- Queued enqueue/steer messages become user conversation blocks when their
477
- turns start, in execution order. Small capsules to the left of the status
478
- pill preview pending Steer (`↑`) and Queue (`◷`) text. Long text is truncated;
479
- hover shows the full pending content. Each disappears when that type has
480
- no waiting input.
481
- - steer is enabled by default in interactive mode: normal input goes into the
482
- runtime steer path, the current request stops at the next safe boundary, and
483
- later steer text is appended to the next model request's `input` in order;
484
- for explicit queueing, use `/queue <message>`, which prints
485
- `[steer] queued: ...` and later `[steer] inserted: ...`
486
- - the default local tool set includes the upstream-aligned subset plus the
487
- pycodex `clock` extension: `shell`, `shell_command`, `exec_command`,
488
- `write_stdin`, `clock`, `exec`, `wait`, `web_search`, `update_plan`,
489
- `request_user_input`, `request_permissions`, `spawn_agent`, `send_input`,
490
- `resume_agent`, `wait_agent`, `close_agent`, `apply_patch`, `grep_files`,
491
- `read_file`, `list_dir`, `view_image`
492
- - `clock(period_m)` sets one periodic clock for the current Agent session;
493
- `null` cancels it. The countdown restarts after each reply and wakes the
494
- Agent with a `<clock_tick>` message containing the current timezone-aware
495
- time when it expires.
496
- - while a background command or clock is pending, the idle status is
497
- `idle: sleeping`
498
- - only the active workspace session tab shows its close button; closing it restores
499
- the selected tab's conversation, draft, and scroll position immediately
500
- after its snapshot loads
501
- - `--vllm-endpoint http://host:port` automatically launches a local
502
- `responses_server` compatibility layer; when the URL path is empty it is
503
- normalized to `/v1`, and `/responses` requests are still forwarded to the
504
- downstream `/v1/chat/completions` endpoint. This local compat path always
505
- uses the canonical Responses request shape, even when the selected model's
506
- metadata enables `responses_lite`. With `--vllm-endpoint`, startup also reads
507
- `/v1/models` and uses the last returned model id for the downstream request.
508
- For `model_provider = "vllm"`, reasoning is preserved across this path:
509
- chat chunks with `reasoning` or `reasoning_content` are translated back into
510
- Responses `reasoning` items, and
511
- historical `reasoning` items are replayed into downstream assistant messages
512
- via the `reasoning` field. Streaming token usage is also requested from vLLM
513
- and forwarded to the final `response.completed.response.usage`. If a
514
- downstream chat stream terminates after emitting only reasoning, with no
515
- assistant content and no tool call, the compat layer discards that partial
516
- reasoning, retries the same downstream request once, and only then emits
517
- `response.failed` with `type = "model_output_invalid"` if the retry is still
518
- reasoning-only
519
- - compat reasoning fragments are forwarded immediately as
520
- `response.reasoning_text.delta`, including thinking from `/v1/messages`.
521
- Continuous reasoning keeps the client's read idle timeout from expiring
522
- while it waits for assistant text; only finalized reasoning items enter history
523
- - malformed JSON in ordinary downstream function-call arguments is detected by
524
- `responses_server` and returned as `response.failed` with `type = "server_error"`;
525
- the client's existing `stream_max_retries` policy retries the current request
526
- before its local tools execute
527
- - downstream connection failures before HTTP response headers or during SSE
528
- reading are converted to `response.failed` for provider retries. Downstream
529
- requests honor environment proxies; see the
530
- [connection troubleshooting notes](docs/responses_server/README.md#连接排查)
531
- for `RemoteDisconnected` and proxy bypass configuration
532
- - standalone `responses_server` now also supports downstream `/v1/messages`
533
- backends via `--outcomming-api messages`, while keeping the internal
534
- canonical request/route logic in chat-completions shape
535
- - `pycodex doctor` checks config, `.env`, API keys, DNS, TCP/TLS, and an
536
- optional live Responses API request
537
-
538
- Current primary uses:
539
-
540
- - verify provider / model / auth configuration
541
- - debug `ResponsesModelClient`
542
- - run minimal single-turn and multi-turn smoke tests
543
-
544
- `doctor` examples:
545
-
546
- ```bash
547
- pycodex doctor
548
- pycodex doctor --skip-live
549
- pycodex doctor --json
550
- ```
551
-
552
- ## Portable Mode
553
-
554
- `Portable Mode` is the quickest way to bring your usual `pycodex` setup into a
555
- fresh machine, container, or debug image.
556
-
557
- The storage client and server are both included in `python-codex`. To start a
558
- local storage service, run this in a separate terminal:
559
-
560
- ```bash
561
- python -m pycodex.portable.server --root .tmp/pycodex_storage --host 127.0.0.1 --port 5577
562
- ```
563
-
564
- Use it like this:
565
-
566
- ```bash
567
- pycodex --put @127.0.0.1:5577
568
- pycodex --put /data/.codex/@127.0.0.1:5577
569
- ```
570
-
571
- - `--put` prints a reusable `SECRET-CALLID@host:port` plus a final one-line
572
- `pycodex --call ...` command
573
- - on the new environment or image, run that printed `--call` command directly
574
- - quickly restoring your usual `config.toml`, `.env`, `AGENTS.md`, and
575
- `skills/` into a clean debug environment
576
- - keeping a new image focused on the bug you are debugging instead of spending
577
- time rebuilding local Codex setup by hand
578
- - bootstrapping `pycodex` even when the target environment does not already
579
- have a populated `~/.codex`
580
- - bare `--put` uses the current user's `~/.codex`
581
- - `--put /path/.codex/@host:port` lets you publish a different Codex home
582
-
583
- ## Example
584
-
585
- ```python
586
- import asyncio
587
- from pathlib import Path
588
-
589
- from pycodex import (
590
- Agent,
591
- BaseTool,
592
- ContextConfig,
593
- ResponsesModelClient,
594
- ToolRegistry,
595
- )
596
-
597
-
598
- class EchoTool(BaseTool):
599
- name = "echo"
600
- description = "Echo the provided text."
601
- input_schema = {
602
- "type": "object",
603
- "properties": {"text": {"type": "string"}},
604
- "required": ["text"],
605
- }
606
-
607
- async def run(self, context, args):
608
- del context
609
- return args["text"]
610
-
611
-
612
- async def main() -> None:
613
- config_path = Path.home() / ".codex" / "config.toml"
614
- model = ResponsesModelClient.from_codex_config(config_path)
615
- context_config = ContextConfig.from_codex_config(config_path)
616
-
617
- tools = ToolRegistry()
618
- tools.register(EchoTool())
619
-
620
- agent = Agent(model, tools, context_config)
621
- result = await agent.run_turn(
622
- ["Call the echo tool with text=hello, then tell me what it returned."]
623
- )
624
- print(result.output_text)
625
-
626
-
627
- asyncio.run(main())
628
- ```
629
-
630
- ### Turn lifecycle
631
-
632
- `Agent` requires a model client, tool registry, and `ContextConfig`. It creates
633
- its own `ContextManager`; cwd, instruction overrides and
634
- extra contextual messages belong in that config. The client's model identifier
635
- is authoritative for the Agent's context. Standalone context construction uses
636
- `ContextManager(config)`; load files through `ContextConfig.from_codex_config`.
637
-
638
- Collaboration modes and their prompt templates are removed. There is no mode
639
- switch in `ContextConfig`, `ToolContext` or `build_agent`; normal interaction,
640
- `update_plan`, structured user input and sub-agents remain independent features.
641
-
642
- Dependencies are ordinary attributes: `model_client`, `tool_registry`,
643
- `context_manager` and `event_handler`. The Agent has no Runtime/Queue reference;
644
- `AgentRuntime` calls its public methods and subscribes to its events.
645
- Pure getter/setter wrappers are removed. `history` remains a tuple snapshot,
646
- and `model_name` and lifecycle properties remain derived values.
647
-
648
- `session_file_path=None`, the default, means **keep history in memory without
649
- recording**. Passing a file path creates an internal rollout recorder. Every
650
- Agent still receives a stable UUIDv7 session id unless an explicit id is supplied.
651
- CLI, Web and Feishu use `build_agent`, which assigns an id and a matching path
652
- under the configured Codex home's `sessions/` directory. Sub-agents leave the path
653
- as `None`, so their turns, compaction, forks and reopening stay in memory.
654
-
655
- When a path is supplied, construction does not create the file or its parent
656
- directories. The first history append or successful compaction creates it,
657
- writing metadata and the current initial history before the new records.
658
- An existing destination raises `FileExistsError`; exclusive creation also
659
- prevents overwriting a file that appears before the first write.
660
- `agent.session_id` and the read-only `agent.session_file_path` expose the identity
661
- and optional recording path without exposing the recorder.
662
-
663
- ```python
664
- agent = Agent(model, tools, context_config) # In-memory session.
665
- agent.resume("~/.codex/sessions/2026/09/22/rollout-example.jsonl")
666
- ```
667
-
668
- `resume(path)` reads an existing file, restores history and session identity, and
669
- continues appending to that same file. Paths accept `~`; the existing
670
- concatenated-JSON and compact-checkpoint loader is reused. Constructing an Agent
671
- and immediately resuming another file leaves no unused rollout behind. Initial
672
- history is recorded only when the new recorded session first writes. `history`
673
- is read-only; there is no public `replace_history()` interface.
674
-
675
- An `Agent` executes one turn or manual compaction at a time.
676
- There are two public turn interfaces:
677
-
678
- - `agent.ask(text)` blocks and returns the turn result.
679
- - `await agent.run_turn(texts, turn_id=None)` executes the ordinary coroutine
680
- and returns the turn result.
681
-
682
- The Agent does not create, hold or return a per-turn Task. Creating a coroutine
683
- does not start it or mark the Agent busy. A host that needs concurrent UI/input
684
- handling can schedule the coroutine with `asyncio.create_task`; synchronous code
685
- can use `ask` or `asyncio.run(agent.run_turn([...]))`. `start_turn` is removed.
686
-
687
- `agent.is_running` reflects actual execution. One idle event backs both that
688
- state and `await agent.wait_until_idle()`; `finally` releases it on exit.
689
- Starting an overlapping operation raises `RuntimeError`; use `AgentRuntime`
690
- for queued or steer submissions. `maybe_invoke` skips a busy or closed Agent;
691
- when idle, it directly awaits the same `run_turn` path and propagates errors.
692
- It returns `True` after that turn finishes, not immediately after scheduling.
693
-
694
- `runtime.is_busy` includes active Agent work and pending submissions, including
695
- direct/background turns that bypass the queue. CLI, web, and Feishu use this
696
- state instead of tracking another busy flag. There is no Agent cancellation API:
697
- `agent.cancel()`, `runtime.cancel_current()` and Task listeners are removed.
698
-
699
- Steer asks `agent.stop_asap()` to end the current execution at a safe boundary.
700
- The in-flight request and all issued tools finish first; the Agent records their
701
- results, emits `turn_interrupted` and raises `TurnInterrupted`, without knowing
702
- why the stop was requested. Runtime alone settles submission futures and starts
703
- the next batch. Runtime-owned steer retains its logical turn id, but starts a new
704
- `run_turn` execution with a fresh iteration count. `/queue` does not request a stop.
705
- Checks after compaction prevent an extra sample when steer arrives during it.
706
- Direct Agent calls never consume Runtime queues; their caller observes
707
- `TurnInterrupted`, and pending input waits for the Runtime worker.
708
-
709
- The core loop has one threshold-compaction entry, then samples, commits output
710
- and executes tools or finishes. Before the first sample, compaction precedes new
711
- user input; later it includes completed tool results. Context-overflow recovery
712
- stays inside sampling: compact and retry once without counting another iteration
713
- or replaying tools. Stops are checked before each request attempt and after the
714
- issued tool batch, without a separate preparation wrapper. Network retries remain
715
- inside the provider.
716
-
717
- `await agent.compact()` computes a summary and commits the replacement history
718
- only after the summary and rollout checkpoint succeed. Failed compaction leaves
719
- the active history unchanged, including tool results pruned from retry prompts.
720
- History persistence failures are surfaced rather than silently ignored.
721
- Manual and automatic compaction share the same execution/observation path.
722
-
723
- Sub-agent status comes from Agent lifecycle events and current busy/queue state,
724
- including direct and background invocations. Bare `Agent.shutdown()` rejects new
725
- calls and disables background hooks, but lets the current turn finish. `runtime.close()`,
726
- `close_agent`, and workspace close wait for accepted work and child workers to
727
- finish naturally; a stuck request can therefore keep close waiting.
728
- `resume_agent` explicitly reopens the child. No turn-abort or synthetic
729
- interruption-result recovery is performed.
730
-
731
- Queue lifecycle uses only `start()` and `close()`. Closing switches off admission;
732
- the worker exits when the queues are empty, without a special shutdown request.
733
- Background invocations obey the same admission switch. Tool shutdown hooks run
734
- once, after draining accepted work. Concurrent or repeated close calls share the
735
- worker's completion and errors; cancelling a caller does not cancel its Agent turn.
736
-
737
- CLI, web and Feishu restore through the backend's `runtime.resume(session_file_path)`,
738
- which delegates to `agent.resume(session_file_path)`. It rejects
739
- active or queued work, loads history, restores supported provider session
740
- identity, switches its internal recorder, and clears stale usage in one
741
- synchronous operation. Load failures leave the existing session unchanged.
742
- The backend broadcasts restored history/title/identity to all attached views.
743
- `agent.resume()` without a path reopens the same in-memory Agent and rebinds its
744
- tool callbacks, without reading or writing a file or changing history, identity,
745
- recorder or usage. It returns `None` and also rejects active or queued work.
746
- Sub-agent services use this form to reopen the child's in-memory history.
747
- There is no separate `reopen()` method.
748
- Model switching and history replacement remain explicit operations because they
749
- enforce state consistency, not merely assign a field.
750
-
751
- ### Shared session backend
752
-
753
- CLI, Web and Feishu all use `AgentRuntime.submit_input`: not just `/model`
754
- and `/resume`, but every session command, steer/queue admission, interactive
755
- questions and permissions, state notifications and shutdown. Frontends only
756
- adapt input and render events. Web no longer imports or runs the CLI shell.
757
- Workspace assembly uses `pycodex_ws.build_runtime()` to add
758
- `/link` and `/unlink`; `pycodex.bootstrap.build_runtime()` provides core commands.
759
-
760
- ```python
761
- from pycodex.bootstrap import build_agent, build_model, build_runtime
762
-
763
- async def run_session():
764
- runtime = build_runtime(build_agent(build_model()))
765
- await runtime.start()
766
- observer = runtime.attach(lambda event: print(event.kind))
767
- try:
768
- receipt = await runtime.submit_input("/model", sender="application")
769
- result = await receipt.future
770
- return result
771
- finally:
772
- await runtime.close()
773
- runtime.detach(observer)
774
- ```
775
-
776
- Attach immediately supplies a state snapshot; subsequent events synchronize
777
- all views. Detach does not close the backend. Plain input steers at the next
778
- sampling boundary, `/queue` waits for its own turn, and busy Feishu cards still
779
- accept steer or question answers. Unknown slash commands report errors instead
780
- of silently entering model history. See [runtime contracts](docs/RUNTIME.md)
781
- for command receipts, structured answers and session ownership.
782
-
783
- Python observers receive typed `Event` dataclasses from `pycodex.events`, such
784
- as `TurnStartedEvent` and `ToolCompletedEvent`, not generic payload dictionaries.
785
- Custom model clients emit `ModelEvent` variants such as `AssistantDeltaEvent`.
786
- Events provide reusable plain text through `event.visualize()`, or render through
787
- `event.render(display)`. Each CLI view and Feishu card owns an `EventDisplay` with
788
- independent stream/queue state and log/status/prompt callbacks. Events own colors,
789
- flushing, status transitions and prompt text; frontends execute I/O or update their
790
- display fields. Feishu disables colors and shows a bounded recent transcript plus
791
- the live stream, rather than a separate last-answer/last-turn projection.
792
- There is no display-handler mapping. Web retains its own projection and unchanged
793
- event JSON format.
794
-
795
- ### Internal contracts
796
-
797
- - Tools implement async `BaseTool.run`; synchronous implementations are not
798
- implicitly accepted. Tool-specific follow-up messages come from
799
- `BaseTool.follow_up_messages`, not tool-name checks in the Agent loop.
800
- - Completed tool results are committed individually. Explicitly parallel tool
801
- batches wait for all their operations before propagating a commit failure;
802
- they do not cancel remaining operations or invent interruption results.
803
- - Background tools bind their own callbacks through `BaseTool.bind_agent`.
804
- The Agent does not reach into specific tools' private managers.
805
- - Event observers report failures to the asyncio exception handler without
806
- changing the execution result. Provider stream callbacks run on the owning
807
- event loop and stop delivering when their request is finished or cancelled.
808
- - `ModelClient.model` is a required read-only model identifier, also supplied by
809
- test clients. `Agent.model_name` reads it directly, without `getattr` fallback.
810
- `ModelClient.complete` returns a `ModelResponse` containing only supported
811
- output item types. Context overflow is reported as `ContextLengthExceeded`;
812
- provider-specific text classification stays inside the Responses client.
813
- - A terminal `response.incomplete` is not a disconnected stream and is not
814
- retried. For `max_output_tokens`, done assistant/reasoning items are retained
815
- for the next turn, but uncommitted text deltas and unresolved tool calls are not.
816
- - Interactive clients expose the `ModelControl` interface in `pycodex.model`.
817
- Use `agent.set_model(name)` to update provider metadata, context instructions,
818
- context limits, and usage state together.
819
- - Runtime services belong to the tool registry. Pass an explicit environment to
820
- `ToolRegistry(environment)` or `get_tools(environment)` when needed; the Agent
821
- uses `agent.tool_registry.runtime_environment`, not a forwarding Agent property.
822
- The global `get_agent_runtime_environment()` and
823
- separate `Agent(..., runtime_environment=...)` entry points are removed.
824
- - Configure cwd once with `ContextConfig(cwd=...)` or `build_agent(cwd=...)`.
825
- Tools use the required `ToolContext.cwd` for each call; tool constructors
826
- and `get_tools` do not take a separate default directory.
827
- - Each sub-agent gets its own context manager and runtime services. The sub-agent
828
- service retains terminal event status and queues one notification per terminal
829
- event. The parent consumes these notifications before its next normal model
830
- request, without requiring `wait_agent`; an idle parent waits for its next turn.
831
- - Every Agent records its session, including direct and child Agents; external
832
- callers no longer construct or inject `SessionRolloutRecorder`. The outer queue
833
- remains optional and no worker starts implicitly. Empty initial history and a
834
- no-op event callback remain valid defaults.
835
-
836
- See `docs/RUNTIME.md` for state ownership and failure semantics.
837
-
838
- ## Alignment Checklist
839
-
840
- See `docs/ALIGNMENT.md` for more detail. This section keeps a high-level
841
- checklist for quick status scanning.
842
-
843
- ### Tool Alignment
844
-
845
- Official upstream tools:
846
-
847
- - [x] `shell` - run shell commands in argv form.
848
- - [x] `shell_command` - run shell scripts in string form.
849
- - [x] `exec_command` - start long-running commands with a session.
850
- - [x] `write_stdin` - write stdin to an existing execution session or poll
851
- output.
852
- - [x] `web_search` - expose provider-native web search capability.
853
- - [x] `update_plan` - update the task plan and maintain step status.
854
- - [x] `request_user_input` - retain the declaration and return the upstream
855
- Default-mode unavailable response.
856
- - [x] `request_permissions` - request extra permissions before continuing.
857
- - [x] `spawn_agent` - create and start a sub-agent.
858
- - [x] `send_input` - continue feeding input to an existing sub-agent.
859
- - [x] `resume_agent` - reopen a closed sub-agent.
860
- - [x] `wait_agent` - wait for a sub-agent to reach a terminal state.
861
- - [x] `close_agent` - close a sub-agent that is no longer needed.
862
- - [x] `apply_patch` - edit files precisely with a freeform patch.
863
- - [x] `grep_files` - search file contents by pattern.
864
- - [x] `read_file` - read file slices while preserving line-number semantics.
865
- - [x] `list_dir` - list directory tree slices.
866
- - [x] `view_image` - turn a local image into model-visible input.
867
-
868
- Upstream low-frequency / special-mode tools not yet modeled separately:
869
-
870
- - [ ] `wait_infinite` - long blocking wait for external events or later input.
871
- - [ ] `spawn_agents_on_csv` - create sub-agent jobs in bulk from CSV.
872
- - [ ] `report_agent_job_result` - report batch agent job results.
873
- - [ ] `js_repl` - JavaScript REPL / code-mode primary entry point.
874
- - [ ] `js_repl_reset` - reset `js_repl` state.
875
- - [ ] `artifacts` - generate or manage structured artifact outputs.
876
- - [ ] `list_mcp_resources` - list MCP resources.
877
- - [ ] `list_mcp_resource_templates` - list MCP resource templates.
878
- - [ ] `read_mcp_resource` - read MCP resource contents.
879
- - [ ] `multi_tool_use.parallel` - parallel wrapper around multiple developer
880
- tool calls.
881
-
882
- Repository-specific compatibility / transition tools:
883
-
884
- - [x] `clock` - pycodex periodic Agent wake-up extension.
885
- - [x] `exec` - current local approximation of code mode.
886
- - [x] `wait` - current local approximation of code-mode waiting behavior.
887
-
888
- ### Behavior Alignment
889
-
890
- - [x] `AgentLoop` / `AgentRuntime` main loop skeleton - turn loop and submission
891
- queue are in place.
892
- - [x] non-interactive `exec` `instructions` alignment - base instructions match
893
- upstream.
894
- - [x] shared `exec` context content - the audited first/resume/tool-follow-up
895
- scenarios match after documented exclusions; raw item identity is not equal.
896
- - [x] developer/contextual-user message shape alignment - message/content shape
897
- matches upstream.
898
- - [x] `AGENTS.md` + `<environment_context>` injection alignment - context
899
- assembly order matches upstream.
900
- - [ ] complete 0.153.4 tool catalog alignment - local `clock`, legacy sub-agent
901
- tools, and upstream goal/deferred/code-mode-v2 surfaces still differ.
902
- - [x] `include = ["reasoning.encrypted_content"]` - reasoning include field is
903
- aligned.
904
- - [x] `prompt_cache_key` - request-level prompt cache key is implemented.
905
- - [x] `x-client-request-id` - request id header is implemented.
906
- - [x] `x-codex-turn-metadata` - turn id / sandbox header is implemented.
907
- - [x] `originator` - mode-aware originator header is implemented.
908
- - [ ] current identity/telemetry header parity - local identity remains
909
- unchanged; the audit records upstream's newer headers and `client_metadata`.
910
- - [x] field-by-field upstream exec-mode tool schema alignment - aligned tools
911
- use class-level specs; `clock` is documented separately as an extension.
912
- - [ ] full interactive-mode and non-`exec` behavior alignment - the non-exec
913
- first-turn context is now on the `codex-tui` path, but continuous REPL
914
- multi-turn behavior is not fully verified yet.
915
- - [ ] sandbox / approvals / compact / memory and other outer behavior alignment
916
- - these systems are still in later scope.