Skip to content

Commit ff5bc15

Browse files
samxu01claude
andcommitted
feat(adr-005): Stage 2 — codex CLI + @commonly/cli in the gateway pod (#236)
Adds a `codex-tools-installer` init container to the clawdbot-gateway deployment that installs `@openai/codex` (pinned via `agents.clawdbot.codexTools.codexVersion`) and the `@commonly/cli` (installed from this repo's `cli/` subdirectory at the pinned git ref) into a shared `/tools` volume the main container mounts read-only with `/tools/bin` prepended to PATH. This is the bridge ADR-005 Stage 2 needs so dev agents (theo / nova / pixel / ops) can eventually mention `@codex` instead of calling `acpx_run`. The wrapper itself shipped in PR #231; this PR puts the substrate where it can run. Why an init container, not a Dockerfile change: `_external/clawdbot` is a submodule on the openclaw fork; touching its Dockerfile would require a fork PR + a submodule pointer bump. The init container path lives entirely in the commonly chart and ships with this deploy. Why @commonly/cli installs from source: it's not on npm yet (ADR-005 Phase 4 publication hasn't shipped). The cli/ subdirectory is a self-contained ~200KB package with one runtime dep. The init container apt-installs git, clones this repo at the pinned ref, copies cli/ into /tools/lib/commonly-cli, runs npm install --omit=dev, and symlinks the bin. Pin the ref to a SHA or tag in values.yaml when stability matters. Soft-fail: if the npm registry or github is unreachable at pod-start time, the wrapper falls through with a warning rather than failing the init container. Gateway routing keeps working (acpx_run continues as fallback); operator can re-trigger on the next pod restart. Hard-failing the gateway pod for a transient outage in one optional capability would strand all agent traffic, which is the wrong trade-off. Auth.json reuse: the existing `clawdbot-auth-seed` init container already provisions chatgpt account-1's codex `auth.json` to `/state/.codex/auth.json`, and the gateway container's `lifecycle.postStart` copies it to `~/.codex/auth.json`. The wrapper reuses that — no new ESO secret. Trade-off: shared quota with the existing acpx_run path. A dedicated codex account for the wrapper is a follow-up if it becomes a bottleneck. Run loop is operator-driven for now (`commonly agent attach codex` + `commonly agent run codex` inside the pod). Auto-start in the container lifecycle is a follow-up after the manual flow validates end-to-end. Runbook at `docs/runbooks/codex-in-gateway-pod.md` covers the operator bootstrap end-to-end and the dev-agent HEARTBEAT cutover plan. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 5f0493c commit ff5bc15

3 files changed

Lines changed: 261 additions & 0 deletions

File tree

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
# Running codex (local-CLI wrapper) inside the gateway pod
2+
3+
ADR-005 Stage 2. The `clawdbot-gateway` pod now ships `codex` and `commonly`
4+
binaries via an init container (`codex-tools-installer`) and a shared
5+
`/tools` volume on `PATH`. This runbook covers the operator steps to wire
6+
a `codex` agent into a pod and start the run loop manually.
7+
8+
This is the bridge that lets dev agents (theo / nova / pixel / ops) replace
9+
their `acpx_run` calls with `@codex` mentions — codex itself runs as a
10+
first-class Commonly agent in a pod the dev agent shares.
11+
12+
## Prerequisites
13+
14+
- Helm release `commonly-dev` includes the chart at or after the commit that
15+
added the `codex-tools-installer` init container (`feat/adr-005-stage-2-codex-image`).
16+
- Codex `auth.json` is already provisioned by the existing `clawdbot-auth-seed`
17+
init container (`/state/.codex/auth.json` → copied to `~/.codex/auth.json`
18+
via the gateway container's `lifecycle.postStart`). No new secret needed —
19+
the wrapper reuses the same chatgpt account #1 the existing acpx_run path
20+
uses.
21+
- Backend image on dev includes `POST /api/agents/runtime/room` and the
22+
agent-room 1:1 enforcement (PR #232 + #235).
23+
24+
## One-time bootstrap
25+
26+
Pick a dev pod or Agent DM where the codex agent should be installed (one
27+
per developer is fine; the wrapper serves multiple sessions per ADR-005's
28+
session-per-pod model).
29+
30+
### 1. Open a shell in the gateway pod
31+
32+
```bash
33+
GATEWAY_POD=$(kubectl get pod -n commonly-dev -l app=clawdbot-gateway -o jsonpath='{.items[0].metadata.name}')
34+
kubectl exec -n commonly-dev -it "$GATEWAY_POD" -- bash
35+
```
36+
37+
### 2. Verify the tools landed
38+
39+
```bash
40+
codex --version # codex-cli 0.125.0 (or whatever is pinned in values)
41+
commonly --version # 0.1.0
42+
```
43+
44+
If either fails, the init container's soft-fail path may have triggered
45+
(npm registry unreachable, github clone failed, etc.) — the gateway is
46+
still up but `/tools` is empty. Check:
47+
48+
```bash
49+
kubectl logs -n commonly-dev "$GATEWAY_POD" -c codex-tools-installer
50+
```
51+
52+
Look for `[codex-tools-installer] install failed` lines. Re-running the
53+
init container = restart the pod (`kubectl delete pod "$GATEWAY_POD"`).
54+
55+
### 3. Authenticate the commonly CLI to api-dev
56+
57+
The wrapper needs a USER token (not the agent runtime token) to call
58+
`commonly agent attach`. Get one from your dev account and save it.
59+
60+
```bash
61+
commonly login --instance https://api-dev.commonly.me --key dev
62+
# enter email + password at the prompts
63+
```
64+
65+
(Inside a non-TTY exec, you'd pipe email/password via stdin — but the
66+
operator step is interactive.)
67+
68+
### 4. Attach the codex agent to a pod
69+
70+
Pick a target pod ID (e.g., a dev-team chat pod or an Agent DM created
71+
via the Agent Hub "Talk to" button). Then:
72+
73+
```bash
74+
commonly agent attach codex \
75+
--pod <podId> \
76+
--name codex \
77+
--instance dev
78+
```
79+
80+
This:
81+
- Registers the codex agent in the kernel (`AgentInstallation` row)
82+
- Mints a runtime token at `~/.commonly/tokens/codex.json`
83+
- Reuses the codex CLI's existing `auth.json` for actual model access
84+
85+
### 5. Start the run loop
86+
87+
```bash
88+
nohup commonly agent run codex > /tmp/commonly-codex-run.log 2>&1 &
89+
```
90+
91+
Or in a tmux session if you want to watch it:
92+
93+
```bash
94+
tmux new -s codex
95+
commonly agent run codex
96+
# Ctrl+b d to detach
97+
```
98+
99+
The run loop polls the instance you passed to `commonly login` (here:
100+
api-dev's URL — your own self-hosted instance is whatever URL you logged
101+
in to), spawns codex on each `chat.mention` / `dm.message`, and posts the
102+
response back to the originating pod. Per ADR-005 §Spawning semantics,
103+
one process serializes spawns — collisions queue, no parallelism.
104+
105+
**Don't run two `commonly agent run codex` processes for the same agent
106+
name.** ADR-005 invariant #4 explicitly calls this out as unsupported in
107+
v1: each `run` would poll, ack, and post independently, producing
108+
duplicate replies. Higher throughput needs a different agent identity
109+
(separate `commonly agent attach codex-2 ...`) — file as a follow-up if
110+
the single-process throughput becomes a real bottleneck.
111+
112+
### 6. Smoke
113+
114+
In the target pod, mention `@codex` from any human or agent member:
115+
116+
```
117+
@codex please reply with the single word: pong
118+
```
119+
120+
Within a minute, codex should post `pong` back. Tail the log to confirm:
121+
122+
```bash
123+
tail -f /tmp/commonly-codex-run.log
124+
```
125+
126+
Expected:
127+
128+
```
129+
[codex] polling https://api-dev.commonly.me for events (ctrl+c to stop)
130+
[codex] [chat.mention] spawning codex
131+
[codex] [chat.mention] posted 4 bytes
132+
```
133+
134+
## After bootstrap — letting dev agents use it
135+
136+
Once `@codex` is live in a dev pod, dev agents (theo / nova / pixel / ops)
137+
can mention it from their HEARTBEAT.md template instead of calling
138+
`acpx_run`. Cutover one agent at a time:
139+
140+
1. Edit the agent's HEARTBEAT.md in `backend/services/registry.js` (the
141+
permanent source of truth — PVC edits get overwritten on
142+
`reprovision-all`).
143+
2. Replace any block that does `acpx_run({ agentId: "codex", ... })` with
144+
`commonly_post_message({ podId, content: "@codex <prompt>" })` plus the
145+
agent's pattern for reading the response on the next heartbeat tick.
146+
3. Run `reprovision-all` so the new HEARTBEAT lands.
147+
4. Watch the agent's next few heartbeats. Compare end-to-end behavior to
148+
the prior `acpx_run` flow.
149+
150+
If the parity holds across one heartbeat cycle, roll out to the next agent.
151+
If it doesn't, revert the HEARTBEAT change (one-line revert) and investigate
152+
before broadening.
153+
154+
## Operational caveats
155+
156+
- **Shared quota.** All `@codex` invocations use the existing chatgpt
157+
account #1's quota — same one the LiteLLM rotator and acpx_run already
158+
consume. Hitting the weekly cap will manifest as `turn.failed` JSONL
159+
events with "usage limit" messages. The codex adapter (`cli/src/lib/adapters/codex.js`)
160+
surfaces these as the agent's reply, so users see a clear error. To
161+
raise the ceiling: add a dedicated codex account for the wrapper (a
162+
follow-up PR — separate auth.json mounted at a non-shared path, run
163+
loop with `CODEX_HOME` env var pointing at it).
164+
- **Pod restart penalty.** The init container reinstalls `@openai/codex` +
165+
`@commonly/cli` from npm on every pod start (~30s). emptyDir is
166+
intentional — simpler than caching; revisit if restart latency hurts.
167+
- **Run loop survives pod restarts only as a manual step.** This runbook
168+
describes a non-daemonized start. A future iteration will move the run
169+
loop into the pod's lifecycle so it auto-starts. Until then, after
170+
`kubectl delete pod` or `helm upgrade`, re-run step 5.
171+
- **Logs go to the pod's filesystem.** `/tmp/commonly-codex-run.log` is
172+
ephemeral; stream to stdout if you want it in `kubectl logs`. Or wire
173+
through to a sidecar fluent-bit later.
174+
175+
## Related
176+
177+
- `cli/src/lib/adapters/codex.js` — the adapter (PR #231)
178+
- `cli/src/commands/agent.js``attach`, `run`, `detach` commands
179+
- ADR-005 §Adapter pattern — invariants the adapter holds
180+
- `_external/clawdbot/extensions/commonly/src/tools.ts` — the `acpx_run`
181+
this is replacing (target for removal once all dev agents are cut over)

k8s/helm/commonly/templates/agents/clawdbot-deployment.yaml

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,61 @@ spec:
464464
readOnly: true
465465
- name: clawdbot-state
466466
mountPath: /state
467+
# ADR-005 Stage 2: install codex CLI + @commonly/cli into a shared
468+
# volume the gateway container mounts. Avoids modifying the
469+
# _external/clawdbot Dockerfile (a submodule on the openclaw fork) —
470+
# the install lives in helm so it ships with the deploy.
471+
#
472+
# `node:22-bookworm-slim` matches the runtime container's Node major
473+
# so any libc-linked binaries are compatible. `git` isn't in slim by
474+
# default; we apt-install it because @commonly/cli isn't published
475+
# to npm yet (ADR-005 Phase 4) and must be installed from source.
476+
#
477+
# Soft-fail: if the npm registry or github is unreachable at pod-
478+
# restart time, the gateway should still start — the run loop is an
479+
# operator-started manual step (Stage 2), and `acpx_run` continues
480+
# to work as a fallback. A hard-failing init container would strand
481+
# the entire gateway pod for an issue with one optional capability.
482+
- name: codex-tools-installer
483+
image: node:22-bookworm-slim
484+
command:
485+
- /bin/sh
486+
- -c
487+
- |
488+
install_codex_tools() {
489+
set -e
490+
export NPM_CONFIG_PREFIX=/tools
491+
apt-get update >/dev/null 2>&1
492+
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
493+
git ca-certificates >/dev/null 2>&1
494+
# Pin codex by version (npm published) — see values.yaml.
495+
npm install --global --no-audit --no-fund \
496+
"@openai/codex@{{ .Values.agents.clawdbot.codexTools.codexVersion | default "0.125.0" }}"
497+
# @commonly/cli — install from source at the pinned ref. The
498+
# cli/ subdirectory is a self-contained npm package with one
499+
# runtime dep (commander); ~200KB of source.
500+
git clone --depth 1 --branch "{{ .Values.agents.clawdbot.codexTools.commonlyCliRef | default "main" }}" \
501+
https://github.com/Team-Commonly/commonly.git /tmp/commonly-src
502+
mkdir -p /tools/lib
503+
cp -r /tmp/commonly-src/cli /tools/lib/commonly-cli
504+
cd /tools/lib/commonly-cli
505+
npm install --omit=dev --no-audit --no-fund
506+
ln -sf /tools/lib/commonly-cli/src/index.js /tools/bin/commonly
507+
chmod +x /tools/lib/commonly-cli/src/index.js
508+
# Verify (best-effort; failures here don't block since the
509+
# outer || already swallowed earlier failures we care about).
510+
/tools/bin/codex --version || true
511+
/tools/bin/commonly --version || true
512+
ls -la /tools/bin/ || true
513+
}
514+
install_codex_tools || {
515+
echo "[codex-tools-installer] install failed — gateway will start without /tools binaries"
516+
echo "[codex-tools-installer] operator: 'commonly agent run codex' is unavailable until next pod restart"
517+
exit 0
518+
}
519+
volumeMounts:
520+
- name: codex-tools
521+
mountPath: /tools
467522
containers:
468523
- name: clawdbot-gateway
469524
image: "{{ .Values.agents.clawdbot.image.repository }}:{{ .Values.agents.clawdbot.image.tag }}"
@@ -485,6 +540,12 @@ spec:
485540
- -c
486541
- mkdir -p /home/node/.codex && cp /state/.codex/auth.json /home/node/.codex/auth.json 2>/dev/null || true
487542
env:
543+
# ADR-005 Stage 2: prepend the codex-tools volume to PATH so
544+
# `codex` and `commonly` are on PATH for operator `kubectl exec`
545+
# invocations and the run-loop process. Order matters — these
546+
# binaries shadow any same-named ones in the openclaw image.
547+
- name: PATH
548+
value: "/tools/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
488549
- name: CLAWDBOT_GATEWAY_PORT
489550
value: {{ .Values.agents.clawdbot.config.gatewayPort | default 18789 | quote }}
490551
- name: CLAWDBOT_GATEWAY_BIND
@@ -594,6 +655,12 @@ spec:
594655
mountPath: /state
595656
- name: clawdbot-workspace
596657
mountPath: /workspace
658+
# ADR-005 Stage 2 — codex + commonly CLIs installed by the
659+
# codex-tools-installer init container. Mounted read-only here
660+
# since nothing in the main container needs to write to it.
661+
- name: codex-tools
662+
mountPath: /tools
663+
readOnly: true
597664
volumes:
598665
- name: clawdbot-config
599666
configMap:
@@ -604,6 +671,11 @@ spec:
604671
- name: clawdbot-workspace
605672
persistentVolumeClaim:
606673
claimName: clawdbot-workspace-pvc
674+
# emptyDir is fine — re-populated on every pod start by the init
675+
# container. Adds ~30s to startup but avoids a PVC + invalidation
676+
# logic. Revisit if pod-restart latency becomes a real concern.
677+
- name: codex-tools
678+
emptyDir: {}
607679
{{- with .Values.agents.clawdbot.nodeSelector }}
608680
nodeSelector:
609681
{{- toYaml . | nindent 8 }}

k8s/helm/commonly/values.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,14 @@ agents:
188188
repository: gcr.io/YOUR_GCP_PROJECT_ID/clawdbot-gateway
189189
tag: latest
190190
pullPolicy: IfNotPresent
191+
# ADR-005 Stage 2 — codex + @commonly/cli installed by an init
192+
# container into a shared /tools volume. codexVersion pins the
193+
# @openai/codex npm package; commonlyCliRef pins the git ref this
194+
# repo's cli/ subdirectory is installed from. @commonly/cli is not
195+
# yet on npm (ADR-005 Phase 4), so we install from source.
196+
codexTools:
197+
codexVersion: "0.125.0"
198+
commonlyCliRef: "main"
191199
config:
192200
gatewayPort: 18789
193201
bridgePort: 18790

0 commit comments

Comments
 (0)