optio-codex 0.1.0__tar.gz

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.
Files changed (52) hide show
  1. optio_codex-0.1.0/PKG-INFO +220 -0
  2. optio_codex-0.1.0/README.md +189 -0
  3. optio_codex-0.1.0/pyproject.toml +52 -0
  4. optio_codex-0.1.0/setup.cfg +4 -0
  5. optio_codex-0.1.0/src/optio_codex/__init__.py +66 -0
  6. optio_codex-0.1.0/src/optio_codex/conversation.py +553 -0
  7. optio_codex-0.1.0/src/optio_codex/conversation_listener.py +322 -0
  8. optio_codex-0.1.0/src/optio_codex/cred_watcher.py +138 -0
  9. optio_codex-0.1.0/src/optio_codex/fs_allowlist.py +149 -0
  10. optio_codex-0.1.0/src/optio_codex/host_actions.py +1070 -0
  11. optio_codex-0.1.0/src/optio_codex/models.py +68 -0
  12. optio_codex-0.1.0/src/optio_codex/prompt.py +184 -0
  13. optio_codex-0.1.0/src/optio_codex/seed_manifest.py +91 -0
  14. optio_codex-0.1.0/src/optio_codex/session.py +731 -0
  15. optio_codex-0.1.0/src/optio_codex/snapshots.py +147 -0
  16. optio_codex-0.1.0/src/optio_codex/types.py +325 -0
  17. optio_codex-0.1.0/src/optio_codex/verify.py +352 -0
  18. optio_codex-0.1.0/src/optio_codex.egg-info/PKG-INFO +220 -0
  19. optio_codex-0.1.0/src/optio_codex.egg-info/SOURCES.txt +50 -0
  20. optio_codex-0.1.0/src/optio_codex.egg-info/dependency_links.txt +1 -0
  21. optio_codex-0.1.0/src/optio_codex.egg-info/requires.txt +9 -0
  22. optio_codex-0.1.0/src/optio_codex.egg-info/top_level.txt +1 -0
  23. optio_codex-0.1.0/tests/test_await_codex_gone.py +54 -0
  24. optio_codex-0.1.0/tests/test_codex_cache.py +324 -0
  25. optio_codex-0.1.0/tests/test_config.py +185 -0
  26. optio_codex-0.1.0/tests/test_conversation.py +522 -0
  27. optio_codex-0.1.0/tests/test_conversation_listener.py +214 -0
  28. optio_codex-0.1.0/tests/test_cred_watcher.py +226 -0
  29. optio_codex-0.1.0/tests/test_file_download.py +189 -0
  30. optio_codex-0.1.0/tests/test_file_upload.py +126 -0
  31. optio_codex-0.1.0/tests/test_fs_allowlist.py +124 -0
  32. optio_codex-0.1.0/tests/test_host_actions.py +367 -0
  33. optio_codex-0.1.0/tests/test_import.py +15 -0
  34. optio_codex-0.1.0/tests/test_kill_ttyd_by_socket.py +38 -0
  35. optio_codex-0.1.0/tests/test_models.py +67 -0
  36. optio_codex-0.1.0/tests/test_prompt.py +86 -0
  37. optio_codex-0.1.0/tests/test_real_codex_conversation.py +112 -0
  38. optio_codex-0.1.0/tests/test_real_codex_seed_resume.py +218 -0
  39. optio_codex-0.1.0/tests/test_real_codex_session.py +85 -0
  40. optio_codex-0.1.0/tests/test_sandbox_enforce.py +158 -0
  41. optio_codex-0.1.0/tests/test_seed_manifest.py +58 -0
  42. optio_codex-0.1.0/tests/test_session_conversation.py +359 -0
  43. optio_codex-0.1.0/tests/test_session_lease.py +143 -0
  44. optio_codex-0.1.0/tests/test_session_local.py +215 -0
  45. optio_codex-0.1.0/tests/test_session_remote.py +130 -0
  46. optio_codex-0.1.0/tests/test_session_resume.py +184 -0
  47. optio_codex-0.1.0/tests/test_session_sandbox.py +93 -0
  48. optio_codex-0.1.0/tests/test_session_seed.py +155 -0
  49. optio_codex-0.1.0/tests/test_snapshots.py +161 -0
  50. optio_codex-0.1.0/tests/test_teardown_session_tree.py +81 -0
  51. optio_codex-0.1.0/tests/test_verify.py +208 -0
  52. optio_codex-0.1.0/tests/test_workdir_trust.py +70 -0
