Plan Codex app-server support for SCVs

3ed85628b17c · AtlantisPleb · · parent 7b8d4c499dd5

Plan Codex app-server support for SCVs

Deploy story

What this commit did to the running system — joined from the forge receipt chain, the part a commit page elsewhere cannot show.

Not deployed through the forge lane

No push, promotion, build, or deploy receipt references this commit (receipts are scanned over a bounded recent window). Changes shipped by full node replacement carry their proof in the release gate receipt instead.

Changed files

  • added docs/scv-codex-app-server-planning.md

Diff

1 file changed, +758 -0

docs/scv-codex-app-server-planning.md added +758

@@ -0,0 +1,758 @@

1
# SCV Codex app-server planning
2
3
Date: 2026-08-20
4
5
Status: design only; no implementation or deployment changes
6
7
## Outcome
8
9
Add a Codex-backed driver to the SCV runtime without making Codex the durable
10
SCV authority. The OpenAgents coordinator should continue to own the SCV
11
identity, work-item lease, policy, budget, event history, artifacts, candidate,
12
and Forge handoff. A local Codex app-server process should own one bounded Codex
13
execution session behind that contract.
14
15
Use the Codex app-server v2 protocol, not ACP, for the first integration. Start
16
the app-server as a supervised local child process and communicate over
17
standard input and output. This surface provides the account login, thread,
18
turn, approval, rate-limit, and live event operations that an SCV needs.
19
20
Support these credential paths:
21
22
- Prefer a ChatGPT service-account access token for production SCVs when the
23
  workspace and plan support one.
24
- Support ChatGPT device-code login for an operator-linked pilot and for
25
  workspaces that explicitly allow device login.
26
- Support a personal Codex access token when an operator needs ChatGPT
27
  workspace attribution but a non-human service account is unavailable.
28
- Keep API-key authentication available for usage-based automation that does
29
  not need ChatGPT workspace entitlements.
