Codex CLI ⇆ OpenMax/CWS channel adapter (Category B) — connects the OpenAI Codex CLI (a bare runtime with no built-in channel) to the OpenMax / CWS platform.
Alpha — published on npm (see the badge above for the current version). P1 MVP (the
/wake+/send bridge, wake queue, SDK-backed CWS bridge) and P2-① (the init/start
onboarding CLI, incl. self-register) are shipped. Backed by the real
@openmaxai/openmax-agent-sdk; verified with a live end-to-end round-trip against
openmax.com.
Two layers, following the shared Bridge + Runtime Adapter architecture:
- Layer 1 · Bridge — uses
@openmaxai/openmax-agent-sdkto speak the CWS protocol over WebSocket (connect / auth / heartbeat / reconnect / sync / message send-recv). - Layer 2 · Runtime Adapter — Codex-specific: a local HTTP server exposing
POST /wake(inject an inbound CWS message into Codex) andPOST /send(relay Codex output back to CWS), driving Codex by spawningcodex app-serverand speaking JSON-RPC over its stdio.
Integration target is the Codex CLI (
codex app-server·turn/start·turn/steer), not the desktop / IDE extension — those lack a stable programmatic injection surface. (Acodex app-server --listen ws://transport exists as of codex-cli 0.144.5 and is tracked as a future option; the shipped adapter uses stdio.) Seedocs/.
npm install -g @openmaxai/codex-openmaxThe adapter is normally onboarded by the OpenMax workspace ("Add Codex agent"), which renders a prompt with the connection material inlined; pasting it into a Codex runs the two commands below with zero interactive input. You can also run them directly.
init accepts either of two mutually-exclusive credential shapes on stdin:
# 1a. Initialize with a provisioned api_key + identity_id (direct).
codex-openmax init --stdin-json <<'ONBOARD'
{
"bff_url": "https://openmax.com",
"ws_url": "wss://openmax.com/ws",
"org_id": "<org id>",
"api_key": "<provisioned agent api key>",
"identity_id": "<provisioned identity id>"
}
ONBOARD
# 1b. …or self-register with an invitation (no pre-provisioned credential needed).
codex-openmax init --stdin-json <<'ONBOARD'
{
"bff_url": "https://openmax.com",
"ws_url": "wss://openmax.com/ws",
"org_id": "<org id>",
"invitation_id": "<invitation id>",
"invitation_token": "<invitation token>"
}
ONBOARD
# 2. Start — connect to CWS and run the adapter (foreground).
codex-openmax startWith the invitation shape, init self-registers a new agent identity
(POST /auth/register/agent), exchanges an identity-only JWT, accepts the invitation with it,
then exchanges an org-scoped JWT — the same self-register → identity-JWT → accept → org-JWT
pattern the platform's default "zylos" agent type already uses. Either way, init exchanges
the org JWT, hydrates the agent's own member info, and writes config.json (mode 0600; the
api_key — direct-supplied or self-minted — is never echoed). start reads that config,
connects the SDK bridge, and serves the adapter until SIGINT/SIGTERM. Requires the codex
binary on PATH. Full field contract + security notes:
docs/onboarding-design.md.
Whether closing your session stops start depends on how it was launched:
- Run directly in a bare interactive terminal: closing that terminal sends
SIGHUPand it exits like any other foreground process. - Launched by an agent/CLI tool that spawns it detached or backgrounded (no controlling tty —
the common case when an onboarding agent runs
startfor you): it keeps running after that agent/CLI session ends.
Either way, to stop it: Ctrl+C the process (or kill its PID) in the terminal it's actually
running in.
If you want codex-openmax to survive a reboot or keep running unattended, run start under
a process manager instead of a bare foreground shell. This is optional and changes system
state (a boot-persistent service) — ask the user before setting it up; don't do it silently as
part of the default init/start flow.
systemd (user service):
mkdir -p ~/.config/systemd/user ~/.codex-openmax
cat > ~/.config/systemd/user/codex-openmax.service <<'UNIT'
[Unit]
Description=codex-openmax adapter
[Service]
WorkingDirectory=%h/.codex-openmax
ExecStart=codex-openmax start
Restart=on-failure
[Install]
WantedBy=default.target
UNIT
systemctl --user daemon-reload
systemctl --user enable --now codex-openmaxTo survive reboot/logout (not just this login session), a user service also needs linger enabled — this is a system-level change (affects this Linux user account beyond the service itself), so confirm with the user before running it:
loginctl enable-linger "$USER"pm2:
pm2 start codex-openmax --name codex-openmax -- start
pm2 save
pm2 startup # prints the command to enable pm2 itself on bootEither way, run codex-openmax init once beforehand so config.json already exists in the
working directory the service starts from.
src/
index.ts # entry: wire Bridge + Adapter, main(bridge)
cli.ts # `codex-openmax init` / `start`
onboarding.ts # init plumbing: JWT exchange, self-hydration, 0600 config write
config.ts # config load/validate
types.ts # /wake /send contract types
bridge/
cws-bridge.ts # CwsBridge interface (+ mock for tests)
sdk-bridge.ts # Layer 1: adapts @openmaxai/openmax-agent-sdk → CwsBridge
adapter/
server.ts # local HTTP: POST /wake, POST /send
codex-client.ts # codex app-server stdio JSON-RPC client (turn tracking, bounded failure)
inject.ts # injection model: turn/steer (active) / turn/start (no-turn); ok:true delivery gate
wake-queue.ts # per-conversation serialization, dedup, backpressure
outbound.ts # capture Codex agentMessage output → /send
invariants.ts # ok:true "truly delivered" + failureClass semantics
test/ # unit + killing regressions + SDK-golden contract conformance
docs/ # architecture, onboarding design, P0 spike findings
scripts/live-roundtrip.ts # manual full-stack live run against real CWS + codex
npm install
npm run typecheck
npm test # includes the type-test gate + SDK contract conformance
npm run buildPublishing is tag-driven: a v* tag on a commit contained in main triggers the release
workflow (npm publish with provenance). See .github/workflows/release.yml.