@@ -0,0 +1,220 @@
1
+ Metadata-Version: 2.4
2
+ Name: optio-codex
3
+ Version: 0.1.0
4
+ Summary: Run OpenAI Codex as an optio task; local subprocess; ttyd-served TUI iframe.
5
+ Author-email: Kristof Csillag <kristof.csillag@deai-labs.com>
6
+ License-Expression: Apache-2.0
7
+ Project-URL: Homepage, https://github.com/deai-network/optio
8
+ Project-URL: Repository, https://github.com/deai-network/optio
9
+ Project-URL: Issues, https://github.com/deai-network/optio/issues
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Operating System :: POSIX :: Linux
16
+ Classifier: Operating System :: MacOS
17
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
18
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
19
+ Classifier: Topic :: Software Development :: Code Generators
20
+ Classifier: Framework :: AsyncIO
21
+ Requires-Python: >=3.11
22
+ Description-Content-Type: text/markdown
23
+ Requires-Dist: optio-core<0.4,>=0.3
24
+ Requires-Dist: optio-host<0.3,>=0.2
25
+ Requires-Dist: optio-agents<0.4,>=0.3
26
+ Requires-Dist: asyncssh>=2.14
27
+ Requires-Dist: aiohttp>=3.9
28
+ Provides-Extra: dev
29
+ Requires-Dist: pytest>=8.0; extra == "dev"
30
+ Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
31
+
32
+ # optio-codex
33
+
34
+ Run OpenAI Codex as an `optio` task — either as the interactive TUI embedded
35
+ in the optio dashboard via an iframe widget served by `ttyd`, or in
36
+ conversation mode (codex app-server over stdio) rendered by the
37
+ `optio-conversation-ui` widget. Local or remote (SSH) workers.
38
+
39
+ ## Install
40
+
41
+ ```bash
42
+ pip install optio-codex
43
+ ```
44
+
45
+ Requires Python 3.11+. Pulls `optio-core`, `optio-host`, `optio-agents`,
46
+ `asyncssh`, and `aiohttp`.
47
+
48
+ ## What it does
49
+
50
+ optio-codex launches `codex` inside a detached tmux session, serves the
51
+ TUI over `ttyd`, and coordinates with the host harness through the
52
+ `optio.log` keyword channel (STATUS / DELIVERABLE / DONE / ERROR). The
53
+ agent reads its task from an `AGENTS.md` file planted in the workdir.
54
+ The tmux+ttyd machinery follows the optio-claudecode pattern, including
55
+ browser handling: `redirect` — `codex login` opens the loopback OAuth URL
56
+ via `xdg-open`, which the redirect shim captures as a `BROWSER:` marker so
57
+ the harness surfaces it to the operator (who completes the sign-in),
58
+ instead of silently swallowing it.
59
+
60
+ ### Isolation
61
+
62
+ Each task runs under an isolated `HOME` (`<workdir>/home`, created at
63
+ prepare time) with `CODEX_HOME` pointing at `<workdir>/home/.codex`, so
64
+ the operator's real `~/.codex` identity and config do not leak into the
65
+ session. The codex binary is launched via a per-task path
66
+ (`<workdir>/home/.local/bin/codex`), so teardown only ever kills this
67
+ task's process.
68
+
69
+ ### Filesystem sandbox
70
+
71
+ Beyond the per-task `HOME`, every codex tool subprocess is confined by
72
+ codex's **own native sandbox** — kernel-enforced (bundled bubblewrap
73
+ primary, Landlock+seccomp fallback on Linux), covering all shell/tool
74
+ commands the agent runs. optio-codex does **not** vendor claustrum for
75
+ this; it renders one resolved sandbox posture (`fs_allowlist.py` SSOT)
76
+ onto every launch surface: the interactive TUI argv, the `codex exec`
77
+ probe flags, and the `codex app-server` command line (`-c
78
+ sandbox_workspace_write.*` overrides; the sandbox *mode* is selected
79
+ out-of-band via `thread/start`'s `sandbox` enum — the 0.142.5 app-server
80
+ schema has no `thread/start.sandboxPolicy` object).
81
+
82
+ `fs_isolation=True` (the default) selects codex `workspace-write`: writes
83
+ are confined to the task workdir, `/tmp`, and any `rw` grants; **reads are
84
+ not restricted**. That read-open behaviour is a deliberate divergence from
85
+ optio-grok/optio-claudecode (whose sandboxes also deny reads) — so
86
+ `AllowedDir("…", "ro")` is a *documented no-op* on codex (an additive grant
87
+ that is already trivially satisfied), kept only for cross-wrapper config
88
+ portability. Only `AllowedDir("…", "rw")` changes behaviour, becoming a
89
+ `sandbox_workspace_write.writable_roots` entry (`~/` expands against the
90
+ real host home at launch). Network access is **OFF** by default (stricter
91
+ than the other wrappers, whose fs sandboxes never touch the network);
92
+ `network_access=True` relaxes it. `fs_isolation=False` runs codex
93
+ unconfined (`danger-full-access`).
94
+
95
+ Extra grants and the mode are cross-validated at config time — e.g.
96
+ `fs_isolation=True` with `sandbox="danger-full-access"`, or an `rw` grant
97
+ under an explicit `read-only` mode, raise `ValueError` rather than silently
98
+ mis-configuring the sandbox.
99
+
100
+ **No optio-side enforcement guard is needed.** codex fails **closed**: on a
101
+ host with no working sandbox mechanism (bubblewrap or Landlock), codex
102
+ errors or panics and the model's command never runs — it never falls back
103
+ to running unconfined (the only unconfined path is the explicit
104
+ `--dangerously-bypass-approvals-and-sandbox` opt-out, which optio-codex
105
+ never emits). This was verified empirically against codex-cli 0.142.5 (see
106
+ the Stage-8 probe verdict in `docs/2026-07-02-optio-codex-design.md`), so
107
+ optio-codex relies on that fail-closed guarantee instead of a launch-time
108
+ probe. As a free hardening bonus, `.codex/` and `.git/` under a writable
109
+ root stay read-only to the agent's shell, so the sandboxed agent cannot
110
+ rewrite its own per-task `auth.json` even though `CODEX_HOME` lives inside
111
+ the workdir.
112
+
113
+ ### Authentication
114
+
115
+ The primary mechanism is **seeds**: log in once, reuse the identity for
116
+ every later task. Run the setup task and log into codex interactively in
117
+ the embedded terminal (`codex login --device-auth`, or `codex login
118
+ --with-api-key`); on teardown the session's `home/.codex` (`auth.json` +
119
+ `config.toml`) is captured as a reusable seed and surfaced through the
120
+ `on_seed_saved` callback. A later task started with
121
+ `CodexTaskConfig(seed_id=…)` merges that stored identity into its fresh
122
+ workdir before launch, so codex starts already logged-in — and the new
123
+ workdir is pre-trusted automatically (`[projects."<workdir>"] trust_level =
124
+ "trusted"` appended to `config.toml`), so codex never prompts about an
125
+ untrusted directory. `seed_id` also accepts a `SeedProvider` callable that
126
+ leases a seed from a pool (the task's `process_id` is the lease holder).
127
+
128
+ Store-binding CRUD helpers (`list_seeds` / `delete_seed` / `purge_seed`)
129
+ operate over the `{prefix}_codex_seeds` collection.
130
+
131
+ **Credential rotation (why a seeded session does more than merge-once):**
132
+ codex's ChatGPT-mode `auth.json` carries a *single-use rotating* refresh
133
+ token (openai/codex#15410) — codex proactively refreshes it after 8 days
134
+ and on any 401, rewriting `auth.json` in place, and a used refresh token
135
+ invalidates every other copy. So a seeded session runs an in-session
136
+ **credential watcher** that saves the rotated `auth.json` back into the
137
+ seed (plus a final teardown backstop); pooled seeds take a **lease** (one
138
+ live lineage per seed — the watcher renews it and aborts the session on
139
+ lease loss); and `verify_and_refresh_seed` refreshes idle pooled seeds
140
+ **host-free** — a direct OpenAI OIDC `refresh_token` grant (no codex
141
+ process, no model turn, non-billable) that persists a fresh token before
142
+ the 8-day cliff, falling back to a headless `codex exec` probe only when
143
+ OIDC discovery is unreachable.
144
+
145
+ Fallbacks without a seed: pass an API key into the session env
146
+ (`CodexTaskConfig(env={"OPENAI_API_KEY": …})`) or log in interactively
147
+ (`codex login`) inside the embedded terminal.
148
+
149
+ ### Binary provisioning
150
+
151
+ The codex binary is resolved through an optio-owned, evictable cache on the
152
+ worker — `OPTIO_CODEX_CACHE_DIR`, else
153
+ `${XDG_CACHE_HOME:-~/.cache}/optio-codex/bin` — resolved host-side, so it is
154
+ correct on a remote SSH worker and never lives under a task workdir or the
155
+ operator's `~/.codex`. On a cache miss the cache is seeded from a host
156
+ `codex` on `PATH` (`cp -L` deref → a stable copy), or, when none exists, the
157
+ pinned release is auto-downloaded (`rust-v0.142.5`, static musl,
158
+ `{x86_64,aarch64}-unknown-linux-musl`). The per-task launch symlink
159
+ (`<workdir>/home/.local/bin/codex`) is preserved and points into the cache,
160
+ so task-scoped teardown stays unaffected.
161
+
162
+ ## Status — Stages 0–8 (feature-complete against the Appendix-A parity bar)
163
+
164
+ Verified against `docs/writing-agent-wrappers.md` Appendix A — 28 of 29 items
165
+ green (see `docs/2026-07-02-optio-codex-parity-audit.md` for per-item
166
+ `file:line` evidence). Suite: 188 passed, 4 skipped (the skips are the opt-in
167
+ real-binary tests, env-gated, never in the default suite).
168
+
169
+ Shipped:
170
+
171
+ - **Two run modes** — iframe/ttyd interactive TUI *and* conversation mode
172
+ (codex app-server over stdio) with a conversation-ui widget
173
+ (`optio-conversation-ui`, `widgetData.protocol = "codex"` → `CodexView`)
174
+ - `optio.log` keyword-protocol coordination + exit-status DONE/ERROR channel
175
+ - per-task `HOME` / `CODEX_HOME` isolation (tree provisioned at prepare)
176
+ - **filesystem isolation** via codex's native sandbox — default-ON
177
+ `fs_isolation` (`workspace-write`), `extra_allowed_dirs` (`rw` grants →
178
+ `writable_roots`), `network_access` (OFF by default); fail-closed, no
179
+ optio-side guard needed (see the Filesystem sandbox section above)
180
+ - task-scoped teardown (per-task codex path; orphan-ttyd reap = crash-orphan
181
+ rescue)
182
+ - `create_codex_task`, `run_codex_session`, `CodexTaskConfig`
183
+ - remote SSH workers (`ssh=SSHConfig(...)` routes to `RemoteHost`; verified
184
+ end-to-end against a docker-sshd harness)
185
+ - resume / workdir snapshots: session-id-keyed relaunch (`codex resume <id>`,
186
+ never `resume --last`), Mongo snapshot store (retention 5, single workdir
187
+ GridFS blob carrying `home/.codex/sessions`), `resume.log` + AGENTS.md
188
+ resume section synced to the snapshot exclude list
189
+ (`workdir_exclude`; defaults drop `home/.codex/packages`, `*.sqlite*`,
190
+ caches — never `home/.codex/sessions`); auto-resume-on-restart via
191
+ optio-core (`supports_resume=True`)
192
+ - seeds: log-in-once capture (`on_seed_saved`) + `seed_id` consume with
193
+ automatic workdir pre-trust; store-binding CRUD (`list_seeds` /
194
+ `delete_seed` / `purge_seed`) over `{prefix}_codex_seeds`
195
+ - pool leases + in-session credential save-back (single-use rotating
196
+ refresh token) with a teardown backstop; lease loss aborts the session
197
+ - host-free `verify_and_refresh_seed` — primary path is a direct OpenAI OIDC
198
+ `refresh_token` grant (non-billable, no codex process) with rotated-token
199
+ write-back and pool-status stamping; falls back to a headless `codex exec`
200
+ probe (stdout-only verdict) only when OIDC discovery is unreachable
201
+ - optio-owned evictable binary cache (`OPTIO_CODEX_CACHE_DIR`), seeded from a
202
+ host binary (`cp -L`) or real GitHub-release auto-download (pinned
203
+ `rust-v0.142.5`, musl); per-task launch symlink preserved
204
+ - conversation-ui surface: permission gate, **inline** model switching, file
205
+ upload/download (`optio-file:`), tool verbosity
206
+ - demo trio: seed-setup + seed-pinned iframe + seed-pinned conversation tasks
207
+ (auto-appear via `fw.resync()`)
208
+
209
+ Remaining opt gaps (deliberate — see the parity audit):
210
+
211
+ - **session restore / rebase (scripted transcript reconstruction):** not
212
+ shipped. codex's resume story is snapshot + `codex resume <id>` (shipped
213
+ above); claudecode's scripted `transcript.py` rebase is engine-specific and
214
+ has no codex analogue. optio-grok and optio-opencode also omit it — parity,
215
+ not a regression.
216
+ - **at-rest encryption of the session blob:** the `encrypt`/`decrypt` seam is
217
+ plumbed through but not activated (sessions pass `encrypt=None`) — identical
218
+ posture to every other optio wrapper.
219
+
220
+ Not yet published to PyPI (first release is a separate, user-approved step).
@@ -0,0 +1,189 @@
1
+ # optio-codex
2
+
3
+ Run OpenAI Codex as an `optio` task — either as the interactive TUI embedded
4
+ in the optio dashboard via an iframe widget served by `ttyd`, or in
5
+ conversation mode (codex app-server over stdio) rendered by the
6
+ `optio-conversation-ui` widget. Local or remote (SSH) workers.
7
+
8
+ ## Install
9
+
10
+ ```bash
11
+ pip install optio-codex
12
+ ```
13
+
14
+ Requires Python 3.11+. Pulls `optio-core`, `optio-host`, `optio-agents`,
15
+ `asyncssh`, and `aiohttp`.
16
+
17
+ ## What it does
18
+
19
+ optio-codex launches `codex` inside a detached tmux session, serves the
20
+ TUI over `ttyd`, and coordinates with the host harness through the
21
+ `optio.log` keyword channel (STATUS / DELIVERABLE / DONE / ERROR). The
22
+ agent reads its task from an `AGENTS.md` file planted in the workdir.
23
+ The tmux+ttyd machinery follows the optio-claudecode pattern, including
24
+ browser handling: `redirect` — `codex login` opens the loopback OAuth URL
25
+ via `xdg-open`, which the redirect shim captures as a `BROWSER:` marker so
26
+ the harness surfaces it to the operator (who completes the sign-in),
27
+ instead of silently swallowing it.
28
+
29
+ ### Isolation
30
+
31
+ Each task runs under an isolated `HOME` (`<workdir>/home`, created at
32
+ prepare time) with `CODEX_HOME` pointing at `<workdir>/home/.codex`, so
33
+ the operator's real `~/.codex` identity and config do not leak into the
34
+ session. The codex binary is launched via a per-task path
35
+ (`<workdir>/home/.local/bin/codex`), so teardown only ever kills this
36
+ task's process.
37
+
38
+ ### Filesystem sandbox
39
+
40
+ Beyond the per-task `HOME`, every codex tool subprocess is confined by
41
+ codex's **own native sandbox** — kernel-enforced (bundled bubblewrap
42
+ primary, Landlock+seccomp fallback on Linux), covering all shell/tool
43
+ commands the agent runs. optio-codex does **not** vendor claustrum for
44
+ this; it renders one resolved sandbox posture (`fs_allowlist.py` SSOT)
45
+ onto every launch surface: the interactive TUI argv, the `codex exec`
46
+ probe flags, and the `codex app-server` command line (`-c
47
+ sandbox_workspace_write.*` overrides; the sandbox *mode* is selected
48
+ out-of-band via `thread/start`'s `sandbox` enum — the 0.142.5 app-server
49
+ schema has no `thread/start.sandboxPolicy` object).
50
+
51
+ `fs_isolation=True` (the default) selects codex `workspace-write`: writes
52
+ are confined to the task workdir, `/tmp`, and any `rw` grants; **reads are
53
+ not restricted**. That read-open behaviour is a deliberate divergence from
54
+ optio-grok/optio-claudecode (whose sandboxes also deny reads) — so
55
+ `AllowedDir("…", "ro")` is a *documented no-op* on codex (an additive grant
56
+ that is already trivially satisfied), kept only for cross-wrapper config
57
+ portability. Only `AllowedDir("…", "rw")` changes behaviour, becoming a
58
+ `sandbox_workspace_write.writable_roots` entry (`~/` expands against the
59
+ real host home at launch). Network access is **OFF** by default (stricter
60
+ than the other wrappers, whose fs sandboxes never touch the network);
61
+ `network_access=True` relaxes it. `fs_isolation=False` runs codex
62
+ unconfined (`danger-full-access`).
63
+
64
+ Extra grants and the mode are cross-validated at config time — e.g.
65
+ `fs_isolation=True` with `sandbox="danger-full-access"`, or an `rw` grant
66
+ under an explicit `read-only` mode, raise `ValueError` rather than silently
67
+ mis-configuring the sandbox.
68
+
69
+ **No optio-side enforcement guard is needed.** codex fails **closed**: on a
70
+ host with no working sandbox mechanism (bubblewrap or Landlock), codex
71
+ errors or panics and the model's command never runs — it never falls back
72
+ to running unconfined (the only unconfined path is the explicit
73
+ `--dangerously-bypass-approvals-and-sandbox` opt-out, which optio-codex
74
+ never emits). This was verified empirically against codex-cli 0.142.5 (see
75
+ the Stage-8 probe verdict in `docs/2026-07-02-optio-codex-design.md`), so
76
+ optio-codex relies on that fail-closed guarantee instead of a launch-time
77
+ probe. As a free hardening bonus, `.codex/` and `.git/` under a writable
78
+ root stay read-only to the agent's shell, so the sandboxed agent cannot
79
+ rewrite its own per-task `auth.json` even though `CODEX_HOME` lives inside
80
+ the workdir.
81
+
82
+ ### Authentication
83
+
84
+ The primary mechanism is **seeds**: log in once, reuse the identity for
85
+ every later task. Run the setup task and log into codex interactively in
86
+ the embedded terminal (`codex login --device-auth`, or `codex login
87
+ --with-api-key`); on teardown the session's `home/.codex` (`auth.json` +
88
+ `config.toml`) is captured as a reusable seed and surfaced through the
89
+ `on_seed_saved` callback. A later task started with
90
+ `CodexTaskConfig(seed_id=…)` merges that stored identity into its fresh
91
+ workdir before launch, so codex starts already logged-in — and the new
92
+ workdir is pre-trusted automatically (`[projects."<workdir>"] trust_level =
93
+ "trusted"` appended to `config.toml`), so codex never prompts about an
94
+ untrusted directory. `seed_id` also accepts a `SeedProvider` callable that
95
+ leases a seed from a pool (the task's `process_id` is the lease holder).
96
+
97
+ Store-binding CRUD helpers (`list_seeds` / `delete_seed` / `purge_seed`)
98
+ operate over the `{prefix}_codex_seeds` collection.
99
+
100
+ **Credential rotation (why a seeded session does more than merge-once):**
101
+ codex's ChatGPT-mode `auth.json` carries a *single-use rotating* refresh
102
+ token (openai/codex#15410) — codex proactively refreshes it after 8 days
103
+ and on any 401, rewriting `auth.json` in place, and a used refresh token
104
+ invalidates every other copy. So a seeded session runs an in-session
105
+ **credential watcher** that saves the rotated `auth.json` back into the
106
+ seed (plus a final teardown backstop); pooled seeds take a **lease** (one
107
+ live lineage per seed — the watcher renews it and aborts the session on
108
+ lease loss); and `verify_and_refresh_seed` refreshes idle pooled seeds
109
+ **host-free** — a direct OpenAI OIDC `refresh_token` grant (no codex
110
+ process, no model turn, non-billable) that persists a fresh token before
111
+ the 8-day cliff, falling back to a headless `codex exec` probe only when
112
+ OIDC discovery is unreachable.
113
+
114
+ Fallbacks without a seed: pass an API key into the session env
115
+ (`CodexTaskConfig(env={"OPENAI_API_KEY": …})`) or log in interactively
116
+ (`codex login`) inside the embedded terminal.
117
+
118
+ ### Binary provisioning
119
+
120
+ The codex binary is resolved through an optio-owned, evictable cache on the
121
+ worker — `OPTIO_CODEX_CACHE_DIR`, else
122
+ `${XDG_CACHE_HOME:-~/.cache}/optio-codex/bin` — resolved host-side, so it is
123
+ correct on a remote SSH worker and never lives under a task workdir or the
124
+ operator's `~/.codex`. On a cache miss the cache is seeded from a host
125
+ `codex` on `PATH` (`cp -L` deref → a stable copy), or, when none exists, the
126
+ pinned release is auto-downloaded (`rust-v0.142.5`, static musl,
127
+ `{x86_64,aarch64}-unknown-linux-musl`). The per-task launch symlink
128
+ (`<workdir>/home/.local/bin/codex`) is preserved and points into the cache,
129
+ so task-scoped teardown stays unaffected.
130
+
131
+ ## Status — Stages 0–8 (feature-complete against the Appendix-A parity bar)
132
+
133
+ Verified against `docs/writing-agent-wrappers.md` Appendix A — 28 of 29 items
134
+ green (see `docs/2026-07-02-optio-codex-parity-audit.md` for per-item
135
+ `file:line` evidence). Suite: 188 passed, 4 skipped (the skips are the opt-in
136
+ real-binary tests, env-gated, never in the default suite).
137
+
138
+ Shipped:
139
+
140
+ - **Two run modes** — iframe/ttyd interactive TUI *and* conversation mode
141
+ (codex app-server over stdio) with a conversation-ui widget
142
+ (`optio-conversation-ui`, `widgetData.protocol = "codex"` → `CodexView`)
143
+ - `optio.log` keyword-protocol coordination + exit-status DONE/ERROR channel
144
+ - per-task `HOME` / `CODEX_HOME` isolation (tree provisioned at prepare)
145
+ - **filesystem isolation** via codex's native sandbox — default-ON
146
+ `fs_isolation` (`workspace-write`), `extra_allowed_dirs` (`rw` grants →
147
+ `writable_roots`), `network_access` (OFF by default); fail-closed, no
148
+ optio-side guard needed (see the Filesystem sandbox section above)
149
+ - task-scoped teardown (per-task codex path; orphan-ttyd reap = crash-orphan
150
+ rescue)
151
+ - `create_codex_task`, `run_codex_session`, `CodexTaskConfig`
152
+ - remote SSH workers (`ssh=SSHConfig(...)` routes to `RemoteHost`; verified
153
+ end-to-end against a docker-sshd harness)
154
+ - resume / workdir snapshots: session-id-keyed relaunch (`codex resume <id>`,
155
+ never `resume --last`), Mongo snapshot store (retention 5, single workdir
156
+ GridFS blob carrying `home/.codex/sessions`), `resume.log` + AGENTS.md
157
+ resume section synced to the snapshot exclude list
158
+ (`workdir_exclude`; defaults drop `home/.codex/packages`, `*.sqlite*`,
159
+ caches — never `home/.codex/sessions`); auto-resume-on-restart via
160
+ optio-core (`supports_resume=True`)
161
+ - seeds: log-in-once capture (`on_seed_saved`) + `seed_id` consume with
162
+ automatic workdir pre-trust; store-binding CRUD (`list_seeds` /
163
+ `delete_seed` / `purge_seed`) over `{prefix}_codex_seeds`
164
+ - pool leases + in-session credential save-back (single-use rotating
165
+ refresh token) with a teardown backstop; lease loss aborts the session
166
+ - host-free `verify_and_refresh_seed` — primary path is a direct OpenAI OIDC
167
+ `refresh_token` grant (non-billable, no codex process) with rotated-token
168
+ write-back and pool-status stamping; falls back to a headless `codex exec`
169
+ probe (stdout-only verdict) only when OIDC discovery is unreachable
170
+ - optio-owned evictable binary cache (`OPTIO_CODEX_CACHE_DIR`), seeded from a
171
+ host binary (`cp -L`) or real GitHub-release auto-download (pinned
172
+ `rust-v0.142.5`, musl); per-task launch symlink preserved
173
+ - conversation-ui surface: permission gate, **inline** model switching, file
174
+ upload/download (`optio-file:`), tool verbosity
175
+ - demo trio: seed-setup + seed-pinned iframe + seed-pinned conversation tasks
176
+ (auto-appear via `fw.resync()`)
177
+
178
+ Remaining opt gaps (deliberate — see the parity audit):
179
+
180
+ - **session restore / rebase (scripted transcript reconstruction):** not
181
+ shipped. codex's resume story is snapshot + `codex resume <id>` (shipped
182
+ above); claudecode's scripted `transcript.py` rebase is engine-specific and
183
+ has no codex analogue. optio-grok and optio-opencode also omit it — parity,
184
+ not a regression.
185
+ - **at-rest encryption of the session blob:** the `encrypt`/`decrypt` seam is
186
+ plumbed through but not activated (sessions pass `encrypt=None`) — identical
187
+ posture to every other optio wrapper.
188
+
189
+ Not yet published to PyPI (first release is a separate, user-approved step).
@@ -0,0 +1,52 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "optio-codex"
7
+ version = "0.1.0"
8
+ description = "Run OpenAI Codex as an optio task; local subprocess; ttyd-served TUI iframe."
9
+ readme = "README.md"
10
+ license = "Apache-2.0"
11
+ requires-python = ">=3.11"
12
+ authors = [
13
+ { name = "Kristof Csillag", email = "kristof.csillag@deai-labs.com" },
14
+ ]
15
+ classifiers = [
16
+ "Development Status :: 4 - Beta",
17
+ "Programming Language :: Python :: 3",
18
+ "Programming Language :: Python :: 3.11",
19
+ "Programming Language :: Python :: 3.12",
20
+ "Programming Language :: Python :: 3.13",
21
+ "Operating System :: POSIX :: Linux",
22
+ "Operating System :: MacOS",
23
+ "Topic :: Software Development :: Libraries :: Python Modules",
24
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
25
+ "Topic :: Software Development :: Code Generators",
26
+ "Framework :: AsyncIO",
27
+ ]
28
+ dependencies = [
29
+ "optio-core>=0.3,<0.4",
30
+ "optio-host>=0.2,<0.3",
31
+ "optio-agents>=0.3,<0.4",
32
+ "asyncssh>=2.14",
33
+ "aiohttp>=3.9",
34
+ ]
35
+
36
+ [project.optional-dependencies]
37
+ dev = [
38
+ "pytest>=8.0",
39
+ "pytest-asyncio>=0.23",
40
+ ]
41
+
42
+ [project.urls]
43
+ Homepage = "https://github.com/deai-network/optio"
44
+ Repository = "https://github.com/deai-network/optio"
45
+ Issues = "https://github.com/deai-network/optio/issues"
46
+
47
+ [tool.setuptools.packages.find]
48
+ where = ["src"]
49
+
50
+ [tool.pytest.ini_options]
51
+ asyncio_mode = "auto"
52
+ testpaths = ["tests"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,66 @@
1
+ """optio-codex — run OpenAI Codex as an optio task."""
2
+
3
+ import logging as _logging
4
+
5
+ from optio_agents import HookContext, HookContextProtocol
6
+ from optio_host import (
7
+ HostCommandError,
8
+ RunResult,
9
+ SSHConfig,
10
+ )
11
+
12
+ from optio_codex.seed_manifest import (
13
+ CODEX_CRED_MANIFEST,
14
+ CODEX_SEED_MANIFEST,
15
+ CODEX_SEED_SUFFIX,
16
+ delete_seed,
17
+ list_seeds,
18
+ purge_seed,
19
+ )
20
+ from optio_codex.session import create_codex_task, run_codex_session
21
+ from optio_codex.types import (
22
+ AllowedDir,
23
+ ApprovalPolicy,
24
+ CodexTaskConfig,
25
+ ConversationMode,
26
+ DeliverableCallback,
27
+ HookCallback,
28
+ SandboxMode,
29
+ ToolVerbosity,
30
+ ThinkingVerbosity,
31
+ SeedProvider,
32
+ SeedUnavailableError,
33
+ )
34
+ from optio_codex.verify import verify_and_refresh_seed
35
+
36
+
37
+ _logging.getLogger("asyncssh").setLevel(_logging.WARNING)
38
+
39
+
40
+ __all__ = [
41
+ "create_codex_task",
42
+ "run_codex_session",
43
+ "AllowedDir",
44
+ "ApprovalPolicy",
45
+ "CodexTaskConfig",
46
+ "ConversationMode",
47
+ "DeliverableCallback",
48
+ "HookCallback",
49
+ "SandboxMode",
50
+ "ToolVerbosity",
51
+ "ThinkingVerbosity",
52
+ "SeedProvider",
53
+ "SeedUnavailableError",
54
+ "SSHConfig",
55
+ "HookContext",
56
+ "HookContextProtocol",
57
+ "HostCommandError",
58
+ "RunResult",
59
+ "CODEX_SEED_MANIFEST",
60
+ "CODEX_CRED_MANIFEST",
61
+ "CODEX_SEED_SUFFIX",
62
+ "delete_seed",
63
+ "list_seeds",
64
+ "purge_seed",
65
+ "verify_and_refresh_seed",
66
+ ]