30
- Do not use experimental `chatgptAuthTokens` in the first version.
31
32
Run one isolated Codex account runtime per connected credential. Start with one
33
active SCV run per account runtime. Never switch the account of a process that
34
has a loaded or running thread. A later measured release may allow several
35
threads for the same account in one process, but it must not combine different
36
accounts in that process.
37
38
The first Codex-backed SCV must remain propose-only. Native app-server events
39
observe command and file effects after Codex admits them, but they do not create
40
the durable effect barrier required for autonomous repository writes. Before a
41
Codex-backed SCV receives write or deployment authority, separate the
42
credential-bearing app-server from candidate command execution and prove that
43
candidate code cannot read its credential.
44
45
## Research basis
46
47
This plan uses two current sources:
48
49
- The official [Codex app-server documentation](https://learn.chatgpt.com/docs/app-server),
50
  [Codex SDK documentation](https://learn.chatgpt.com/docs/codex-sdk),
51
  [authentication guide](https://learn.chatgpt.com/docs/auth),
52
  [access-token guide](https://learn.chatgpt.com/docs/enterprise/access-tokens),
53
  [service-account guide](https://learn.chatgpt.com/docs/enterprise/service-accounts),
54
  and [configuration reference](https://learn.chatgpt.com/docs/config-file/config-reference)
55
  as retrieved on 2026-08-20.
56
- The local `openai/codex` checkout at commit
57
  `bf2aee99c58362d3f588d98432dcbc7adc371c73`, committed on 2026-08-20.
58
59
The local checkout is a moving development baseline. Its Python SDK manifest
60
pins `openai-codex-cli-bin` `0.147.0`, while the source checkout contains newer
61
protocol work. Do not build a production contract from the development checkout
62
alone. Pin a released Codex runtime, generate its schema, and retain the
63
checkout only as explanatory source.
64
65
The most relevant inspected paths are:
66
67
| Area | Local Codex path | Finding |
68
| --- | --- | --- |
69
| Protocol overview | `codex-rs/app-server/README.md` | App-server v2 provides JSON-RPC lifecycle, events, approvals, and account methods. |
70
| Account protocol | `codex-rs/app-server-protocol/src/protocol/v2/account.rs` | Managed browser login, device-code login, API-key login, and experimental external-token login are separate modes. |
71
| Account processor | `codex-rs/app-server/src/request_processors/account_processor.rs` | One processor has one active login slot and replaces an earlier pending login when a new login starts. |
72
| Device flow | `codex-rs/login/src/device_code_auth.rs` | Codex requests a one-time code, polls for completion, warns about login phishing, and expires the code after 15 minutes. |
73
| Authentication state | `codex-rs/login/src/auth/manager.rs` | One process shares one `AuthManager` and one current authentication snapshot across its thread manager. |
74
| Credential storage | `codex-rs/login/src/auth/storage.rs` | File storage writes `auth.json` with mode `0600`; managed tokens, refresh state, and other authentication material live in that record. |
75
| Process-wide thread state | `codex-rs/app-server/src/message_processor.rs` and `codex-rs/core/src/thread_manager.rs` | Threads in one process share the same authentication manager and process-scoped state stores. |
76
| Environment inheritance | `codex-rs/protocol/src/config_types.rs` and `codex-rs/protocol/src/shell_environment.rs` | The default shell policy inherits all variables and keeps names containing `KEY`, `SECRET`, or `TOKEN` unless the host changes the policy. |
77
| Python SDK | `sdk/python/src/openai_codex/client.py` and `sdk/python/src/openai_codex/_login.py` | The stable SDK starts app-server over standard input and output and exposes device-login handles, typed requests, notifications, and approvals. Its default approval callback accepts command and file requests, so an SCV must replace it if the SDK is used. |
78
79
The protocol source contains account-session data structures, but this checkout
80
does not register corresponding account-session requests or implement their
81
processor methods. Do not infer supported multi-account switching from unused
82
types.
83
84
## Decision
85
86
Treat Codex app-server as another SCV driver implementation:
87
88
```text
89
OpenAgents SCV coordinator
90
          |
91
          | durable run request, lease, policy, and budget
92
          v
93
SCV worker and Codex driver
94
          |
95
          | local JSONL JSON-RPC over an Erlang Port
96
          v
97
codex app-server --listen stdio://
98
          |
99
          | one credential and one isolated CODEX_HOME
100
          v
101
Codex thread and turns for one SCV run
102
```
103
104
Name the driver `codex_app_server` in durable SCV records. The name identifies
105
the implementation boundary; it does not create a second kind of SCV. Keep
106
OpenCode as a separate driver behind the same SCV run and event contracts.
107
108
Implement the first protocol client in Elixir as a supervised port adapter.
109
This preserves the Elixir-native coordinator, avoids placing Node.js or Python
110
in the control plane, and exposes the complete event and approval stream. Use
111
the official Python SDK as a conformance oracle and fallback packaging option,
112
not as the initial authority. If OpenAI requires the stable SDK for production
113
support, replace the port adapter with a narrow Python SDK bridge without
114
changing the outer SCV contract.
115
116
This decision carries a support gate. OpenAI describes app-server as the rich
117
product-integration surface, but its documentation also labels the app-server
118
command and WebSocket transport experimental and recommends the Codex SDK for
119
automated jobs. Do not call the driver production-ready until one of these is
120
true:
121
122
- OpenAI confirms that the pinned app-server use is supported for this
123
  integration.
124
- The driver uses a stable Codex SDK release that pins its Codex runtime.
125
- We explicitly accept the version-support risk and maintain the required
126
  compatibility matrix and rollback path.
127
128
## Why app-server is the appropriate surface
129
130
| Option | Useful capabilities | Decision |
131
| --- | --- | --- |
132
| Codex app-server v2 | Device login, account inspection, rate limits, threads, turns, approvals, live item events, cancellation, history, and output schemas | Use as the SCV driver protocol. |
133
| Codex Python SDK | Stable typed wrapper around a pinned local app-server runtime, including device login and streaming | Use for conformance and as the production fallback if direct protocol support is unacceptable. Always replace its permissive default approval handler. |
134
| Codex TypeScript SDK and `codex exec` | Strong batch automation, JSONL progress, resumable threads, and structured final output | Keep as a possible batch driver, but it does not provide the full operator account-connection and approval surface needed here. |
135
| Codex MCP server | Exposes `codex` and `codex-reply` tools to another MCP client | Do not use for the primary driver. It intentionally compresses Codex into tool calls and omits the account, rate-limit, approval, and detailed lifecycle control that the SCV coordinator needs. |
136
| ACP | No first-party ACP implementation or supported ACP surface appeared in the inspected checkout or official Codex app-server documentation | Do not design against ACP. Re-evaluate only if OpenAI publishes a supported surface. |
137
| Experimental `chatgptAuthTokens` | Lets a host that already owns the ChatGPT authentication lifecycle inject access tokens and answer refresh requests | Do not use. OpenAgents does not own a supported ChatGPT OAuth client lifecycle, the mode is experimental, and refresh responses have a short deadline. |
138
139
MCP remains useful when another general-purpose model uses Codex as one tool.
140
That is not the SCV topology. OpenAgents already owns durable orchestration and
141
needs Codex's lower-level run events.
142
143
## SCV authority boundary
144
145
The Codex thread is not the durable SCV. The Codex app-server process may exit,
146
its connection may close, or a thread may become unloadable without changing
147
the SCV's identity. The outer SCV record must remain authoritative for:
148
149
- the admitted work item and exact repository base SHA;
150
- the SCV ID, run ID, execution ID, lease generation, and driver revision;
151
- the account runtime selected for the run;
152
- the allowed repository, branch, paths, commands, network, and risk class;
153
- wall, token, cost, process, and storage budgets;
154
- cancellation and operator pause state;
155
- durable events, reports, artifacts, candidate commits, and Forge receipts;
156
- terminal status and recovery decisions.
157
158
Store the Codex thread ID and turn IDs as nested driver identifiers. Never use a
159
Codex thread ID as an SCV lease, authority token, or idempotency key.
160
161
The coordinator must still make every work-admission and Forge decision. Codex
162
may propose edits and produce evidence, but it must not receive promotion,
163
deployment, SCV-policy, account-management, or credential-management tools.
164
165
## Authentication strategy
166
167
### Credential choices
168
169
| Credential | Appropriate use | Persistence | Initial status |
170
| --- | --- | --- | --- |
171
| ChatGPT service-account access token | Production Business or Enterprise automation that needs a non-human ChatGPT workspace identity, governance, and attribution | Store the token in the platform secret manager and rotate it. Do not persist a login in the worker. | Preferred production path when available. |
172
| Personal Codex access token | Trusted automation attributed to one workspace member | Store and rotate it like any other automation secret. | Allowed for a bounded pilot; prefer a service account for shared production work. |
173
| Managed ChatGPT device login | An authenticated operator explicitly connects a ChatGPT account and lets Codex own refresh and persistence | Preserve the account's updated `auth.json` across restarts under a single-writer lease. | Preferred operator-linked pilot path. |
174
| Platform API key | Usage-based Codex work that does not need ChatGPT plan limits or workspace identity | Use a scoped secret or existing inference grant. | Supported fallback. |
175
| Browser callback login | Interactive local clients where the browser can return to a localhost callback | Requires the app-server callback listener. | Do not use for the hosted admin interface; the device flow is less brittle. |
176
| Experimental external ChatGPT tokens | A host that already owns the complete ChatGPT token lifecycle | Host-managed access-token refresh. | Refused for the first implementation. |
177
178
[Codex access tokens](https://learn.chatgpt.com/docs/enterprise/access-tokens)
179
are available for ChatGPT Business and Enterprise workspaces. OpenAI documents
180
them for trusted non-interactive local workflows, including app-server-based
181
automation. [Service accounts](https://learn.chatgpt.com/docs/enterprise/service-accounts)
182
provide non-human workspace identities on eligible pay-as-you-go plans and
183
require Codex CLI `0.142.0` or later.
184
185
If a Platform API key meets the requirement, prefer it over connecting a human
186
ChatGPT account. Use a ChatGPT credential only when the SCV needs ChatGPT
187
workspace attribution, entitlements, limits, or governance.
188
189
### Production preference
190
191
Use one ChatGPT service account per distinct SCV authority domain, not one
192
service account per short run and not one employee credential for the entire
193
company. Examples of distinct domains include staging source maintenance and
194
production source maintenance. Give each service account only the workspace
195
roles, groups, plugins, and connections required for that domain.
196
197
Create a finite-lived Codex-scoped access token and inject it as
198
`CODEX_ACCESS_TOKEN` only into the credential-bearing Codex runtime. OpenAI
199
documents that the same variable works for app-server. Do not persist the token
200
with `codex login --with-access-token` on an ephemeral worker, and do not reuse
201
it as a client-to-app-server transport token.
202
203
Use this rotation sequence:
204
205
1. Mark the account runtime as draining so it receives no new SCV runs.
206
2. Create a replacement token and store it as a new secret revision.
207
3. Start a fresh account runtime with the new revision.
208
4. Run an account-read and bounded read-only SCV smoke test.
209
5. Route new work to the fresh runtime.
210
6. Let old work finish or cancel it according to policy.
211
7. Revoke the old token and destroy the old runtime.
212
213
### Device-code connection flow
214
215
Use device code when a site administrator or operator explicitly connects a
216
ChatGPT account through OpenAgents. Device login must be enabled in the user's
217
ChatGPT security settings or by the ChatGPT workspace administrator.
218
219
1. Require an authenticated OpenAgents operator session with account-management
220
   authority and recent reauthentication.
221
2. Create a pending Codex account slot and a temporary, isolated account runtime
222
   with an empty `CODEX_HOME`.
223
3. Initialize app-server, then send `account/login/start` with
224
   `{ "type": "chatgptDeviceCode" }`.
225
4. Bind the returned `loginId`, `verificationUrl`, and `userCode` to the
226
   requesting operator session. Accept only the expected OpenAI verification
227
   origin.
228
5. Display the verification URL, one-time code, expiration, account slot, and
229
   this warning: Continue only if you started this Codex connection from this
230
   OpenAgents screen. Cancel if another site or person supplied the code.
231
6. Wait for the matching `account/login/completed` notification. Do not ask the
232
   browser to return an OAuth token to OpenAgents.
233
7. On success, call `account/read` and `account/rateLimits/read`. Record the
234
   returned account type, plan, operator-visible email, and initial health
235
   without copying access tokens into PostgreSQL.
236
8. Stop the temporary runtime, move the resulting credential home into the
237
   account's encrypted persistent store under a single-writer generation, then
238
   start its normal account runtime.
239
9. Delete the one-time code and temporary login record after success, failure,
240
   cancellation, or expiration.
241
242
The local Codex implementation polls for up to 15 minutes. Treat that value as
243
version-specific and store the actual UI expiration separately from the durable
244
account. A new login in the same app-server process cancels the prior active
245
login, so use one temporary process per pending connection attempt.
246
247
Do not expose device connection on a public or visitor route. Apply CSRF
248
protection, rate limits, audit logging, and an allowlist of operators. Never log
249
the user code, authentication notifications, access token, refresh token, or
250
raw `auth.json`.
251
252
### Managed credential persistence
253
254
Codex-managed ChatGPT login refreshes its tokens and writes updated state to
255
`auth.json`. The official [advanced CI/CD auth guide](https://learn.chatgpt.com/docs/auth/ci-cd-auth)
256
requires one serialized user of an auth file and preservation of the refreshed
257
file. An old bootstrap copy cannot safely overwrite the refreshed copy.
258
259
Use one of these storage patterns:
260
261
- Prefer a long-lived encrypted account volume with one account runtime as its
262
  only writer.
263
- If workers are ephemeral, restore one encrypted account blob under a database
264
  lease, run exactly one account runtime, and write the refreshed file back
265
  with a compare-and-swap generation before releasing the lease.
266
- If the refreshed file cannot be acknowledged durably, mark the account
267
  `reauthentication_required` and stop assigning work. Do not continue from a
268
  potentially stale seed.
269
270
Use `cli_auth_credentials_store = "file"` only inside this isolated account
271
home. Codex documents that `auth.json` contains access tokens and must be
272
treated like a password. A generic container keyring is not a durable
273
multi-worker account store.
274
275
## Multiple connected accounts
276
277
### Isolation rule
278
279
Use one account runtime per connected credential because the inspected Codex
280
process constructs one shared `AuthManager` and gives it to the process-wide
281
thread manager. `account/login/start`, `account/logout`, external authentication,
282
and file reloads change that shared snapshot. Switching it while several
283
threads run can change which identity later requests use.
284
285
Each account runtime needs distinct values for:
286
287
- `CODEX_HOME` and `CODEX_SQLITE_HOME`;
288
- authentication storage and secret revisions;
289
- configuration, logs, sessions, skills, plugin state, and MCP credentials;
290
- process user, PID namespace, temporary directory, and cache;
291
- SCV account-runtime ID, generation, health, and capacity;
292
- rate-limit and usage snapshots.
293
294
Never mount one account's Codex home into another account runtime. Never copy a
295
thread history between account homes. Never log out, log in, or replace the
296
credential on a runtime with active work.
297
298
Start with account capacity `1`. Codex can host several threads in one process,
299
but serialized capacity makes account pinning, refresh writes, cancellation,
300
and recovery auditable. Increase capacity only after tests prove that
301
concurrent threads preserve event routing, resource bounds, approval routing,
302
and account attribution.
303
304
### Account record
305
306
Plan for a durable `scv_driver_accounts` record with at least:
307
308
- opaque account ID, driver `codex_app_server`, label, and environment;
309
- credential kind and secret or encrypted-home reference;
310
- credential revision and storage generation;
311
- ChatGPT workspace account ID when available;
312
- operator-visible email and plan, stored as account metadata rather than run
313
  output;
314
- allowed repositories, SCV classes, and risk classes;
315
- capacity, weight, drain state, and disabled state;
316
- `connected`, `ready`, `degraded`, `rate_limited`, `draining`, `disabled`,
317
  `reauthentication_required`, and `revoked` lifecycle states;
318
- last verification, last successful run, last failure, and last rate-limit
319
  update;
320
- creating operator, rotating operator, and immutable audit refs.
321
322
Store secret references, not secret values, in PostgreSQL. Store only the
323
opaque account-runtime ID on ordinary SCV events. Keep email and workspace
324
metadata out of the public SCV stream.
325
326
### Account selection
327
328
Select an account before an SCV execution claim becomes runnable. The scheduler
329
should require all of these conditions:
330
331
- The account is enabled, ready, and authorized for the repository and SCV
332
  class.
333
- Its credential revision is active and its runtime generation is healthy.
334
- Its capacity has a free lease.
335
- Its current Codex model catalog contains the admitted model.
336
- Its rate-limit snapshot leaves the configured reserve.
337
- Its workspace and data-handling policy match the work item.
338
339
Pin the account for the complete SCV run. Do not move a live thread to another
340
account after a `401`, quota event, or model error. Pause or fail the run,
341
release its workspace according to policy, and require a new execution
342
generation if retry is safe.
343
344
Do not rotate across accounts to evade rate limits or contractual restrictions.
345
Multiple-account support exists to separate owners, workspaces, environments,
346
and capacity. It is not a quota-bypass mechanism.
347
348
## Runtime and secret isolation
349
350
### Required topology
351
352
The target topology separates the SCV control plane, Codex credential runtime,
353
and candidate execution:
354
355
```text
356
Phoenix and durable SCV coordinator
357
                |
358
                | authenticated worker protocol
359
                v
360
SCV worker supervisor
361
       |                          |
362
       | private JSONL            | typed, policy-checked effects
363
       v                          v
364
Codex credential compartment   candidate execution compartment
365
- codex app-server             - exact Forge checkout
366
- one account credential       - no CODEX_HOME
367
- private CODEX_HOME           - no provider credential
368
- OpenAI egress only           - bounded command and file tools
369
- no Forge operator token      - restricted network and cgroup
370
```
371
372
Run app-server on the same trusted worker as its Elixir supervisor and use
373
standard input and output. Do not expose a TCP port. A Unix socket is an
374
acceptable later local transport when several local clients need one runtime.
375
Do not use remote WebSocket transport for the first production version; OpenAI
376
labels it experimental and unsupported. If it is ever admitted, use TLS and a
377
dedicated capability or signed bearer token that is unrelated to the Codex
378
account credential.
379
380
### Credential exposure blocker
381
382
Codex app-server needs its ChatGPT or API credential while model-selected tools
383
can start child processes. The current Codex default shell environment policy
384
inherits all variables and, by default, does not remove names containing
385
`KEY`, `SECRET`, or `TOKEN`. Therefore, setting `CODEX_ACCESS_TOKEN` on an
386
app-server process without an explicit environment policy can expose it to a
387
model-reachable command.
388
389
For every Codex-backed SCV:
390
391
- Set shell environment inheritance to `core` or `none`.
392
- Set `shell_environment_policy.ignore_default_excludes = false`.
393
- Add explicit deny patterns for `CODEX_ACCESS_TOKEN`, OpenAI keys, cloud
394
  credentials, Forge credentials, database URLs, release cookies, and every
395
  worker control secret.
396
- Never allow repository configuration to override the host-owned environment
397
  policy.
398
- Deny reads of the account `CODEX_HOME`, secret mounts, worker control
399
  sockets, and host process metadata.
400
- Put candidate commands in a separate user, mount, PID, and network namespace
401
  so they cannot read the app-server process environment through `/proc`.
402
- Give the candidate compartment no OpenAI egress and no route to the account
403
  credential store.
404
- Run a canary test that searches environment variables, filesystem paths,
405
  process metadata, crash reports, and logs for a synthetic credential.
406
407
Environment filtering alone is not a security boundary. A same-user child may
408
read a parent process or a credential file even when the value is absent from
409
its immediate environment. The production gate requires operating-system or
410
remote-execution isolation.
411
412
Codex currently has evolving remote environment, permission-profile, dynamic
413
tool, and code-mode host surfaces that may help implement this split. Several
414
are experimental in the inspected revision. Do not base production authority
415
on them until their stable contract and failure behavior are proven. The
416
alternative is an SCV-owned effect sidecar that disables native command and
417
file effects and exposes only typed, durably persisted tools.
418
419
Until that boundary exists, allow only trusted-repository, read-only,
420
propose-only qualification with a disposable or narrowly scoped credential.
421
Do not execute candidate build scripts, dependency hooks, tests, or generated
422
binaries in the credential compartment.
423
424
## App-server lifecycle
425
426
### Process startup
427
428
For one admitted account runtime:
429
430
1. Resolve a digest-pinned worker image and Codex binary or stable SDK release.
431
2. Materialize the account secret or credential home into its isolated
432
   compartment.
433
3. Generate host-owned Codex configuration. Ignore repository-controlled user
434
   configuration for authentication, shell environment, sandbox, network,
435
   telemetry, plugins, and MCP servers.
436
4. Start `codex app-server --listen stdio://` under the SCV worker supervisor.
437
5. Send `initialize` once with a stable client name, title, and version, then
438
   send `initialized`.
439
6. Call `account/read`, `model/list`, and, for ChatGPT-backed accounts,
440
   `account/rateLimits/read` before marking the runtime ready.
441
7. Record the Codex binary digest, CLI version, generated protocol-schema
442
   digest, configuration digest, credential revision, and account-runtime
443
   generation.
444
445
Use `openagents_scv` as the proposed `clientInfo.name`. OpenAI asks enterprise
446
integrations to register a known client name for compliance logs. Contact
447
OpenAI before production enterprise use and record the accepted identifier.
448
449
Keep `initialize.capabilities.experimentalApi` disabled in the first version.
450
Enable individual experimental features only in a separately admitted driver
451
revision with protocol fixtures and downgrade behavior.
452
453
### SCV run sequence
454
455
1. Claim one account-runtime capacity lease and bind it to the SCV run
456
   generation.
457
2. Prepare an exact-SHA disposable workspace outside `CODEX_HOME`.
458
3. Call `thread/start` with the exact `cwd`, admitted model, explicit sandbox,
459
   explicit approval policy, and host-owned developer instructions.
460
4. Store the returned Codex thread ID before starting the first turn.
461
5. Call `turn/start` with the bounded SCV objective, `low` or `none` reasoning,
462
   and an `openagents.scv.report.v1` output schema.
463
6. Persist and project every admitted notification while the turn runs. Handle
464
   server-initiated approval requests synchronously and fail closed.
465
7. On `turn/completed`, persist the final report, usage, changed-file evidence,
466
   event artifact, and terminal turn status before acknowledging the execution
467
   as successful.
468
8. Inspect the workspace independently. Codex output is evidence, not proof of
469
   its filesystem state.
470
9. Archive or retain the thread according to the SCV retention policy, release
471
   the account capacity, and destroy the disposable workspace.
472
473
Use `gpt-5.6-luna` with `low` reasoning by default. Allow `none` for a measured
474
latency-sensitive workload. Call `model/list` at runtime and refuse the claim if
475
the connected account cannot use the admitted model. Do not silently fall back
476
to GPT-5.4 or another model family.
477
478
Never call `thread/shellCommand` from the SCV driver. App-server documents that
479
this operation runs outside the thread sandbox.
480
481
### Recovery
482
483
Standard input and output provide one ordered, process-local control channel.
484
If the channel closes, treat the app-server process as failed. Preserve the SCV
485
run and account lease long enough to determine the recovery action.
486
487
For a durable thread, restart the same pinned runtime with the same account
488
home, initialize it, call `thread/read`, and use `thread/resume` only when the
489
persisted history and exact workspace generation still match. Do not resend a
490
turn merely because its terminal notification was lost.
491
492
If a command or file effect may have occurred but the outer SCV step has no
493
durable effect receipt, mark the step uncertain and discard or quarantine the
494
workspace. App-server history can help investigation, but it cannot replace a
495
pre-effect durable SCV record.
496
497
Do not share one managed `auth.json` between concurrently recovering processes.
498
The account-runtime generation fence must ensure that only one process can
499
refresh and write the credential home.
500
501
## Events and the public SCV stream
502
503
App-server already provides the live visibility required by an SCV. Normalize
504
its JSON-RPC notifications into the common `openagents.scv.event.v1` envelope
505
instead of exposing raw Codex payloads to `/status`.
506
507
| Codex signal | SCV event | Public projection |
508
| --- | --- | --- |
509
| Process start and `initialize` response | `driver_started` | SCV label, driver, runtime version, and start time |
510
| `thread/started` | `driver_session_started` | SCV phase and bounded session ID suffix |
511
| `turn/started` | `run_started` or `turn_started` | Objective summary, admitted model, reasoning effort, and elapsed time |
512
| `item/started` | `activity_started` | Normalized activity kind such as reading, searching, editing, testing, or reviewing |
513
| `item/agentMessage/delta` | `message_delta` | Bounded sanitized text suitable for the SCV stream |
514
| Reasoning deltas and raw model internals | Private diagnostic event | Do not publish raw reasoning. Show a normalized phase or summary only. |
515
| Command and file-change items | `tool_started`, `tool_progress`, and `tool_completed` | Sanitized command category, relative path, duration, status, and output byte count; omit raw secrets and unbounded output |
516
| Approval request | `approval_pending` | Reason, bounded action summary, and operator action state |
517
| `serverRequest/resolved` | `approval_resolved` | Decision, decision source, and resolution time |
518
| Token-usage updates | `usage_updated` | Input, cached input, output, reasoning, and total tokens when available |
519
| Rate-limit update | `account_capacity_updated` | Account-runtime health and available-capacity class; do not publish account identity |
520
| `turn/completed` | `turn_finished` | Terminal status, duration, report availability, changes, tests, and usage |
521
| Process exit | `driver_finished` | Exit classification, restart count, and terminal receipt status |
522
523
Persist a bounded private copy of raw protocol events for debugging and schema
524
replay. Store large command output, diffs, and traces as digest-addressed
525
artifacts. The public stream must use a normalized, redacted projection with
526
rate limits and byte limits.
527
528
Preserve unknown notification methods as bounded private events, increment a
529
schema-mismatch metric, and continue only when the unknown event is
530
observational. Fail closed on an unknown server-initiated request because it
531
may require a security decision.
532
533
The terminal report must be durable. A run is not successful merely because
534
Codex exited with `0` or emitted `turn/completed`. Persist and acknowledge the
535
bounded report, event artifact, workspace inspection, and their digests before
536
the SCV coordinator advances the work item.
537
538
## Approval handling
539
540
App-server sends command, file-change, permission, user-input, and MCP
541
elicitation requests from server to client. The Elixir client must route each
542
request by JSON-RPC ID and answer within a bounded deadline.
543
544
Use these rules:
545
546
- Default to decline or cancel for an unknown request, malformed payload,
547
  expired SCV lease, stale generation, disconnected operator, or policy error.
548
- Evaluate the SCV authority envelope before presenting or approving an action.
549
- Allow session-scoped approvals only when the SCV policy explicitly permits
550
  the exact reusable scope.
551
- Persist the request and decision before responding when the action can create
552
  an external effect.
553
- Pause the SCV and show the request to an operator when policy requires human
554
  review.
555
- Record the decision source as host policy, operator, Codex automatic review,
556
  or refusal.
557
- Do not let Codex automatic approval review widen the SCV's sandbox,
558
  repository, network, or Forge authority.
559
560
If the Python SDK becomes the implementation layer, always pass a custom
561
approval handler. Its inspected default handler accepts command and file-change
562
requests. That behavior is inappropriate for an SCV and must never reach
563
production configuration.
564
565
## Rate limits, usage, and scheduling
566
567
Use `account/rateLimits/read` and `account/rateLimits/updated` to maintain a
568
bounded account-capacity projection. Keep the backward-compatible primary
569
bucket and any `rateLimitsByLimitId` buckets. Record used percentage, window,
570
reset time, reached type, and plan without treating an absent field as zero.
571
572
Use turn token-usage notifications and `account/usage/read` as complementary
573
evidence:
574
575
- Turn usage binds tokens to one SCV run and remains the primary run ledger.
576
- Account usage reconciles lifetime and daily activity for ChatGPT-backed
577
  accounts.
578
- Host measurements record process CPU, memory, disk, event lag, and wall time.
579
- Provider or workspace billing remains external authority for charged usage.
580
581
The account scheduler should keep a reserve instead of dispatching until a
582
window reaches 100 percent. On a rate-limit event, stop new claims for that
583
account, let policy decide whether active work may finish, and wake the account
584
at the documented reset time plus jitter. Do not select another account merely
585
to bypass the same limit class.
586
587
Record these Codex-specific measurements:
588
589
- app-server cold-start and initialization time;
590
- account verification and model-list time;
591
- queue time waiting for account capacity;
592
- time to `thread/started`, `turn/started`, first item, first text delta, and
593
  terminal report;
594
- notification count, bytes, ingest lag, dropped projections, and unknown
595
  methods;
596
- command, file-change, approval, and tool counts and durations;
597
- input, cached input, output, and reasoning tokens;
598
- rate-limit snapshots before and after the run;
599
- process RSS, peak memory, CPU, disk, child count, restarts, and exit reason;
600
- recovery attempts, thread resume results, and uncertain effects;
601
- report, event, transcript, diff, and benchmark artifact digests.
602
603
## Version and schema policy
604
605
Pin the Codex runtime by image and binary digest. Do not install `latest` at
606
worker startup. For every admitted version:
607
608
1. Run `codex app-server generate-json-schema --out <directory>`.
609
2. Store the schema bundle and its digest with the SCV driver revision.
610
3. Generate or validate the Elixir request and response fixtures against that
611
   bundle.
612
4. Replay a recorded, redacted event corpus through the normalizer.
613
5. Run login, read-only turn, approval, cancellation, report, restart, and
614
   unknown-message tests.
615
6. Compare the direct Elixir client with the matching stable Python SDK where
616
   the SDK exposes the same operation.
617
7. Promote the version only after the worker image and rollback image both
618
   pass.
619
620
Use stable protocol fields with `experimentalApi` disabled first. App-server
621
schemas are version-specific. Treat a method or field added on the development
622
branch as unavailable until it appears in the pinned released schema.
623
624
## Data retention and privacy
625
626
Codex threads may contain source, prompts, tool output, diffs, and account
627
metadata. Apply the same visibility classification as the SCV work item and
628
repository.
629
630
- Keep account credentials and raw authentication records out of PostgreSQL,
631
  logs, reports, events, and artifacts.
632
- Keep account email, workspace identifiers, and plan details on restricted
633
  account records.
634
- Redact secrets before event persistence, not only in the `/status`
635
  projection.
636
- Bound raw text deltas, command output, and diagnostic logs.
637
- Store transcripts and large outputs in encrypted, digest-addressed artifact
638
  storage with an explicit retention period.
639
- Allow an operator to drain, disconnect, revoke, and delete a connected
640
  account without deleting immutable run receipts required for audit.
641
- On disconnect, cancel pending login attempts, stop new claims, terminate the
642
  account runtime, revoke or delete the credential, and remove its private
643
  Codex home according to retention policy.
644
645
ChatGPT sign-in and API-key sign-in use different OpenAI data-handling and
646
workspace-control policies. Store the credential kind and workspace policy on
647
the account record so the scheduler can prevent a work item from using an
648
incompatible account.
649
650
## Implementation phases
651
652
### Phase 0: Support and policy confirmation
653
654
- Confirm the eligible ChatGPT plan, service-account availability, token
655
  expiration policy, and Codex Local permissions.
656
- Contact OpenAI about the `openagents_scv` client identifier and the supported
657
  app-server or SDK path.
658
- Decide which repositories may use ChatGPT credentials instead of Platform
659
  API keys.
660
- Admit the first Codex runtime and schema digests.
661
662
### Phase 1: Local protocol spike
663
664
- Build a supervised Elixir port client for initialization, account read,
665
  model list, thread start, turn start, notifications, approvals, cancellation,
666
  and process exit.
667
- Use an isolated disposable `CODEX_HOME` and a read-only repository.
668
- Normalize live notifications into `openagents.scv.event.v1` and persist an
669
  `openagents.scv.report.v1` terminal result.
670
- Compare behavior with the stable Python SDK and capture protocol fixtures.
671
672
### Phase 2: Operator account connection
673
674
- Add restricted account and login-attempt records.
675
- Implement the device-code ceremony with one temporary process per attempt.
676
- Add account read, model, rate-limit, health, drain, disconnect, and audit
677
  operations.
678
- Add service-account and personal access-token secret references without
679
  showing saved token values after entry.
680
681
### Phase 3: Account runtime scheduler
682
683
- Add one runtime generation and one capacity lease per account.
684
- Bind every SCV execution to one account, credential revision, Codex version,
685
  schema digest, and thread ID.
686
- Add quota-aware admission, health backoff, restart limits, and reauthentication
687
  state.
688
- Add live public SCV projection and restricted driver diagnostics.
689
690
### Phase 4: Propose-only SCV qualification
691
692
- Run bounded read-only investigation and candidate proposal tasks.
693
- Prove cancellation, report durability, event continuity, exact-SHA binding,
694
  resource collection, and restart behavior.
695
- Keep repository writes, pushes, Forge promotion, and deployment disabled.
696
697
### Phase 5: Credential-free effect execution
698
699
- Separate app-server credentials from candidate command and file effects.
700
- Add a durable pre-effect receipt and idempotency boundary.
701
- Prove that a synthetic credential cannot be read from environment,
702
  filesystem, process metadata, sockets, logs, or artifacts.
703
- Run adversarial repository instructions and build scripts with network denied.
704
705
### Phase 6: Bounded write admission
706
707
- Enable only a repository-scoped propose branch and admitted path and command
708
  policy.
709
- Require exact-SHA tests, event and report artifacts, independent workspace
710
  inspection, and human promotion.
711
- Consider staging autonomy only after the general SCV and Forge gates in
712
  [SCV planning](scv-planning.md) pass.
713
714
## Qualification checklist
715
716
Do not call the Codex-backed driver ready until it proves all of these items:
717
718
- Two separately connected accounts run in different Codex homes and cannot
719
  see each other's identity, threads, history, credentials, plugins, or MCP
720
  state.
721
- A new device login cannot cancel another account's pending connection.
722
- Service-account token rotation drains the old generation without changing a
723
  running thread's identity.
724
- `gpt-5.6-luna` with `low` or `none` reasoning is verified through
725
  `model/list`; an unavailable model fails closed.
726
- A user can see bounded live SCV activity during the run.
727
- The complete final SCV report and event artifact survive app-server exit.
728
- Cancellation stops the turn, descendants, and account capacity lease.
729
- Restart recovery never repeats an uncertain command or file effect.
730
- Approval requests route to the correct SCV, turn, operator, and generation.
731
- Unknown server requests fail closed.
732
- Rate-limit updates stop new claims and never trigger quota-evasion routing.
733
- A synthetic credential is absent from command environments, filesystem
734
  reads, `/proc`, diagnostics, crash output, transcripts, and artifacts.
735
- Candidate code has no OpenAI, Forge operator, production database, release,
736
  or cloud credential.
737
- The pinned schema, stable Python SDK comparison, runtime digest, rollback
738
  image, and support decision are recorded.
739
740
## Open questions
741
742
- Will OpenAI support `openagents_scv` as a direct app-server client, or should
743
  production use the stable Python SDK bridge?
744
- Which ChatGPT workspace and pay-as-you-go plan will own the production SCV
745
  service account?
746
- Should staging and production use separate service accounts, separate
747
  workspaces, or both?
748
- Which encrypted persistent store will hold managed device-login homes with a
749
  single-writer compare-and-swap contract?
750
- Which stable Codex execution surface will provide the credential-free effect
751
  compartment: a remote environment, a code-mode host, dynamic tools, or an
752
  SCV-owned sidecar?
753
- What private transcript retention period satisfies repository and workspace
754
  policy?
755
- What account reserve and maximum concurrency should benchmarks admit?
756
757
These questions block production authority, but they do not block a local,
758
read-only protocol and device-login spike.

This page updates live while a promote is in flight · changelog