Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,15 @@ jobs:
run: python -m unittest discover -s tests/codex -t . -v
- name: Distribution source tests
run: python -m unittest discover -s tests/distribution -t . -v
- name: Offline Codex experiments (no native model calls)
if: matrix.os != 'windows-latest' && matrix.python == '3.13'
shell: bash
run: |
for suite in experiments/codex_side_session experiments/codex_steering; do
if test -d "$suite"; then
python -X dev -W error::ResourceWarning -m unittest discover -s "$suite" -p 'test_*.py' -v
fi
done
- name: Complete default discovery contract
if: matrix.os == 'ubuntu-latest' && matrix.python == '3.13'
run: python -m unittest discover -s tests -v
Expand Down
78 changes: 78 additions & 0 deletions experiments/codex_side_session/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Codex live side-session experiment (#245)

Not installed or exposed by the canonical `session-peer` CLI. Existing package
rules exclude `experiments/`. No relay, SSH, native queue, generated runtime,
installation or policy changes are made by this prototype.

This local Unix-domain-socket adapter connects only to an existing owning
Codex app-server. It never starts/resumes/forks a target. A private socket
directory and explicit experimental flag are required. The adapter uses
WebSocket-over-Unix sockets (including Codex's owned rendezvous symlink),
requires `websockets` (tested with 17.1), and gates server version to 0.159.2.
The required absolute `--expected-home` must match the canonical `codexHome`
reported by that server before inventory or input. This metadata comparison
does not establish a server generation, atomic storage identity or side-UI lifetime.
`list` reports loaded
ephemeral threads as **unclassified**, not necessarily `/btw` conversations.
For a real `/btw` test, establish its origin separately using the dedicated TUI.

Malformed RPC envelopes, error fields, deeply nested JSON and invalid discovery
metadata are rejected with fixed diagnostics. Once `turn/start` has been
attempted, an invalid response is `unknown`, with no automatic retry. An ordinary
connection-close error preserves an already validated submission or the original
unknown outcome; it does not establish non-submission. These boundaries are
covered by offline fake-transport tests, not a new native-version acceptance run.

Example (dummy paths/UUID):

```sh
python3 experiments/codex_side_session/side_session.py \
--experimental-side-session --socket /private-test/app.sock \
--expected-home /private-test/home list
python3 experiments/codex_side_session/side_session.py \
--experimental-side-session --socket /private-test/app.sock \
--expected-home /private-test/home send \
--to codex:00000000-0000-4000-8000-000000000001 \
--exclusive-test-session --dry-run --message 'test input'
```

## Critical limitation

**Use only a dedicated test session with a single input producer.** Codex
0.159.2 `turn/start` calls `start_or_steer_turn`; an idle preflight is not an
atomic idle-only guard. Concurrent input can turn a intended new turn into
steering. `--exclusive-test-session` acknowledges this restriction; it does
not enforce exclusivity against other native clients. General use remains
blocked until safe native semantics or a cooperating owner can enforce it.

**Loaded is not UI-open.** After the native TUI dismisses `/btw`, app-server
may retain the ephemeral thread as loaded and idle during its unload grace
period. The live test reproduced this: a dry-run remained validated after the
side UI closed. No further input was sent. A future integration needs an
owner-provided active registration/generation, not merely a loaded-ID check.

No automatic steering, retries, parent fallback, approval responses, permission
overrides, or persistence of a side transcript. Acceptance is not ACK. The
dedicated live test may read its own TUI output to verify an exact response,
but this does not implement general wait/completion detection.

## Review-ready experiment scope

The offline suite runs explicitly in the Linux/macOS Python 3.13 CI jobs. It
uses fake filesystem and RPC responses, including private-endpoint, server-home,
version, approval-request and idle/start rejection cases. It never launches Codex
or sends a model request. The historical native evidence in `RESULTS.md` predates
the required home comparison and is not a rerun of the current adapter.

Ready for code review means this bounded experiment can be reviewed with those
limitations visible. General `/btw` support still requires the atomic submission
and owner-lifecycle contracts described above; issue #245 remains open.

Source review: session-peer base 90489fc; Codex CLI 0.159.2/upstream ff6aec9.
See [RESULTS.md](RESULTS.md) for actual roundtrip evidence and unmet gates.

Unit tests:

```sh
python3 -m unittest discover -s experiments/codex_side_session -v
```
94 changes: 94 additions & 0 deletions experiments/codex_side_session/RESULTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# Side Session experiment results — 2026-09-30

## Verdict

**Transport feasibility: PASS (one real request / one exact side-session ACK).**
**General-use safety and complete issue #245 acceptance: NOT PASS.**

This is a local opt-in proof of concept in an isolated worktree based on
session-peer 90489fccc87fe2588a2546aaaf7f285429d2bcfd. Canonical CLI, generated
runtime, packaging configuration, existing agent sessions and hosted services
were not changed. No PR, push, package publication or rollout was performed.

## Actual test, not a simulated fork

- macOS arm64; Codex CLI/server 0.159.2; Python 3.14 environment with websockets
17.1. TUI-selected default model GPT-6.1-Sol/high; no model override.
- A dedicated local app-server exposed a private Unix-domain endpoint. A fresh
native Codex TUI connected to that exact server. The new server/test thread
was configured read-only/on-request; no existing permission settings changed.
- A short parent setup request returned `ACK SIDE245-PARENT-READY-20260930`.
- Native `/btw` was entered once through the dedicated terminal. Its rendered
screen showed `Side from main thread`; the server inventory gained one
ephemeral, idle, direct-input-capable thread. No artificial API fork was
substituted for the real slash command.
- The experimental adapter's exact-target dry-run was validated with
`submitted:false`. It then submitted one new input via `turn/start`, with
no resume, fork, sandbox/model overrides or retry. Result:
`status:accepted`, `submitted:true`, `consumptionConfirmed:false`.
- The rendered native **side** screen displayed the request separately from
the response: `ACK SIDE245-ROUNDTRIP-A-20260930` (about 3 seconds of work,
displayed completion 19:36 KST). This response was actually generated by the
target, not inferred from submission.
- Parent history before/after: one turn, idle, identical serialized-turn hash:
`e08cdef71e4c1c171bee1c793ef1958954ad070020ef001ce02960023c443d82`.
The side request/ACK did not enter the parent transcript.

ACK observation used the dedicated terminal's rendered screen. The adapter did
**not** implement generic ACK waiting, structured Reply-To return routing, or
delivery back into the original working Codex conversation. Acceptance output
correctly remained `consumptionConfirmed:false`; the exact ACK is separate test
evidence. Side history was never exported/persisted by the adapter.

## Controls and limitations

| Check | Outcome |
| --- | --- |
| Actual side discovery, dry-run and one-shot ACK | PASS |
| Parent transcript unchanged | PASS |
| Persistent parent selected by side adapter | Refused before input: `ephemeral_target_required` |
| Native `thread/queue/add` on real side ID | Rejected: JSON-RPC -32600 |
| Target absent from loaded inventory | Unit-test refusal; no fallback |
| Busy target at preflight | Unit-test refusal; actual concurrent race not tested |
| Timeout, malformed response, internal error after attempt | Unit-test unknown, no retry |
| Side dismissed from TUI but retained in server | **Unmet safety gate:** dry-run still validated |

Two production blockers remain:

1. `turn/start` is backed by `start_or_steer_turn`. A preflight idle check cannot
atomically prevent steering if another producer submits in between. The
exclusive-test flag acknowledges a single-producer requirement, but does not
enforce it against a native client. No adversarial concurrency test was run.
2. UI dismissal is not immediate server unload. Ctrl+C returned the TUI to its
parent, but the old side ID remained ephemeral/idle in memory, and a dry-run
still validated it. **No further message was sent after this finding.** An
owner-cooperating active-registration/generation mechanism is needed.

`ephemeral` alone does not identify a `/btw` conversation. The experiment knew
its provenance because it controlled the dedicated TUI and observed creation.
Existing Orca-hosted private app servers, SSH, Linux, Windows, busy steering,
multi-client approval handling and normal automatic Reply-To are unverified.

## Verification and cleanup

- 13 scoped unit tests pass with `-X dev -W error::ResourceWarning`; no warnings.
One explicitly models the loaded-but-UI-closed limitation, without sending.
- Native WebSocket-over-Unix initialize/read/start was exercised with the real
server. Native stdio `app-server proxy` was not usable for the initial probe
(no initialize response); direct WebSocket transport was used instead.
- Existing package rules exclude `experiments/`; no artifact is being released.
- The two dedicated Orca terminals were closed, with PTY shutdown confirmed.
- The one newly created persisted parent test thread was deleted through its
owning server only after confirming exact cwd, one turn and unchanged hash.
Existing session records were not deleted. This test record is not retained;
its sanitized outcome is preserved here.
- Final checks: no Orca terminals in this worktree, no process whose command
references the unique test endpoint, no Codex process with this test cwd, and
no open physical test socket. Stale socket/rendezvous/startup-lock files created
by this test were removed by exact path after verifying no open handles. The
empty private scratch directory was removed. Unrelated daemon files and
existing worktrees/processes were preserved; the implementation worktree remains.

References: [App Server](https://learn.chatgpt.com/docs/app-server),
[slash commands](https://learn.chatgpt.com/docs/developer-commands?surface=cli).
Source review is pinned to upstream ff6aec96948b70d94983af2641a6b67c94faeff5.
Loading
Loading