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
36 changes: 36 additions & 0 deletions .github/workflows/codex-steering-experiment.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: Codex steering experiment

on:
pull_request:
paths:
- 'experiments/codex_steering/**'
- '.github/workflows/codex-steering-experiment.yml'
workflow_dispatch:

permissions:
contents: read

jobs:
fixture-tests:
name: Fixtures (${{ matrix.os }}, Node ${{ matrix.node }})
runs-on: ${{ matrix.os }}
timeout-minutes: 10
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
node: [22, 24]
defaults:
run:
working-directory: experiments/codex_steering
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: ${{ matrix.node }}
- run: npm ci --ignore-scripts
- run: npm run typecheck
- run: npm test
env:
TASK_TEMP: ${{ runner.temp }}/codex-steering-fixtures
- run: npm audit
92 changes: 92 additions & 0 deletions experiments/codex_steering/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Opt-in Codex steering experiment

This is an unreleased, local TypeScript experiment for issue #141. It is not an
installed `session-peer` command and is excluded from the npm package. Ordinary
queue delivery, runtime behavior, SSH, and the Side Session experiment remain
unchanged.

## Purpose and limits

The prototype connects to an operator-selected, private Unix-domain WebSocket
of an existing Codex app-server. It submits user text using `turn/steer` with a
full thread UUID and the operator's exact `expectedTurnId`. It does not start a
server or session. The exact advertised server version candidate is **0.159.2**,
drawn from the Python experiment; that is not a qualification of native Codex
0.159.2 for this TypeScript implementation or of newer Codex versions.

Opt-in, absolute socket/home paths, and an exact home match are required. Socket
ownership/privacy and canonical home-path checks are local guards. They do not establish atomic
owner generation or exclude same-user endpoint impersonation. Loaded target,
active status, direct-input capability, and a thread-specific feature snapshot
are inspected. These snapshots can race with state changes. The actual
`expectedTurnId` guard is applied by native `turn/steer`, not by a dry-run.

`--require-instant` requires an affirmative `instant_interrupt` snapshot.
Disabled or unknown evidence is refused rather than treated as enabled. Without
that option, unknown/disabled feature evidence does not imply instant input
handling. The sender never enables a feature or overrides approvals or target
permissions.

A metadata-only dry-run performs no `turn/steer` call and does not prove that the
expected turn matches. An `accepted` result means that the native endpoint accepted
the input; it does not mean consumption, ACK, completion, or termination of
running tools. A refusal and an uncertain post-attempt outcome are distinct.
Neither causes queue fallback, retry, or resend. The prototype never calls
queue-add, `turn/start`, `turn/interrupt`, resume, fork, or wake. It does not
answer approval requests.

## Privacy and resource boundary

Supply text through private stdin, not a secret-bearing command line. Results
and diagnostics omit message text, transcript text, and raw server errors.
Bounded incoming notifications are discarded, but may themselves contain
content. `thread/read` with `includeTurns: false` can still return a preview;
this experiment does **not** promise body-free native access. It makes no ACK
collector or receipt capability and offers no generic completion wait.

RPC/frame/input bounds and cleanup cover this experiment's owned connection and
temporary test fixtures. The transport's monotonic deadline starts after CLI
stdin has completed; waiting for interactive stdin is not covered by that deadline.
They do not stop the existing owner or undo native side
effects. Timeout/disconnect after a steering call is not proof of non-delivery
and must not trigger an automatic second submission.

## Isolated usage

Use Node 22 or 24 for the separate experiment package. Never point the example
at an existing user session without an explicit, separate test authorization.
The values below are placeholders; no native test is authorized by this README.

```sh
cd experiments/codex_steering
npm ci --ignore-scripts
npm run typecheck
npm test
printf '%s' 'Change the test review focus' | node --experimental-strip-types steer.ts \
--experimental-steering --socket /private-test/app.sock \
--expected-home /private-test/home \
--to 00000000-0000-4000-8000-000000000001 \
--expected-turn-id turn-example --message - --dry-run \
--require-instant --timeout 10
```

The experiment has its own dependencies, typecheck, and fixture tests. Root
runtime build/package tests do not qualify it. Its CI uses fake Unix sockets on
macOS/Linux; it invokes no native agent, provider, or existing user session and
uses no account credentials.

## Qualification still required

