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.
- optio_codex-0.1.0/PKG-INFO +220 -0
- optio_codex-0.1.0/README.md +189 -0
- optio_codex-0.1.0/pyproject.toml +52 -0
- optio_codex-0.1.0/setup.cfg +4 -0
- optio_codex-0.1.0/src/optio_codex/__init__.py +66 -0
- optio_codex-0.1.0/src/optio_codex/conversation.py +553 -0
- optio_codex-0.1.0/src/optio_codex/conversation_listener.py +322 -0
- optio_codex-0.1.0/src/optio_codex/cred_watcher.py +138 -0
- optio_codex-0.1.0/src/optio_codex/fs_allowlist.py +149 -0
- optio_codex-0.1.0/src/optio_codex/host_actions.py +1070 -0
- optio_codex-0.1.0/src/optio_codex/models.py +68 -0
- optio_codex-0.1.0/src/optio_codex/prompt.py +184 -0
- optio_codex-0.1.0/src/optio_codex/seed_manifest.py +91 -0
- optio_codex-0.1.0/src/optio_codex/session.py +731 -0
- optio_codex-0.1.0/src/optio_codex/snapshots.py +147 -0
- optio_codex-0.1.0/src/optio_codex/types.py +325 -0
- optio_codex-0.1.0/src/optio_codex/verify.py +352 -0
- optio_codex-0.1.0/src/optio_codex.egg-info/PKG-INFO +220 -0
- optio_codex-0.1.0/src/optio_codex.egg-info/SOURCES.txt +50 -0
- optio_codex-0.1.0/src/optio_codex.egg-info/dependency_links.txt +1 -0
- optio_codex-0.1.0/src/optio_codex.egg-info/requires.txt +9 -0
- optio_codex-0.1.0/src/optio_codex.egg-info/top_level.txt +1 -0
- optio_codex-0.1.0/tests/test_await_codex_gone.py +54 -0
- optio_codex-0.1.0/tests/test_codex_cache.py +324 -0
- optio_codex-0.1.0/tests/test_config.py +185 -0
- optio_codex-0.1.0/tests/test_conversation.py +522 -0
- optio_codex-0.1.0/tests/test_conversation_listener.py +214 -0
- optio_codex-0.1.0/tests/test_cred_watcher.py +226 -0
- optio_codex-0.1.0/tests/test_file_download.py +189 -0
- optio_codex-0.1.0/tests/test_file_upload.py +126 -0
- optio_codex-0.1.0/tests/test_fs_allowlist.py +124 -0
- optio_codex-0.1.0/tests/test_host_actions.py +367 -0
- optio_codex-0.1.0/tests/test_import.py +15 -0
- optio_codex-0.1.0/tests/test_kill_ttyd_by_socket.py +38 -0
- optio_codex-0.1.0/tests/test_models.py +67 -0
- optio_codex-0.1.0/tests/test_prompt.py +86 -0
- optio_codex-0.1.0/tests/test_real_codex_conversation.py +112 -0
- optio_codex-0.1.0/tests/test_real_codex_seed_resume.py +218 -0
- optio_codex-0.1.0/tests/test_real_codex_session.py +85 -0
- optio_codex-0.1.0/tests/test_sandbox_enforce.py +158 -0
- optio_codex-0.1.0/tests/test_seed_manifest.py +58 -0
- optio_codex-0.1.0/tests/test_session_conversation.py +359 -0
- optio_codex-0.1.0/tests/test_session_lease.py +143 -0
- optio_codex-0.1.0/tests/test_session_local.py +215 -0
- optio_codex-0.1.0/tests/test_session_remote.py +130 -0
- optio_codex-0.1.0/tests/test_session_resume.py +184 -0
- optio_codex-0.1.0/tests/test_session_sandbox.py +93 -0
- optio_codex-0.1.0/tests/test_session_seed.py +155 -0
- optio_codex-0.1.0/tests/test_snapshots.py +161 -0
- optio_codex-0.1.0/tests/test_teardown_session_tree.py +81 -0
- optio_codex-0.1.0/tests/test_verify.py +208 -0
- 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,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
|
+
]
|