Native queue versus steer with `instant_interrupt` ON/OFF, real-provider input
handling, code-mode behavior, current native versions, owner lifecycle,
generation/authorization, and snapshot/acceptance races remain unqualified.
SSH and Windows are not implemented by this local Unix-socket prototype. Linux
fixture CI is not a native Linux steering test. Python's historical synthetic
native observations must not be presented as TypeScript test results. See
[RESULTS.md](RESULTS.md) for the exact executed scope.

References:

- [Official App Server: steer an active turn](https://learn.chatgpt.com/docs/app-server#steer-an-active-turn)
- [Python issue #247](https://github.com/abruption/session-peer/issues/247)
- [Python Draft PR #248](https://github.com/abruption/session-peer/pull/248), reference commit `6ebedde` (historical experimental evidence, not TS qualification)
62 changes: 62 additions & 0 deletions experiments/codex_steering/RESULTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# TypeScript Codex steering experiment results

## Status

Unreleased experiment. Local checks below ran on 2026-10-12 KST on macOS
arm64, Node 24.16.0/npm 11.15.0. Current-head CI results are recorded in the
Draft PR rather than assumed from these local results. No native Codex, real provider, or existing user session was invoked
for this initial record. There is no merge, release, or general-support claim.

## Evidence categories

| Check | Current evidence |
| --- | --- |
| Separate package typecheck and fake-socket tests | PASS: strict TypeScript; 24 tests passed, 0 failed/skipped |
| macOS/Linux Node 22/24 fixture CI | Pending execution/report |
| Root npm artifact exclusion | PASS: npm pack dry-run lists 57 files, none under experiments/ |
| Actual native Codex 0.159.2 | Not run; exact-version candidate only |
| Native queue/steer × instant_interrupt ON/OFF | Not run |
| Real provider or model | Not run |
| Existing user session or live message | Not run |
| Owner-generation, restart, same-user impersonation resistance | Not established |
| Native Linux/Windows or SSH steering | Not qualified |
| Consumption, ACK, completion, running-tool cancellation | Not established |

Tests using fake sockets can establish request construction, local guards,
bounded parsing, refusal/uncertainty classification, and owned-connection cleanup.
They cannot establish native acceptance, actual active-turn handling, or
instant-input latency. Metadata-only dry-run is not an expected-turn proof.

## Remaining gates

1. Independent current-head review and completed fixture/platform CI.
2. Explicit authorization and isolated native tests before any actual steering.
3. Native ON/OFF ordering and real-provider/code-mode behavior; no latency SLA.
4. Safe owner endpoint discovery/generation, authorization, and lifecycle.
5. Submission races, interruption/disconnect after acceptance, and concurrency.
6. Any SSH/Windows design, stable installed CLI contract, packaging, and release
require separate implementation and approval.

The Python experiment at reference commit `6ebedde` reports historical native
observations for its own harness. Those results are not copied into the TS
counts or treated as proof that this implementation works against a native
server. No automatic fallback/resubmission is permitted after an attempted
effect, including when observation is incomplete.

## Local commands and checks

- In the experiment package: `npm ci --ignore-scripts`, `npm run typecheck`,
`npm test`, `npm audit` (zero vulnerabilities; ws 8.22.0).
- In the root package: `npm run build`, `node scripts/check-repository.mjs`,
`npm pack --ignore-scripts --dry-run --json`; inspected all packaged paths.
- Fake tests cover private socket guards, exact request/turn matching, dry-run,
active/direct/home/feature guards, malformed wire/UTF-8/binary/server requests,
disconnect and post-attempt timeout, concurrent/repeated attempt fencing,
cursor cycles, event flood, frame limits, and fixture cleanup.
- Read-only internal adapter review found no blocker for the Draft scope.
This is not native qualification or an external model review.

The root installed runtime, lockfile, version, parity pins, and existing
workflows are unchanged. A separate fake-only experiment workflow was added.
The input byte limit includes UTF-8 bytes; interactive stdin has no elapsed-time
bound. The transport's total deadline starts after stdin completes.
82 changes: 82 additions & 0 deletions experiments/codex_steering/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 18 additions & 0 deletions experiments/codex_steering/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"name": "session-peer-ts-steering-experiment",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"test": "node --experimental-strip-types --test test_steer.ts",
"typecheck": "tsc --noEmit --strict --module NodeNext --target ES2022 --allowImportingTsExtensions --skipLibCheck steer.ts test_steer.ts"
},
"dependencies": {
"ws": "8.22.0"
},
"devDependencies": {
"@types/node": "22.13.10",
"@types/ws": "8.18.2",
"typescript": "5.9.3"
}
}
Loading
Loading