baa-conductor

git clone 

commit
6845b72
parent
c2734c0
author
im_wower
date
2026-03-23 08:30:55 +0800 CST
feat(runtime): make codexd a first-class launchd service
18 files changed,  +564, -552
M docs/runtime/README.md
+25, -36
 1@@ -1,70 +1,59 @@
 2 # runtime
 3 
 4-当前 runtime 只定义 `mini` 单节点的长期运行方式,并默认以 local API cutover 为主线。
 5+当前 runtime 只定义 `mini` 单节点的正式长期运行方式。
 6 
 7 ## 内容
 8 
 9 - [`layout.md`](./layout.md): runtime 目录布局
10-- [`environment.md`](./environment.md): 必要环境变量
11-- [`launchd.md`](./launchd.md): `mini` 上的 launchd 安装
12+- [`environment.md`](./environment.md): 必要环境变量和默认值
13+- [`launchd.md`](./launchd.md): `mini` 上的 launchd 安装与日常操作
14 - [`node-verification.md`](./node-verification.md): `mini` 节点 on-node 检查
15-- [`codexd.md`](./codexd.md): Codex 常驻代理骨架与后续边界
16+- [`codexd.md`](./codexd.md): `codexd` 的正式运行边界
17 
18 ## 当前约定
19 
20 - 长期运行节点只有 `mini`
21 - canonical local API: `http://100.71.210.78:4317`
22 - canonical local Firefox WS: `ws://100.71.210.78:4317/ws/firefox`
23+- codexd local API: `http://127.0.0.1:4319`
24+- codexd event stream: `ws://127.0.0.1:4319/v1/codexd/events`
25 - canonical public host: `https://conductor.makefile.so`
26 - `status-api` `http://100.71.210.78:4318` 只作为本地只读观察面
27 - `BAA_CONTROL_API_BASE` 仍保留为兼容变量名,但默认值已经收口到 `https://conductor.makefile.so`
28 - 推荐仓库路径:`/Users/george/code/baa-conductor`
29 - repo 内的 plist 只作为模板;真正加载的是脚本渲染出来的安装副本
30 
31-Firefox WS 说明:
32+## 当前正式服务
33 
34-- 不单独开新端口,直接复用 `BAA_CONDUCTOR_LOCAL_API`
35-- 固定 upgrade path 是 `/ws/firefox`
36-- 只给本地 Firefox 插件双向通讯使用,不是公网通道
37-
38-`codexd` 说明:
39-
40-- 仓库里已经有 `apps/codexd` 最小骨架
41-- 当前骨架能做的事情:
42-  - 解析最小运行配置
43-  - 维护 `logs/codexd` 和 `state/codexd`
44-  - 启动或占位一个 `codex app-server` 子进程配置
45-  - 持久化 daemon identity、child state、session registry、recent event cache
46-  - 提供本地 HTTP / WS 服务面
47-  - 让 `conductor-daemon` 通过 `/v1/codex/*` 代理 session / turn / status
48-- 运行约束:
49-  - `codexd` 是独立常驻进程,不是 `conductor-daemon` 的内嵌 bridge
50-  - `/v1/codex/*` 现在以 `conductor-daemon -> codexd` 代理方式提供
51-  - `launchd` 负责自启动和硬重启,不做两个进程互相直接拉起
52-- 当前还没有:
53-  - 完整的会话恢复和断线重放
54-  - 更丰富的增量事件订阅语义
55-  - 任何 `/v1/codex/runs*` 的正式代理面
56+- `conductor`: `launchd` 托管的主控制面,承载 `4317` 本地 API
57+- `codexd`: `launchd` 托管的独立 Codex 运行面,只走 `codex app-server` 路线,监听 `127.0.0.1:4319`
58+- `status-api`: `launchd` 托管的本地只读观察面,监听 `4318`
59 
60 ## 最短路径
61 
62 1. `./scripts/runtime/install-mini.sh`
63 2. `./scripts/runtime/status-launchd.sh`
64-3. `./scripts/runtime/stop-launchd.sh`
65-4. `./scripts/runtime/start-launchd.sh`
66-5. `./scripts/runtime/restart-launchd.sh`
67-6. `./scripts/runtime/check-node.sh --node mini`
68+3. `./scripts/runtime/restart-launchd.sh`
69+4. `./scripts/runtime/check-node.sh --node mini`
70 
71 ## 当前推荐入口
72 
73 - 安装并切到当前正式仓库路径:
74   - `./scripts/runtime/install-mini.sh`
75-- 查看当前 launchd / HTTP 状态:
76+- 查看 `launchd` / HTTP 状态:
77   - `./scripts/runtime/status-launchd.sh`
78-- 停止:
79+  - 默认同时检查 `conductor`、`codexd`、`status-api`
80+- 停止 / 启动 / 重启:
81   - `./scripts/runtime/stop-launchd.sh`
82-- 启动:
83   - `./scripts/runtime/start-launchd.sh`
84-- 重启:
85   - `./scripts/runtime/restart-launchd.sh`
86-  - 脚本会在 reload 后等待 `conductor /healthz` 恢复;如果服务处于 loaded 但未提供 HTTP,会自动再做一次 `launchctl kickstart -k`
87+  - 默认同时操作 `conductor`、`codexd`、`status-api`
88+  - 需要单独管理时,用 `--service codexd`
89+- 节点检查:
90+  - `./scripts/runtime/check-node.sh --node mini`
91+
92+职责边界:
93+
94+- `launchd` 负责开机自启动和硬重启
95+- `conductor-daemon` 和 `codexd` 只负责健康感知、重连和降级
96+- 不做两个进程互相直接拉起
M docs/runtime/codexd.md
+70, -316
  1@@ -1,63 +1,34 @@
  2 # codexd
  3 
  4-`codexd` 是 `baa-conductor` 下一阶段预留的本地常驻组件设计。
  5-
  6-它的目标不是替代 `conductor-daemon`,而是把 Codex 从“手工打开一个 TUI 窗口”提升为“可被本地系统长期调用的 Codex 代理能力”。
  7-
  8-当前状态:
  9-
 10-- 仓库里已经有 `apps/codexd` 最小骨架
 11-- 已经有本地 HTTP / WS 服务面
 12-- `conductor-daemon` 已经通过 `/v1/codex/*` 代理到它
 13-- 主目标仍然围绕 `codex app-server` 演进
 14-
 15-## 目标
 16-
 17-`codexd` 需要解决的不是“再开一个 UI”,而是这 4 件事:
 18-
 19-1. 让 Codex 作为 worker 执行 step
 20-2. 让 Codex 能完成异步任务,而不是只跑一次同步 CLI
 21-3. 让 Codex 能参与 AI 对话,而不是只能手动开 TUI
 22-4. 让 Codex 全程记录日志、可恢复、可观察
 23-
 24-## 为什么需要它
 25-
 26-当前仓库已经有:
 27-
 28-- `conductor-daemon`
 29-- `worker-runner`
 30-- `planner`
 31-- `task / run / step`
 32-- `checkpoint / logging`
 33-
 34-但还没有:
 35-
 36-- Codex 常驻进程
 37-- Codex 会话代理
 38-- Codex 子进程管理
 39-- Codex 对话与任务统一入口
 40-
 41-所以目前的“Codex”仍停留在抽象层:
 42-
 43-- planner 和 step template 里有 `codex` step kind
 44-- 但系统没有一个真正的 `codexd`
 45-
 46-## 定位
 47-
 48-`codexd` 的定位应该是:
 49-
 50-- 本地常驻进程
 51-- 只跑在 `mini`
 52-- 由 `launchd` 托管
 53-- 通过本地 IPC、HTTP 或 WS 与 `conductor-daemon` 协作
 54-- 必须是独立进程,不接受长期内嵌在 `conductor-daemon` 内
 55-
 56-它不是:
 57-
 58-- 新的真相源
 59-- 新的主控制面
 60-- 新的公网入口
 61-- 手工操作的主界面
 62+`codexd` 现在是 `mini` 上的正式 Codex 运行时服务。
 63+
 64+它不是 `conductor-daemon` 的内嵌 bridge,也不是手工调试时顺便开的附属进程;它是由 `launchd` 托管的独立常驻进程,用来承接 Codex child、会话、日志、恢复和本地服务面。
 65+
 66+## 当前正式运行面
 67+
 68+- launchd label: `so.makefile.baa-codexd`
 69+- 本地监听地址:`http://127.0.0.1:4319`
 70+- 本地事件流:`ws://127.0.0.1:4319/v1/codexd/events`
 71+- 正式模式:`app-server`
 72+- child 策略:`spawn`
 73+- child 命令:`codex app-server`
 74+- 日志目录:`logs/codexd/`
 75+- 状态目录:`state/codexd/`
 76+
 77+正式 launchd 运行面固定写入:
 78+
 79+```text
 80+BAA_CODEXD_MODE=app-server
 81+BAA_CODEXD_LOCAL_API_BASE=http://127.0.0.1:4319
 82+BAA_CODEXD_EVENT_STREAM_PATH=/v1/codexd/events
 83+BAA_CODEXD_SERVER_STRATEGY=spawn
 84+BAA_CODEXD_SERVER_COMMAND=codex
 85+BAA_CODEXD_SERVER_ARGS=app-server
 86+BAA_CODEXD_SERVER_CWD=/Users/george/code/baa-conductor
 87+BAA_CODEXD_SERVER_ENDPOINT=stdio://codex-app-server
 88+BAA_CODEXD_LOGS_DIR=/Users/george/code/baa-conductor/logs/codexd
 89+BAA_CODEXD_STATE_DIR=/Users/george/code/baa-conductor/state/codexd
 90+```
 91 
 92 ## 组件边界
 93 
 94@@ -67,287 +38,70 @@
 95 
 96 - 系统真相
 97 - 任务编排
 98-- HTTP / WS 主接口
 99-- pause / resume / drain
100-- task / run / state / capability 读写
101-
102-### `worker-runner`
103-
104-负责:
105-
106-- 通用 step 执行模型
107-- 本地目录结构
108-- checkpoint 落盘
109-- 日志文件管理
110+- 对外主 HTTP / WS 接口
111+- 健康感知、重连和降级
112 
113 ### `codexd`
114 
115 负责:
116 
117-- 启动和托管 Codex 子进程
118-- 管理 Codex 会话
119-- 处理 Codex 对话和 worker step 执行
120-- 记录 Codex stdout / stderr / 结构化事件
121-- 超时、重试、崩溃恢复
122+- 启动和托管 Codex child
123+- 管理本地 Codex 会话和运行态状态
124+- 维护 `logs/codexd/**` 与 `state/codexd/**`
125+- 提供本地 HTTP / WS 服务面
126+- 处理 child 崩溃后的重建
127 
128 一句话:
129 
130 - `conductor-daemon` 管系统
131-- `worker-runner` 管通用执行框架
132 - `codexd` 管 Codex 本身
133 
134-额外约束:
135+## 当前本地服务面
136 
137-- `conductor-daemon` 不自己长期持有 Codex 会话状态
138-- `codexd` 是唯一 Codex 会话 / turn / recent events 的真相源
139+`apps/codexd` 当前已经有本地 HTTP / WS 入口,正式运行时由 `launchd` 长期托管:
140 
141-## 运行模型
142+- `GET /healthz`
143+- `GET /v1/codexd/status`
144+- `GET /v1/codexd/sessions`
145+- `POST /v1/codexd/sessions`
146+- `POST /v1/codexd/turn`
147+- `GET /v1/codexd/runs`
148+- `POST /v1/codexd/runs`
149+- `WS /v1/codexd/events`
150 
151-`codexd` 最合理的运行模型是:
152+当前骨架已经会维护:
153 
154-1. `conductor-daemon` 接到任务或对话请求
155-2. 需要 Codex 时,转给 `codexd`
156-3. `codexd` 负责:
157-   - 创建或复用 Codex session
158-   - 启动子进程
159-   - 采集增量输出
160-   - 记录日志
161-   - 返回 step 结果或对话结果
162-4. `conductor-daemon` 把结果写回本地真相源
163+- daemon identity
164+- child state
165+- session registry
166+- run registry
167+- recent event cache
168+- 结构化事件日志
169 
170-进程恢复规则:
171+## 运行职责边界
172 
173-- `launchd` 负责把挂掉的 `conductor-daemon` 或 `codexd` 拉起
174+- `launchd` 负责开机自启动和硬重启
175 - `conductor-daemon` 负责发现 `codexd` 不可用并重连
176 - `codexd` 负责发现 Codex child 不可用并重建子进程
177-- 不做“conductor 直接 spawn codexd”或“codexd 直接 spawn conductor”
178-
179-## 官方接口结论
180-
181-基于当前本机已验证的 Codex CLI 公开接口,`codexd` 的设计结论应明确为:
182-
183-- 主会话与双工能力:基于 `codex app-server`
184-- 不实现 `codex exec` 式无交互正式模式
185-- `conductor-daemon` 对外正式能力只保留 session / turn / status
186-- 不驱动 TUI
187-- 不逆向私有协议
188-
189-原因:
190-
191-- 当前目标是常驻、多轮、可恢复的会话代理,不是一次性命令壳
192-- `codex exec` 虽然适合一次性非交互执行,但天然不是多轮双工模型
193-- 在当前环境里,`codex exec` 还表现出明显卡顿和假死风险
194-- `codex app-server` 虽然仍标记为 experimental,但它已经公开了最完整的线程、turn、流式事件和恢复语义
195-- 已验证单个 `app-server` 进程可承载多个 `thread`,因此默认不需要“一对话一进程”
196-
197-当前推荐口径:
198-
199-- `codexd v1` 继续围绕 `app-server`
200-- `conductor-daemon -> codexd` 是唯一正式代理链路
201-- 不新增、不对外暴露 `/v1/codex/runs*`
202-- 不新增、不扩展、不对外暴露 `codex exec` 路线
203-- 已存在的 `run` / fallback 相关实现视为过渡代码,应逐步删除
204-
205-## 当前骨架已经落下的内容
206-
207-`apps/codexd` 当前已经提供:
208-
209-- 最小 CLI:
210-  - `start`
211-  - `status`
212-  - `config`
213-  - `smoke`
214-- 最小配置:
215-  - server mode
216-  - logs/state dir
217-  - app-server endpoint
218-  - child process strategy
219-- 最小状态:
220-  - daemon identity
221-  - child process state
222-  - session registry
223-  - recent event cache
224-- 最小运行时文件:
225-  - `logs/codexd/events.jsonl`
226-  - `logs/codexd/stdout.log`
227-  - `logs/codexd/stderr.log`
228-  - `state/codexd/identity.json`
229-  - `state/codexd/daemon-state.json`
230-  - `state/codexd/session-registry.json`
231-  - `state/codexd/recent-events.json`
232-
233-当前 `smoke` 不依赖真实 Codex CLI。
234-
235-- 它使用内置 stub child 验证骨架目录、状态文件和结构化日志能否闭环写出
236-
237-当前 `start` 的语义是:
238-
239-- `spawn` 策略下,拉起一个配置好的 child command 并持续托管它
240-- `external` 策略下,不启 child,只把 endpoint 和状态占位出来
241-
242-## 当前明确还没做的事
243-
244-当前骨架还没有:
245-
246-- `thread/start` / `thread/resume` / `turn/start` 的真实代理
247-- `codex-app-server` 传输层接线
248-- crash recovery 的自动复连和 session 恢复
249-- 更完整的事件恢复和会话恢复能力
250-
251-## 支持的两类工作
252-
253-### 1. worker 模式
254-
255-用于:
256-
257-- `codex` step
258-- 代码修改
259-- 审阅
260-- 任务执行
261-
262-特点:
263-
264-- 有 task / step / run 关联
265-- 受 timeout / retry / checkpoint 约束
266-- 由 `worker-runner` / `conductor-daemon` 编排
267-- 正式实现只走 `app-server`
268-- 不再把 `exec` 作为 worker 正路或降级路径
269-
270-### 2. duplex 对话模式
271-
272-用于:
273-
274-- 和 AI 的持续双向对话
275-- 支持 CLI / 网页版 AI 参与
276-- 长于一次性命令式调用
277-
278-特点:
279-
280-- 需要会话 ID
281-- 需要增量输出
282-- 需要日志和历史
283-- 可以和任务执行共用同一个 `codexd`,但不能混淆真相
284-- 这一层应直接建立在 `app-server` 的 `thread` / `turn` 模型上
285-
286-## 推荐能力面
287-
288-`codexd` 后续可以提供这些本地能力:
289-
290-### 会话类
291-
292-- 创建会话
293-- 列出会话
294-- 读取会话状态
295-- 关闭会话
296-
297-### 对话类
298-
299-- 向某个会话发送输入
300-- 读取流式输出
301-- 拉取最近日志
302-
303-### worker 类
304-
305-- 启动某个 `codex` step
306-- 查询 step 执行状态
307-- 取消 step
308-- 读取 step 日志和产物
309-
310-## 推荐实现顺序
311-
312-### v1
313-
314-- 启动单个常驻 `codex app-server`
315-- 由 `codexd` 维护:
316-  - server 生命周期
317-  - thread 映射
318-  - turn 启动 / 打断 / 继续
319-  - 增量事件日志
320-- 对 `conductor-daemon` 暴露稳定的本地适配层
321-- 这层至少拆成:
322-  - 本地 HTTP:session / turn / status
323-  - 本地 WS 或 SSE:增量事件流
324-
325-### v1 约束
326-
327-`codexd` 不再提供 `codex exec` 式无交互正式能力:
328-
329-- 不新增 `/runs` 类正式接口
330-- 不把 `exec` 当作 worker 正路
331-- 不把 `exec` 当作会话降级面
332-- 不把 `exec` 暴露给上层 AI 直接调用
333-
334-原因:
335-
336-- 卡顿明显
337-- 容易假死
338-- 不符合常驻双工模型
339-
340-### v2
341-
342-- 在 `app-server` 之上继续补:
343-  - 会话恢复
344-  - 多 thread 管理
345-  - 对话 steering / interrupt
346-  - worker 与对话统一日志索引
347-
348-## 日志与可恢复性
349-
350-`codexd` 不应该只把结果打印到终端。
351-
352-最少需要:
353-
354-- 会话级日志
355-- step 级日志
356-- 结构化事件日志
357-- 最近输出缓存
358-
359-推荐落点:
360-
361-- `logs/codexd/`
362-- `runs/<run-id>/`
363-- `state/codexd/`
364-
365-要支持:
366-
367-- 进程重启后恢复会话索引
368-- 任务执行失败后保留日志和上下文
369-- CLI / 网页 UI 可查询最近输出
370-
371-## 自启动
372-
373-当前已经预留了 launchd 模板;后续完整接线时,仍推荐:
374-
375-- 新增 `launchd` 服务
376-- 例如:`so.makefile.baa-codexd`
377-
378-默认行为:
379-
380-- `mini` 启动后自动拉起
381-- 不要求用户手工开 TUI
382-- TUI 只作为调试手段,而不是主运行方式
383-
384-## 当前建议
385-
386-当前实现已经有最小骨架,但继续遵守这些边界:
387+- 不做 `conductor-daemon` 直接 spawn `codexd`
388+- 不做 `codexd` 直接 spawn `conductor-daemon`
389 
390-- `conductor-daemon` 是主接口
391-- `worker-runner` 是通用执行框架
392-- `codex` 仍只是 step kind 和未来的 worker 类型
393+## 正式口径明确排除的内容
394 
395-如果开始实现 `codexd`,默认遵守这条约束:
396+下面这些不属于正式 launchd 运行面:
397 
398-- `app-server` 是主能力面
399-- `conductor-daemon` 只代理独立 `codexd`
400-- 不把 `runs` / `exec` 扩成正式产品能力
401-- `packages/codex-exec` 不再作为正式产品能力推进;后续应缩成内部测试/迁移遗留,或直接删除
402+- `codex exec` 正式模式
403+- TUI 常驻模式
404+- 两个进程互相直接拉起
405+- 把 `codexd` 描述成“可选以后再说”的附属能力
406 
407-不要把当前系统误认为已经有:
408+`codex exec` 可以继续存在于测试、临时工具或过渡代码里,但不进入 `mini` 正式 runtime 配置、launchd 模板或安装脚本。
409 
410-- Codex daemon
411-- Codex 会话代理
412-- Codex 双工对话桥
413+## 当前仍未补齐的部分
414 
415-## 一句话定义
416+当前 runtime / launchd / 检查口径已经正式纳入 `codexd`,但以下能力仍在后续演进范围:
417 
418-`codexd` = `mini` 上的 Codex 常驻代理,用来承接 Codex 的 worker 执行、对话、日志与恢复;它是 `baa-conductor` 的后续组件,不是当前已上线能力。
419+- 更完整的 `conductor-daemon -> codexd` 代理接线
420+- 更丰富的会话恢复和断线重放
421+- 更强的 steering / interrupt / replay 语义
422+- 更细的 worker 与对话统一日志索引
M docs/runtime/environment.md
+22, -8
 1@@ -4,6 +4,8 @@
 2 
 3 - canonical local API: `http://100.71.210.78:4317`
 4 - canonical local Firefox WS: `ws://100.71.210.78:4317/ws/firefox`
 5+- codexd local API: `http://127.0.0.1:4319`
 6+- codexd event stream: `ws://127.0.0.1:4319/v1/codexd/events`
 7 - canonical public host: `https://conductor.makefile.so`
 8 - local status view: `http://100.71.210.78:4318`
 9 
10@@ -20,13 +22,16 @@
11 说明:
12 
13 - `BAA_CONTROL_API_BASE` 是兼容变量名,当前主要给 `status-api` 和遗留脚本使用
14-- 它的默认值已经收口到 `https://conductor.makefile.so`,不再代表单独旧控制面
15+- 它的默认值已经收口到 `https://conductor.makefile.so`
16+- `codexd` 独立安装时不要求 `BAA_SHARED_TOKEN`
17 
18 ## codexd 变量
19 
20 `apps/codexd` 当前识别这些变量:
21 
22 - `BAA_CODEXD_REPO_ROOT`
23+- `BAA_CODEXD_LOCAL_API_BASE`
24+- `BAA_CODEXD_EVENT_STREAM_PATH`
25 - `BAA_CODEXD_MODE`
26 - `BAA_CODEXD_LOGS_DIR`
27 - `BAA_CODEXD_STATE_DIR`
28@@ -39,20 +44,27 @@
29 - `BAA_CODEXD_SMOKE_LIFETIME_MS`
30 - `BAA_CODEXD_VERSION`
31 
32-当前默认值:
33+正式 launchd 运行面默认写入:
34 
35 ```text
36+BAA_CODEXD_REPO_ROOT=/Users/george/code/baa-conductor
37+BAA_CODEXD_LOCAL_API_BASE=http://127.0.0.1:4319
38+BAA_CODEXD_EVENT_STREAM_PATH=/v1/codexd/events
39 BAA_CODEXD_MODE=app-server
40 BAA_CODEXD_SERVER_STRATEGY=spawn
41 BAA_CODEXD_SERVER_COMMAND=codex
42 BAA_CODEXD_SERVER_ARGS=app-server
43+BAA_CODEXD_SERVER_CWD=/Users/george/code/baa-conductor
44 BAA_CODEXD_SERVER_ENDPOINT=stdio://codex-app-server
45+BAA_CODEXD_LOGS_DIR=/Users/george/code/baa-conductor/logs/codexd
46+BAA_CODEXD_STATE_DIR=/Users/george/code/baa-conductor/state/codexd
47 ```
48 
49-派生目录:
50+说明:
51 
52-- `BAA_CODEXD_LOGS_DIR` 未设置时,默认 `${BAA_LOGS_DIR}/codexd`
53-- `BAA_CODEXD_STATE_DIR` 未设置时,默认 `${BAA_STATE_DIR}/codexd`
54+- 正式运行面只支持 `app-server`
55+- 不为 launchd 运行面增加 `codex exec` 正式开关
56+- `BAA_CODEXD_LOCAL_API_BASE` 必须保持 loopback host;当前默认是 `127.0.0.1:4319`
57 
58 ## 节点变量
59 
60@@ -66,7 +78,7 @@ BAA_STATUS_API_HOST=100.71.210.78
61 BAA_CONTROL_API_BASE=https://conductor.makefile.so
62 ```
63 
64-上面最后一项只是兼容旧代码的变量名;默认目标已经与 canonical public host 对齐。
65+最后一项只是兼容旧代码的变量名;默认目标已经与 canonical public host 对齐。
66 
67 Firefox WS 派生规则:
68 
69@@ -81,15 +93,17 @@ Firefox WS 派生规则:
70 
71 ## 最小例子
72 
73-当前脚本仍要求保留兼容参数时,可这样安装:
74-
75 ```bash
76 ./scripts/runtime/install-launchd.sh \
77   --repo-dir /Users/george/code/baa-conductor \
78   --node mini \
79+  --service conductor \
80+  --service codexd \
81+  --service status-api \
82   --shared-token-file /Users/george/.config/baa-conductor/shared-token.txt \
83   --control-api-base https://conductor.makefile.so \
84   --local-api-base http://100.71.210.78:4317 \
85   --local-api-allowed-hosts 100.71.210.78 \
86+  --codexd-local-api-base http://127.0.0.1:4319 \
87   --status-api-host 100.71.210.78
88 ```
M docs/runtime/launchd.md
+45, -85
  1@@ -5,8 +5,8 @@
  2 ## 当前目标状态
  3 
  4 - `conductor` 由 `launchd` 托管,并承载 canonical local API `http://100.71.210.78:4317`
  5-- `status-api` 仍会随默认安装一起部署,但只作为本地只读观察面
  6-- `codexd` 现在有独立 plist 模板,但还没有接进统一安装脚本
  7+- `codexd` 由 `launchd` 托管,并作为正式独立 Codex 运行面监听 `http://127.0.0.1:4319`
  8+- `status-api` 随默认安装一起部署,但只作为本地只读观察面
  9 - 工作目录固定到 `/Users/george/code/baa-conductor`
 10 - 通过仓库内脚本统一安装、启动、停止、重启与验证
 11 
 12@@ -20,16 +20,10 @@
 13 
 14 1. 初始化 runtime 目录
 15 2. 构建仓库
 16-3. 渲染并安装 `conductor` / `status-api` 的 LaunchAgents
 17+3. 渲染并安装 `conductor` / `codexd` / `status-api` 的 LaunchAgents
 18 4. 重启 launchd 服务
 19 5. 跑静态检查和节点检查
 20 
 21-`codexd` 说明:
 22-
 23-- 模板文件已经在 [`ops/launchd/so.makefile.baa-codexd.plist`](../../ops/launchd/so.makefile.baa-codexd.plist)
 24-- 当前任务没有改 `scripts/runtime/install-launchd.sh`
 25-- 所以它现在还是“手工可加载模板”,不是 `install-mini.sh` 的默认安装对象
 26-
 27 默认会把共享 token 收口到:
 28 
 29 - `~/.config/baa-conductor/shared-token.txt`
 30@@ -38,12 +32,13 @@
 31 
 32 - `~/.config/baa-conductor/runtime-secrets.env`
 33 
 34-里提取 `BAA_SHARED_TOKEN` 并生成它。
 35+里提取 `BAA_SHARED_TOKEN` 并生成它;如果新的 env 文件不存在,还会回退读取 legacy `control-api-worker.secrets.env`。
 36 
 37 说明:
 38 
 39-- 如果新的 `runtime-secrets.env` 不存在,脚本还会回退读取 legacy `control-api-worker.secrets.env`
 40-- 脚本里保留 `--control-api-base` 只是为了写入兼容变量名;默认值已经是 `https://conductor.makefile.so`
 41+- `codexd` 独立安装时不需要共享 token
 42+- `--control-api-base` 仍然保留,只是为了写入兼容变量 `BAA_CONTROL_API_BASE`
 43+- `codexd` 正式运行面只写入 `app-server` 相关默认值,不暴露 `codex exec` 正式开关
 44 
 45 ## 日常管理
 46 
 47@@ -71,116 +66,81 @@
 48 ./scripts/runtime/restart-launchd.sh
 49 ```
 50 
 51-当前 `restart-launchd.sh` / `reload-launchd.sh` 在 `bootstrap + kickstart` 结束后不会只看 `launchctl` 返回码,而会继续检查 `conductor /healthz`。
 52+这些命令默认同时操作 `conductor`、`codexd`、`status-api`。需要单独管理时,用 `--service codexd`、`--service conductor` 或 `--service status-api`。
 53 
 54-- 优先读取已安装 `conductor` plist 里的 `BAA_CONDUCTOR_LOCAL_API`
 55-- 默认检查 `<local-api-base>/healthz`
 56-- 如果第一次 reload 后仍然是 loaded but not serving,会自动再执行一次:
 57+例如只重启 `codexd`:
 58 
 59 ```bash
 60-launchctl kickstart -k gui/$(id -u)/so.makefile.baa-conductor
 61+./scripts/runtime/restart-launchd.sh --service codexd
 62 ```
 63 
 64-- 如果二次 kickstart 后仍未恢复,脚本会返回非零,并输出:
 65-  - 最后一次 health probe 的 curl 结果
 66-  - `launchctl print` 诊断
 67-  - `conductor` stdout/stderr 日志尾部
 68-  - 手工兜底命令提示
 69-
 70-## 1. 构建
 71-
 72-```bash
 73-npx --yes pnpm -r build
 74-```
 75+## 渲染安装副本
 76 
 77-## 2. 初始化 runtime 目录
 78-
 79-```bash
 80-./scripts/runtime/bootstrap.sh --repo-dir /Users/george/code/baa-conductor
 81-```
 82-
 83-## 3. 渲染安装副本
 84-
 85-保留 `--control-api-base`,只是为了让当前 `status-api` 继续读取兼容变量名:
 86+完整 mini 安装副本示例:
 87 
 88 ```bash
 89 ./scripts/runtime/install-launchd.sh \
 90   --repo-dir /Users/george/code/baa-conductor \
 91   --node mini \
 92   --service conductor \
 93+  --service codexd \
 94   --service status-api \
 95   --install-dir /Users/george/Library/LaunchAgents \
 96   --shared-token-file /Users/george/.config/baa-conductor/shared-token.txt \
 97   --control-api-base https://conductor.makefile.so \
 98   --local-api-base http://100.71.210.78:4317 \
 99   --local-api-allowed-hosts 100.71.210.78 \
100+  --codexd-local-api-base http://127.0.0.1:4319 \
101   --status-api-host 100.71.210.78
102 ```
103 
104-## 4. 静态校验
105+单独安装 `codexd`:
106 
107 ```bash
108-./scripts/runtime/check-launchd.sh \
109+./scripts/runtime/install-launchd.sh \
110   --repo-dir /Users/george/code/baa-conductor \
111   --node mini \
112-  --service conductor \
113-  --service status-api \
114+  --service codexd \
115   --install-dir /Users/george/Library/LaunchAgents \
116-  --shared-token-file /Users/george/.config/baa-conductor/shared-token.txt \
117-  --control-api-base https://conductor.makefile.so \
118-  --local-api-base http://100.71.210.78:4317 \
119-  --local-api-allowed-hosts 100.71.210.78 \
120-  --status-api-host 100.71.210.78
121+  --codexd-local-api-base http://127.0.0.1:4319
122 ```
123 
124-## 5. 重载
125+`codexd` 默认写入这些正式运行值:
126 
127-```bash
128-./scripts/runtime/reload-launchd.sh \
129-  --install-dir /Users/george/Library/LaunchAgents \
130-  --service conductor \
131-  --service status-api
132-```
133+- `BAA_CODEXD_MODE=app-server`
134+- `BAA_CODEXD_LOCAL_API_BASE=http://127.0.0.1:4319`
135+- `BAA_CODEXD_EVENT_STREAM_PATH=/v1/codexd/events`
136+- `BAA_CODEXD_SERVER_STRATEGY=spawn`
137+- `BAA_CODEXD_SERVER_COMMAND=codex`
138+- `BAA_CODEXD_SERVER_ARGS=app-server`
139+- `BAA_CODEXD_SERVER_CWD=/Users/george/code/baa-conductor`
140+- `BAA_CODEXD_SERVER_ENDPOINT=stdio://codex-app-server`
141 
142-推荐最小验证:
143+## reload 行为
144 
145-```bash
146-./scripts/runtime/restart-launchd.sh
147-curl -fsS http://100.71.210.78:4317/healthz
148-curl -fsS http://100.71.210.78:4317/rolez
149-```
150+`restart-launchd.sh` / `reload-launchd.sh` 不只看 `launchctl` 返回码,还会等待已选服务的 `/healthz` 恢复:
151 
152-预期:
153+- `conductor`: 读取 `BAA_CONDUCTOR_LOCAL_API`
154+- `codexd`: 读取 `BAA_CODEXD_LOCAL_API_BASE`
155+- `status-api`: 读取 `BAA_STATUS_API_HOST` 并按默认端口 `4318` 探活
156 
157-- `restart-launchd.sh` 直接成功返回
158-- `/healthz` 返回 `ok`
159-- `/rolez` 返回当前角色
160-- 不需要再手工执行一次 `launchctl kickstart -k gui/$(id -u)/so.makefile.baa-conductor`
161-
162-## 6. 节点检查
163+如果服务处于 loaded 但未提供 HTTP,脚本会自动再执行一次:
164 
165 ```bash
166-./scripts/runtime/check-node.sh \
167-  --repo-dir /Users/george/code/baa-conductor \
168-  --node mini \
169-  --service conductor \
170-  --service status-api \
171-  --install-dir /Users/george/Library/LaunchAgents \
172-  --local-api-base http://100.71.210.78:4317 \
173-  --local-api-allowed-hosts 100.71.210.78 \
174-  --status-api-base http://100.71.210.78:4318 \
175-  --status-api-host 100.71.210.78 \
176-  --expected-rolez leader \
177-  --check-loaded
178+launchctl kickstart -k gui/$(id -u)/<label>
179 ```
180 
181-## 7. 当前验证口径
182+如果二次 kickstart 后仍未恢复,脚本会返回非零,并输出:
183 
184-最小验证就是这两条:
185+- 最后一次 health probe 的 curl 结果
186+- `launchctl print` 诊断
187+- 对应服务 stdout/stderr 日志尾部
188+- 手工兜底命令提示
189 
190-```bash
191-./scripts/runtime/status-launchd.sh
192-./scripts/runtime/check-node.sh --node mini --check-loaded --expected-rolez leader
193-```
194+## 职责边界
195 
196-如果要单独试 `codexd` 模板,先 build,再手工复制 plist 到 `~/Library/LaunchAgents`,最后用 `launchctl bootstrap` / `launchctl kickstart` 加载它。
197+- `launchd` 负责开机自启动和硬重启
198+- `conductor-daemon` 负责发现 `codexd` 不可用并重连
199+- `codexd` 负责发现 Codex child 不可用并重建子进程
200+- 不做 `conductor-daemon` 直接 spawn `codexd`
201+- 不做 `codexd` 直接 spawn `conductor-daemon`
M docs/runtime/node-verification.md
+28, -22
  1@@ -1,6 +1,10 @@
  2 # node verification
  3 
  4-当前只检查 `mini`,并把 `4317` 视为主接口。
  5+当前只检查 `mini`,正式运行面同时认识:
  6+
  7+- `conductor` `http://100.71.210.78:4317`
  8+- `codexd` `http://127.0.0.1:4319`
  9+- `status-api` `http://100.71.210.78:4318`
 10 
 11 ## 1. 构建与静态检查
 12 
 13@@ -10,19 +14,21 @@ npx --yes pnpm -r build
 14   --repo-dir /Users/george/code/baa-conductor \
 15   --node mini \
 16   --service conductor \
 17+  --service codexd \
 18   --service status-api \
 19   --install-dir /Users/george/Library/LaunchAgents \
 20   --shared-token-file /Users/george/.config/baa-conductor/shared-token.txt \
 21   --control-api-base https://conductor.makefile.so \
 22   --local-api-base http://100.71.210.78:4317 \
 23   --local-api-allowed-hosts 100.71.210.78 \
 24+  --codexd-local-api-base http://127.0.0.1:4319 \
 25   --status-api-host 100.71.210.78
 26 ```
 27 
 28 说明:
 29 
 30 - `--control-api-base` 仍是当前静态检查参数,但只用于校验兼容变量 `BAA_CONTROL_API_BASE`
 31-- 它的默认值已经与 `https://conductor.makefile.so` 对齐,不改变 `4317` 是 canonical local API 的事实
 32+- `check-launchd.sh` 现在会校验 `codexd` 的监听地址、事件流路径、日志目录、状态目录和 `app-server` child 配置
 33 
 34 ## 2. 运行态检查
 35 
 36@@ -31,17 +37,30 @@ npx --yes pnpm -r build
 37   --repo-dir /Users/george/code/baa-conductor \
 38   --node mini \
 39   --service conductor \
 40+  --service codexd \
 41   --service status-api \
 42   --install-dir /Users/george/Library/LaunchAgents \
 43   --local-api-base http://100.71.210.78:4317 \
 44   --local-api-allowed-hosts 100.71.210.78 \
 45+  --codexd-api-base http://127.0.0.1:4319 \
 46   --status-api-base http://100.71.210.78:4318 \
 47   --status-api-host 100.71.210.78 \
 48   --expected-rolez leader \
 49   --check-loaded
 50 ```
 51 
 52-## 3. 主路径手工探针
 53+`check-node.sh` 当前会验证:
 54+
 55+- `launchctl print` 是否成功
 56+- 进程命令行是否匹配
 57+- `logs/launchd/*.log` 是否存在
 58+- `conductor` 是否监听 `4317` 并返回 `/healthz`、`/readyz`、`/rolez`
 59+- `codexd` 是否监听 `4319` 并返回 `/healthz`、`/v1/codexd/status`
 60+- `status-api` 是否监听 `4318` 并返回 `/healthz`、`/v1/status`
 61+
 62+## 3. 手工探针
 63+
 64+主路径:
 65 
 66 ```bash
 67 curl -fsSL https://conductor.makefile.so/healthz
 68@@ -50,34 +69,21 @@ curl -fsSL https://conductor.makefile.so/rolez
 69 curl -fsSL https://conductor.makefile.so/v1/runtime
 70 ```
 71 
 72-on-node 时也可以直接探:
 73+on-node:
 74 
 75 ```bash
 76 curl -fsSL http://100.71.210.78:4317/healthz
 77 curl -fsSL http://100.71.210.78:4317/v1/runtime
 78-```
 79-
 80-## 4. 兼容层检查
 81-
 82-如果 `status-api` 仍在本地保留,再检查:
 83-
 84-```bash
 85+curl -fsSL http://127.0.0.1:4319/healthz
 86+curl -fsSL http://127.0.0.1:4319/v1/codexd/status
 87 curl -fsSL http://100.71.210.78:4318/v1/status
 88 ```
 89 
 90-如果你在做残留依赖排查,优先确认安装副本里的兼容变量已经收口到当前公网域名:
 91-
 92-```bash
 93-/usr/libexec/PlistBuddy -c 'Print :EnvironmentVariables:BAA_CONTROL_API_BASE' \
 94-  /Users/george/Library/LaunchAgents/so.makefile.baa-conductor.plist
 95-```
 96-
 97-期望输出是 `https://conductor.makefile.so`;如果不是,说明节点仍残留旧口径。
 98-
 99-## 常见失败点
100+## 4. 常见失败点
101 
102 - `conductor /rolez` 不是 `leader`
103+- `codexd` 没有监听 `127.0.0.1:4319`
104+- `codexd /v1/codexd/status` 没有返回 `app-server` 运行信息
105 - `conductor.makefile.so` 没有正确回源到 `100.71.210.78:4317`
106-- `status-api /v1/status` 没有正确回源到当前 `conductor.makefile.so` / `4317`,导致本地观察结果漂移
107 - `launchctl print` 失败
108 - `logs/launchd/*.log` 没有新内容
M ops/launchd/so.makefile.baa-codexd.plist
+15, -3
 1@@ -2,9 +2,11 @@
 2 <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
 3 <!--
 4   Source template kept in the repo.
 5-  This codexd plist is intentionally manual for now: install-launchd.sh has not
 6-  been extended in this task, so render and copy it explicitly if you want to
 7-  load codexd under launchd before the runtime scripts are taught about it.
 8+  Default values target the formal mini codexd runtime at
 9+  /Users/george/code/baa-conductor.
10+  Use scripts/runtime/install-launchd.sh to render the actual install copy.
11+  launchd is responsible for auto-start and hard restart; codexd itself only
12+  manages Codex child health and reconnect.
13 -->
14 <plist version="1.0">
15   <dict>
16@@ -30,8 +32,18 @@
17       <string>/Users/george/code/baa-conductor/logs</string>
18       <key>BAA_STATE_DIR</key>
19       <string>/Users/george/code/baa-conductor/state</string>
20+      <key>BAA_CODEXD_REPO_ROOT</key>
21+      <string>/Users/george/code/baa-conductor</string>
22+      <key>BAA_CODEXD_LOGS_DIR</key>
23+      <string>/Users/george/code/baa-conductor/logs/codexd</string>
24+      <key>BAA_CODEXD_STATE_DIR</key>
25+      <string>/Users/george/code/baa-conductor/state/codexd</string>
26       <key>BAA_CODEXD_MODE</key>
27       <string>app-server</string>
28+      <key>BAA_CODEXD_LOCAL_API_BASE</key>
29+      <string>http://127.0.0.1:4319</string>
30+      <key>BAA_CODEXD_EVENT_STREAM_PATH</key>
31+      <string>/v1/codexd/events</string>
32       <key>BAA_CODEXD_SERVER_STRATEGY</key>
33       <string>spawn</string>
34       <key>BAA_CODEXD_SERVER_COMMAND</key>
M ops/launchd/so.makefile.baa-conductor.plist
+4, -2
 1@@ -32,9 +32,11 @@
 2       <key>BAA_CONDUCTOR_ROLE</key>
 3       <string>primary</string>
 4       <key>BAA_CONTROL_API_BASE</key>
 5-      <string>https://control-api.makefile.so</string>
 6+      <string>https://conductor.makefile.so</string>
 7       <key>BAA_CONDUCTOR_LOCAL_API</key>
 8-      <string>http://127.0.0.1:4317</string>
 9+      <string>http://100.71.210.78:4317</string>
10+      <key>BAA_CONDUCTOR_LOCAL_API_ALLOWED_HOSTS</key>
11+      <string>100.71.210.78</string>
12       <key>BAA_RUNS_DIR</key>
13       <string>/Users/george/code/baa-conductor/runs</string>
14       <key>BAA_WORKTREES_DIR</key>
M ops/launchd/so.makefile.baa-status-api.plist
+6, -4
 1@@ -1,7 +1,7 @@
 2 <?xml version="1.0" encoding="UTF-8"?>
 3 <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
 4 <!--
 5-  Optional local status API.
 6+  Local read-only status API for the mini runtime.
 7   Keep the same runtime paths as conductor and worker-runner so that service
 8   logs and temporary files stay under one repo-owned runtime root.
 9   Use scripts/runtime/install-launchd.sh to render the actual install copy.
10@@ -29,11 +29,13 @@
11       <key>BAA_CONDUCTOR_ROLE</key>
12       <string>primary</string>
13       <key>BAA_CONTROL_API_BASE</key>
14-      <string>https://control-api.makefile.so</string>
15+      <string>https://conductor.makefile.so</string>
16       <key>BAA_CONDUCTOR_LOCAL_API</key>
17-      <string>http://127.0.0.1:4317</string>
18+      <string>http://100.71.210.78:4317</string>
19+      <key>BAA_CONDUCTOR_LOCAL_API_ALLOWED_HOSTS</key>
20+      <string>100.71.210.78</string>
21       <key>BAA_STATUS_API_HOST</key>
22-      <string>127.0.0.1</string>
23+      <string>100.71.210.78</string>
24       <key>BAA_RUNS_DIR</key>
25       <string>/Users/george/code/baa-conductor/runs</string>
26       <key>BAA_WORKTREES_DIR</key>
M ops/launchd/so.makefile.baa-worker-runner.plist
+4, -2
 1@@ -30,9 +30,11 @@
 2       <key>BAA_CONDUCTOR_ROLE</key>
 3       <string>primary</string>
 4       <key>BAA_CONTROL_API_BASE</key>
 5-      <string>https://control-api.makefile.so</string>
 6+      <string>https://conductor.makefile.so</string>
 7       <key>BAA_CONDUCTOR_LOCAL_API</key>
 8-      <string>http://127.0.0.1:4317</string>
 9+      <string>http://100.71.210.78:4317</string>
10+      <key>BAA_CONDUCTOR_LOCAL_API_ALLOWED_HOSTS</key>
11+      <string>100.71.210.78</string>
12       <key>BAA_RUNS_DIR</key>
13       <string>/Users/george/code/baa-conductor/runs</string>
14       <key>BAA_WORKTREES_DIR</key>
M scripts/runtime/check-launchd.sh
+73, -13
  1@@ -14,7 +14,7 @@ Options:
  2   --node mini               Select node defaults. Defaults to mini.
  3   --scope agent|daemon      Expected launchd scope for install copies. Defaults to agent.
  4   --service NAME            Add one service to the check set. Repeatable.
  5-  --all-services            Check conductor, worker-runner, and status-api.
  6+  --all-services            Check conductor, codexd, worker-runner, and status-api.
  7   --repo-dir PATH           Repo root used to derive runtime paths.
  8   --home-dir PATH           HOME value expected in installed plist files.
  9   --install-dir PATH        Validate installed copies under this directory.
 10@@ -24,6 +24,13 @@ Options:
 11   --local-api-base URL      Expected BAA_CONDUCTOR_LOCAL_API.
 12   --local-api-allowed-hosts CSV
 13                             Expected BAA_CONDUCTOR_LOCAL_API_ALLOWED_HOSTS.
 14+  --codexd-local-api-base URL
 15+                            Expected BAA_CODEXD_LOCAL_API_BASE.
 16+  --codexd-event-stream-path PATH
 17+                            Expected BAA_CODEXD_EVENT_STREAM_PATH.
 18+  --codexd-server-command COMMAND
 19+                            Expected BAA_CODEXD_SERVER_COMMAND.
 20+  --codexd-server-cwd PATH  Expected BAA_CODEXD_SERVER_CWD.
 21   --status-api-host HOST    Expected BAA_STATUS_API_HOST.
 22   --username NAME           Expected UserName for LaunchDaemons.
 23   --skip-dist-check         Skip dist/index.js existence checks.
 24@@ -32,8 +39,7 @@ Options:
 25   --help                    Show this help text.
 26 
 27 Notes:
 28-  If no service is specified, only conductor is checked. Use --all-services or
 29-  repeat --service to opt into worker-runner/status-api templates.
 30+  If no service is specified, conductor + codexd + status-api are checked.
 31 EOF
 32 }
 33 
 34@@ -48,9 +54,13 @@ install_dir=""
 35 shared_token=""
 36 shared_token_file=""
 37 control_api_base="${BAA_RUNTIME_DEFAULT_CONTROL_API_BASE}"
 38-local_api_base="${BAA_RUNTIME_DEFAULT_LOCAL_API}"
 39-local_api_allowed_hosts="${BAA_CONDUCTOR_LOCAL_API_ALLOWED_HOSTS:-}"
 40-status_api_host="${BAA_STATUS_API_HOST:-127.0.0.1}"
 41+local_api_base="http://100.71.210.78:4317"
 42+local_api_allowed_hosts="${BAA_CONDUCTOR_LOCAL_API_ALLOWED_HOSTS:-100.71.210.78}"
 43+codexd_local_api_base="${BAA_CODEXD_LOCAL_API_BASE:-${BAA_RUNTIME_DEFAULT_CODEXD_LOCAL_API}}"
 44+codexd_event_stream_path="${BAA_CODEXD_EVENT_STREAM_PATH:-${BAA_RUNTIME_DEFAULT_CODEXD_EVENT_STREAM_PATH}}"
 45+codexd_server_command="${BAA_CODEXD_SERVER_COMMAND:-${BAA_RUNTIME_DEFAULT_CODEXD_SERVER_COMMAND}}"
 46+codexd_server_cwd="${BAA_CODEXD_SERVER_CWD:-}"
 47+status_api_host="${BAA_STATUS_API_HOST:-100.71.210.78}"
 48 username="$(default_username)"
 49 skip_dist_check="0"
 50 check_loaded="0"
 51@@ -114,6 +124,22 @@ while [[ $# -gt 0 ]]; do
 52       local_api_allowed_hosts="$2"
 53       shift 2
 54       ;;
 55+    --codexd-local-api-base)
 56+      codexd_local_api_base="$2"
 57+      shift 2
 58+      ;;
 59+    --codexd-event-stream-path)
 60+      codexd_event_stream_path="$2"
 61+      shift 2
 62+      ;;
 63+    --codexd-server-command)
 64+      codexd_server_command="$2"
 65+      shift 2
 66+      ;;
 67+    --codexd-server-cwd)
 68+      codexd_server_cwd="$2"
 69+      shift 2
 70+      ;;
 71     --status-api-host)
 72       status_api_host="$2"
 73       shift 2
 74@@ -153,7 +179,23 @@ if [[ "${#services[@]}" -eq 0 ]]; then
 75   done < <(default_services)
 76 fi
 77 
 78-if [[ -n "$shared_token" || -n "$shared_token_file" ]]; then
 79+if [[ -z "$codexd_server_cwd" ]]; then
 80+  codexd_server_cwd="$repo_dir"
 81+fi
 82+
 83+services_require_shared_token() {
 84+  local service
 85+
 86+  for service in "${services[@]}"; do
 87+    if service_requires_shared_token "$service"; then
 88+      return 0
 89+    fi
 90+  done
 91+
 92+  return 1
 93+}
 94+
 95+if services_require_shared_token && [[ -n "$shared_token" || -n "$shared_token_file" ]]; then
 96   shared_token="$(load_shared_token "$shared_token" "$shared_token_file")"
 97 fi
 98 
 99@@ -192,7 +234,6 @@ check_installed_plist() {
100   local stdout_path="$3"
101   local stderr_path="$4"
102   local dist_entry="$5"
103-  local actual_shared_token=""
104 
105   assert_file "$plist_path"
106   plutil -lint "$plist_path" >/dev/null
107@@ -215,11 +256,15 @@ check_installed_plist() {
108   check_string_equals "${service}:stderr" "$(plist_print_value "$plist_path" ":StandardErrorPath")" "$stderr_path"
109   check_string_equals "${service}:entry" "$(plist_print_value "$plist_path" ":ProgramArguments:2")" "$dist_entry"
110 
111-  actual_shared_token="$(plist_print_value "$plist_path" ":EnvironmentVariables:BAA_SHARED_TOKEN")"
112-  if [[ -n "$shared_token" ]]; then
113-    check_string_equals "${service}:BAA_SHARED_TOKEN" "$actual_shared_token" "$shared_token"
114-  elif [[ -z "$actual_shared_token" || "$actual_shared_token" == "replace-me" ]]; then
115-    die "${service}: BAA_SHARED_TOKEN is empty or still replace-me"
116+  if service_requires_shared_token "$service"; then
117+    local actual_shared_token
118+
119+    actual_shared_token="$(plist_print_value "$plist_path" ":EnvironmentVariables:BAA_SHARED_TOKEN")"
120+    if [[ -n "$shared_token" ]]; then
121+      check_string_equals "${service}:BAA_SHARED_TOKEN" "$actual_shared_token" "$shared_token"
122+    elif [[ -z "$actual_shared_token" || "$actual_shared_token" == "replace-me" ]]; then
123+      die "${service}: BAA_SHARED_TOKEN is empty or still replace-me"
124+    fi
125   fi
126 
127   if [[ "$service" == "conductor" ]]; then
128@@ -227,6 +272,21 @@ check_installed_plist() {
129     check_string_equals "${service}:role-arg" "$(plist_print_value "$plist_path" ":ProgramArguments:6")" "$conductor_role"
130   fi
131 
132+  if [[ "$service" == "codexd" ]]; then
133+    check_string_equals "${service}:start-arg" "$(plist_print_value "$plist_path" ":ProgramArguments:3")" "start"
134+    check_string_equals "${service}:BAA_CODEXD_REPO_ROOT" "$(plist_print_value "$plist_path" ":EnvironmentVariables:BAA_CODEXD_REPO_ROOT")" "$repo_dir"
135+    check_string_equals "${service}:BAA_CODEXD_LOGS_DIR" "$(plist_print_value "$plist_path" ":EnvironmentVariables:BAA_CODEXD_LOGS_DIR")" "${repo_dir}/logs/codexd"
136+    check_string_equals "${service}:BAA_CODEXD_STATE_DIR" "$(plist_print_value "$plist_path" ":EnvironmentVariables:BAA_CODEXD_STATE_DIR")" "${repo_dir}/state/codexd"
137+    check_string_equals "${service}:BAA_CODEXD_MODE" "$(plist_print_value "$plist_path" ":EnvironmentVariables:BAA_CODEXD_MODE")" "$BAA_RUNTIME_DEFAULT_CODEXD_SERVER_MODE"
138+    check_string_equals "${service}:BAA_CODEXD_LOCAL_API_BASE" "$(plist_print_value "$plist_path" ":EnvironmentVariables:BAA_CODEXD_LOCAL_API_BASE")" "$codexd_local_api_base"
139+    check_string_equals "${service}:BAA_CODEXD_EVENT_STREAM_PATH" "$(plist_print_value "$plist_path" ":EnvironmentVariables:BAA_CODEXD_EVENT_STREAM_PATH")" "$codexd_event_stream_path"
140+    check_string_equals "${service}:BAA_CODEXD_SERVER_STRATEGY" "$(plist_print_value "$plist_path" ":EnvironmentVariables:BAA_CODEXD_SERVER_STRATEGY")" "$BAA_RUNTIME_DEFAULT_CODEXD_SERVER_STRATEGY"
141+    check_string_equals "${service}:BAA_CODEXD_SERVER_COMMAND" "$(plist_print_value "$plist_path" ":EnvironmentVariables:BAA_CODEXD_SERVER_COMMAND")" "$codexd_server_command"
142+    check_string_equals "${service}:BAA_CODEXD_SERVER_ARGS" "$(plist_print_value "$plist_path" ":EnvironmentVariables:BAA_CODEXD_SERVER_ARGS")" "$BAA_RUNTIME_DEFAULT_CODEXD_SERVER_ARGS"
143+    check_string_equals "${service}:BAA_CODEXD_SERVER_CWD" "$(plist_print_value "$plist_path" ":EnvironmentVariables:BAA_CODEXD_SERVER_CWD")" "$codexd_server_cwd"
144+    check_string_equals "${service}:BAA_CODEXD_SERVER_ENDPOINT" "$(plist_print_value "$plist_path" ":EnvironmentVariables:BAA_CODEXD_SERVER_ENDPOINT")" "$BAA_RUNTIME_DEFAULT_CODEXD_SERVER_ENDPOINT"
145+  fi
146+
147   if [[ "$service" == "status-api" ]]; then
148     check_string_equals "${service}:BAA_STATUS_API_HOST" "$(plist_print_value "$plist_path" ":EnvironmentVariables:BAA_STATUS_API_HOST")" "$status_api_host"
149   fi
M scripts/runtime/check-node.sh
+33, -8
  1@@ -14,7 +14,7 @@ Options:
  2   --node mini                  Select node defaults. Defaults to mini.
  3   --scope agent|daemon         Expected launchd scope. Defaults to agent.
  4   --service NAME               Add one service to the runtime check set. Repeatable.
  5-  --all-services               Check conductor, worker-runner, and status-api.
  6+  --all-services               Check conductor, codexd, worker-runner, and status-api.
  7   --repo-dir PATH              Repo root used to derive runtime paths.
  8   --home-dir PATH              HOME value expected in installed plist files.
  9   --install-dir PATH           Validate installed copies under this directory.
 10@@ -24,6 +24,7 @@ Options:
 11   --local-api-base URL         Conductor local API base URL. Defaults to 127.0.0.1:4317.
 12   --local-api-allowed-hosts CSV
 13                                Expected BAA_CONDUCTOR_LOCAL_API_ALLOWED_HOSTS in installed copies.
 14+  --codexd-api-base URL        codexd local API base URL. Defaults to 127.0.0.1:4319.
 15   --status-api-base URL        Status API base URL. Defaults to 127.0.0.1:4318.
 16   --status-api-host HOST       Expected BAA_STATUS_API_HOST in installed copies.
 17   --username NAME              Expected UserName for LaunchDaemons.
 18@@ -33,13 +34,12 @@ Options:
 19   --skip-static-check          Skip the underlying check-launchd.sh pass.
 20   --skip-port-check            Skip local TCP LISTEN checks.
 21   --skip-process-check         Skip host process command-line checks.
 22-  --skip-http-check            Skip conductor/status-api HTTP probes.
 23+  --skip-http-check            Skip conductor/codexd/status-api HTTP probes.
 24   --skip-log-check             Skip launchd stdout/stderr file checks.
 25   --help                       Show this help text.
 26 
 27 Notes:
 28-  The default runtime check set is conductor + status-api, because that is the
 29-  minimum on-node surface for a realistic node verification pass. Use
 30+  The default runtime check set is conductor + codexd + status-api. Use
 31   --service to narrow the scope or --all-services to include worker-runner.
 32 EOF
 33 }
 34@@ -57,10 +57,11 @@ install_dir=""
 35 shared_token=""
 36 shared_token_file=""
 37 control_api_base="${BAA_RUNTIME_DEFAULT_CONTROL_API_BASE}"
 38-local_api_base="${BAA_RUNTIME_DEFAULT_LOCAL_API}"
 39-local_api_allowed_hosts="${BAA_CONDUCTOR_LOCAL_API_ALLOWED_HOSTS:-}"
 40-status_api_base="${BAA_RUNTIME_DEFAULT_STATUS_API}"
 41-status_api_host="${BAA_STATUS_API_HOST:-127.0.0.1}"
 42+local_api_base="http://100.71.210.78:4317"
 43+local_api_allowed_hosts="${BAA_CONDUCTOR_LOCAL_API_ALLOWED_HOSTS:-100.71.210.78}"
 44+codexd_api_base="${BAA_RUNTIME_DEFAULT_CODEXD_LOCAL_API}"
 45+status_api_base="http://100.71.210.78:4318"
 46+status_api_host="${BAA_STATUS_API_HOST:-100.71.210.78}"
 47 username="$(default_username)"
 48 domain_target=""
 49 check_loaded="0"
 50@@ -129,6 +130,10 @@ while [[ $# -gt 0 ]]; do
 51       local_api_allowed_hosts="$2"
 52       shift 2
 53       ;;
 54+    --codexd-api-base)
 55+      codexd_api_base="$2"
 56+      shift 2
 57+      ;;
 58     --status-api-base)
 59       status_api_base="$2"
 60       shift 2
 61@@ -337,6 +342,7 @@ run_static_checks() {
 62     --control-api-base "$control_api_base"
 63     --local-api-base "$local_api_base"
 64     --local-api-allowed-hosts "$local_api_allowed_hosts"
 65+    --codexd-local-api-base "$codexd_api_base"
 66     --status-api-host "$status_api_host"
 67     --username "$username"
 68   )
 69@@ -455,8 +461,24 @@ check_status_api_runtime() {
 70 }
 71 
 72 local_api_base="$(normalize_base_url "$local_api_base")"
 73+codexd_api_base="$(normalize_base_url "$codexd_api_base")"
 74 status_api_base="$(normalize_base_url "$status_api_base")"
 75 
 76+check_codexd_runtime() {
 77+  local codexd_base_url="$1"
 78+  local port
 79+
 80+  if [[ "$skip_port_check" != "1" ]]; then
 81+    port="$(extract_port_from_url "codexd" "$codexd_base_url")"
 82+    check_listen_port "codexd" "$port"
 83+  fi
 84+
 85+  if [[ "$skip_http_check" != "1" ]]; then
 86+    assert_http_contains "codexd /healthz" "${codexd_base_url}/healthz" "200" "\"status\": \"ok\""
 87+    assert_http_contains "codexd /v1/codexd/status" "${codexd_base_url}/v1/codexd/status" "200" "\"mode\": \"app-server\""
 88+  fi
 89+}
 90+
 91 if [[ "$skip_static_check" != "1" ]]; then
 92   run_static_checks
 93 elif [[ "$check_loaded" == "1" ]]; then
 94@@ -476,6 +498,9 @@ for service in "${services[@]}"; do
 95     conductor)
 96       check_conductor_runtime "$local_api_base"
 97       ;;
 98+    codexd)
 99+      check_codexd_runtime "$codexd_api_base"
100+      ;;
101     status-api)
102       check_status_api_runtime "$status_api_base"
103       ;;
M scripts/runtime/common.sh
+36, -4
  1@@ -9,6 +9,13 @@ readonly BAA_RUNTIME_SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &&
  2 readonly BAA_RUNTIME_REPO_DIR_DEFAULT="$(cd -- "${BAA_RUNTIME_SCRIPT_DIR}/../.." && pwd)"
  3 readonly BAA_RUNTIME_DEFAULT_CONTROL_API_BASE="https://conductor.makefile.so"
  4 readonly BAA_RUNTIME_DEFAULT_LOCAL_API="http://127.0.0.1:4317"
  5+readonly BAA_RUNTIME_DEFAULT_CODEXD_LOCAL_API="http://127.0.0.1:4319"
  6+readonly BAA_RUNTIME_DEFAULT_CODEXD_EVENT_STREAM_PATH="/v1/codexd/events"
  7+readonly BAA_RUNTIME_DEFAULT_CODEXD_SERVER_MODE="app-server"
  8+readonly BAA_RUNTIME_DEFAULT_CODEXD_SERVER_STRATEGY="spawn"
  9+readonly BAA_RUNTIME_DEFAULT_CODEXD_SERVER_COMMAND="codex"
 10+readonly BAA_RUNTIME_DEFAULT_CODEXD_SERVER_ARGS="app-server"
 11+readonly BAA_RUNTIME_DEFAULT_CODEXD_SERVER_ENDPOINT="stdio://codex-app-server"
 12 readonly BAA_RUNTIME_DEFAULT_STATUS_API="http://127.0.0.1:4318"
 13 readonly BAA_RUNTIME_DEFAULT_LOCALE="en_US.UTF-8"
 14 
 15@@ -47,7 +54,7 @@ contains_value() {
 16 
 17 validate_service() {
 18   case "$1" in
 19-    conductor | worker-runner | status-api) ;;
 20+    conductor | codexd | worker-runner | status-api) ;;
 21     *)
 22       die "Unsupported service: $1"
 23       ;;
 24@@ -73,15 +80,26 @@ validate_node() {
 25 }
 26 
 27 default_services() {
 28-  printf '%s\n' conductor
 29+  printf '%s\n' conductor codexd status-api
 30 }
 31 
 32 default_node_verification_services() {
 33-  printf '%s\n' conductor status-api
 34+  printf '%s\n' conductor codexd status-api
 35 }
 36 
 37 all_services() {
 38-  printf '%s\n' conductor worker-runner status-api
 39+  printf '%s\n' conductor codexd worker-runner status-api
 40+}
 41+
 42+service_requires_shared_token() {
 43+  case "$1" in
 44+    codexd)
 45+      return 1
 46+      ;;
 47+    *)
 48+      return 0
 49+      ;;
 50+  esac
 51 }
 52 
 53 service_label() {
 54@@ -89,6 +107,9 @@ service_label() {
 55     conductor)
 56       printf '%s\n' "so.makefile.baa-conductor"
 57       ;;
 58+    codexd)
 59+      printf '%s\n' "so.makefile.baa-codexd"
 60+      ;;
 61     worker-runner)
 62       printf '%s\n' "so.makefile.baa-worker-runner"
 63       ;;
 64@@ -103,6 +124,9 @@ service_dist_entry_relative() {
 65     conductor)
 66       printf '%s\n' "apps/conductor-daemon/dist/index.js"
 67       ;;
 68+    codexd)
 69+      printf '%s\n' "apps/codexd/dist/index.js"
 70+      ;;
 71     worker-runner)
 72       printf '%s\n' "apps/worker-runner/dist/index.js"
 73       ;;
 74@@ -117,6 +141,9 @@ service_default_port() {
 75     conductor)
 76       printf '%s\n' "4317"
 77       ;;
 78+    codexd)
 79+      printf '%s\n' "4319"
 80+      ;;
 81     status-api)
 82       printf '%s\n' "4318"
 83       ;;
 84@@ -139,6 +166,9 @@ service_process_match() {
 85     conductor)
 86       printf '%s --host %s --role %s\n' "$dist_entry" "$conductor_host" "$conductor_role"
 87       ;;
 88+    codexd)
 89+      printf '%s start\n' "$dist_entry"
 90+      ;;
 91     *)
 92       printf '%s\n' "$dist_entry"
 93       ;;
 94@@ -309,9 +339,11 @@ resolve_runtime_paths() {
 95 
 96   printf '%s\n' \
 97     "${repo_dir}/state" \
 98+    "${repo_dir}/state/codexd" \
 99     "${repo_dir}/runs" \
100     "${repo_dir}/worktrees" \
101     "${repo_dir}/logs" \
102+    "${repo_dir}/logs/codexd" \
103     "${repo_dir}/logs/launchd" \
104     "${repo_dir}/tmp"
105 }
M scripts/runtime/install-launchd.sh
+81, -10
  1@@ -14,7 +14,7 @@ Options:
  2   --node mini               Select node defaults. Defaults to mini.
  3   --scope agent|daemon      Install under LaunchAgents or LaunchDaemons. Defaults to agent.
  4   --service NAME            Add one service to the install set. Repeatable.
  5-  --all-services            Install conductor, worker-runner, and status-api templates.
  6+  --all-services            Install conductor, codexd, worker-runner, and status-api templates.
  7   --repo-dir PATH           Repo root used for WorkingDirectory and runtime paths.
  8   --home-dir PATH           HOME value written into plist files.
  9   --install-dir PATH        Override launchd install directory.
 10@@ -24,13 +24,21 @@ Options:
 11   --local-api-base URL      Override BAA_CONDUCTOR_LOCAL_API.
 12   --local-api-allowed-hosts CSV
 13                             Override BAA_CONDUCTOR_LOCAL_API_ALLOWED_HOSTS.
 14+  --codexd-local-api-base URL
 15+                            Override BAA_CODEXD_LOCAL_API_BASE.
 16+  --codexd-event-stream-path PATH
 17+                            Override BAA_CODEXD_EVENT_STREAM_PATH.
 18+  --codexd-server-command COMMAND
 19+                            Override BAA_CODEXD_SERVER_COMMAND while keeping app-server mode.
 20+  --codexd-server-cwd PATH  Override BAA_CODEXD_SERVER_CWD.
 21   --status-api-host HOST    Override BAA_STATUS_API_HOST.
 22   --username NAME           UserName for LaunchDaemons. Defaults to the current user.
 23   --help                    Show this help text.
 24 
 25 Notes:
 26-  If no service is specified, only conductor is installed. Use --all-services or
 27-  repeat --service to opt into worker-runner/status-api templates.
 28+  If no service is specified, conductor + codexd + status-api are installed.
 29+  Use --service codexd to render codexd independently; it does not require a
 30+  shared token.
 31 EOF
 32 }
 33 
 34@@ -46,9 +54,14 @@ install_dir=""
 35 shared_token="${BAA_SHARED_TOKEN:-}"
 36 shared_token_file=""
 37 control_api_base="${BAA_RUNTIME_DEFAULT_CONTROL_API_BASE}"
 38-local_api_base="${BAA_RUNTIME_DEFAULT_LOCAL_API}"
 39-local_api_allowed_hosts="${BAA_CONDUCTOR_LOCAL_API_ALLOWED_HOSTS:-}"
 40-status_api_host="${BAA_STATUS_API_HOST:-127.0.0.1}"
 41+local_api_base="http://100.71.210.78:4317"
 42+local_api_allowed_hosts="${BAA_CONDUCTOR_LOCAL_API_ALLOWED_HOSTS:-100.71.210.78}"
 43+codexd_local_api_base="${BAA_CODEXD_LOCAL_API_BASE:-${BAA_RUNTIME_DEFAULT_CODEXD_LOCAL_API}}"
 44+codexd_event_stream_path="${BAA_CODEXD_EVENT_STREAM_PATH:-${BAA_RUNTIME_DEFAULT_CODEXD_EVENT_STREAM_PATH}}"
 45+codexd_server_command="${BAA_CODEXD_SERVER_COMMAND:-${BAA_RUNTIME_DEFAULT_CODEXD_SERVER_COMMAND}}"
 46+codexd_server_cwd="${BAA_CODEXD_SERVER_CWD:-}"
 47+codexd_server_cwd_set="0"
 48+status_api_host="${BAA_STATUS_API_HOST:-100.71.210.78}"
 49 username="$(default_username)"
 50 services=()
 51 
 52@@ -109,6 +122,23 @@ while [[ $# -gt 0 ]]; do
 53       local_api_allowed_hosts="$2"
 54       shift 2
 55       ;;
 56+    --codexd-local-api-base)
 57+      codexd_local_api_base="$2"
 58+      shift 2
 59+      ;;
 60+    --codexd-event-stream-path)
 61+      codexd_event_stream_path="$2"
 62+      shift 2
 63+      ;;
 64+    --codexd-server-command)
 65+      codexd_server_command="$2"
 66+      shift 2
 67+      ;;
 68+    --codexd-server-cwd)
 69+      codexd_server_cwd="$2"
 70+      codexd_server_cwd_set="1"
 71+      shift 2
 72+      ;;
 73     --status-api-host)
 74       status_api_host="$2"
 75       shift 2
 76@@ -136,15 +166,33 @@ if [[ "${#services[@]}" -eq 0 ]]; then
 77   done < <(default_services)
 78 fi
 79 
 80-shared_token="$(load_shared_token "$shared_token" "$shared_token_file")"
 81-if [[ -z "$shared_token" ]]; then
 82-  die "A shared token is required. Use --shared-token, --shared-token-file, or BAA_SHARED_TOKEN."
 83+services_require_shared_token() {
 84+  local service
 85+
 86+  for service in "${services[@]}"; do
 87+    if service_requires_shared_token "$service"; then
 88+      return 0
 89+    fi
 90+  done
 91+
 92+  return 1
 93+}
 94+
 95+if services_require_shared_token; then
 96+  shared_token="$(load_shared_token "$shared_token" "$shared_token_file")"
 97+  if [[ -z "$shared_token" ]]; then
 98+    die "A shared token is required. Use --shared-token, --shared-token-file, or BAA_SHARED_TOKEN."
 99+  fi
100 fi
101 
102 if [[ -z "$install_dir" ]]; then
103   install_dir="$(default_install_dir "$scope" "$home_dir")"
104 fi
105 
106+if [[ "$codexd_server_cwd_set" != "1" && -z "$codexd_server_cwd" ]]; then
107+  codexd_server_cwd="$repo_dir"
108+fi
109+
110 set -- $(resolve_node_defaults "$node")
111 conductor_host="$1"
112 conductor_role="$2"
113@@ -157,6 +205,8 @@ worktrees_dir="${repo_dir}/worktrees"
114 logs_dir="${repo_dir}/logs"
115 logs_launchd_dir="${logs_dir}/launchd"
116 tmp_dir="${repo_dir}/tmp"
117+codexd_logs_dir="${logs_dir}/codexd"
118+codexd_state_dir="${state_dir}/codexd"
119 
120 assert_directory "$state_dir"
121 assert_directory "$runs_dir"
122@@ -166,6 +216,8 @@ assert_directory "$logs_launchd_dir"
123 assert_directory "$tmp_dir"
124 
125 ensure_directory "$install_dir" "755"
126+ensure_directory "$codexd_logs_dir" "700"
127+ensure_directory "$codexd_state_dir" "700"
128 
129 for service in "${services[@]}"; do
130   template_path="$(service_template_path "$repo_dir" "$service")"
131@@ -193,16 +245,35 @@ for service in "${services[@]}"; do
132   plist_set_string "$install_path" ":EnvironmentVariables:BAA_TMP_DIR" "$tmp_dir"
133   plist_set_string "$install_path" ":EnvironmentVariables:BAA_STATE_DIR" "$state_dir"
134   plist_set_string "$install_path" ":EnvironmentVariables:BAA_NODE_ID" "$node_id"
135-  plist_set_string "$install_path" ":EnvironmentVariables:BAA_SHARED_TOKEN" "$shared_token"
136   plist_set_string "$install_path" ":StandardOutPath" "$stdout_path"
137   plist_set_string "$install_path" ":StandardErrorPath" "$stderr_path"
138   plist_set_string "$install_path" ":ProgramArguments:2" "$dist_entry"
139 
140+  if service_requires_shared_token "$service"; then
141+    plist_set_string "$install_path" ":EnvironmentVariables:BAA_SHARED_TOKEN" "$shared_token"
142+  else
143+    plist_delete_key "$install_path" ":EnvironmentVariables:BAA_SHARED_TOKEN"
144+  fi
145+
146   if [[ "$service" == "conductor" ]]; then
147     plist_set_string "$install_path" ":ProgramArguments:4" "$conductor_host"
148     plist_set_string "$install_path" ":ProgramArguments:6" "$conductor_role"
149   fi
150 
151+  if [[ "$service" == "codexd" ]]; then
152+    plist_set_string "$install_path" ":EnvironmentVariables:BAA_CODEXD_REPO_ROOT" "$repo_dir"
153+    plist_set_string "$install_path" ":EnvironmentVariables:BAA_CODEXD_LOGS_DIR" "$codexd_logs_dir"
154+    plist_set_string "$install_path" ":EnvironmentVariables:BAA_CODEXD_STATE_DIR" "$codexd_state_dir"
155+    plist_set_string "$install_path" ":EnvironmentVariables:BAA_CODEXD_MODE" "$BAA_RUNTIME_DEFAULT_CODEXD_SERVER_MODE"
156+    plist_set_string "$install_path" ":EnvironmentVariables:BAA_CODEXD_LOCAL_API_BASE" "$codexd_local_api_base"
157+    plist_set_string "$install_path" ":EnvironmentVariables:BAA_CODEXD_EVENT_STREAM_PATH" "$codexd_event_stream_path"
158+    plist_set_string "$install_path" ":EnvironmentVariables:BAA_CODEXD_SERVER_STRATEGY" "$BAA_RUNTIME_DEFAULT_CODEXD_SERVER_STRATEGY"
159+    plist_set_string "$install_path" ":EnvironmentVariables:BAA_CODEXD_SERVER_COMMAND" "$codexd_server_command"
160+    plist_set_string "$install_path" ":EnvironmentVariables:BAA_CODEXD_SERVER_ARGS" "$BAA_RUNTIME_DEFAULT_CODEXD_SERVER_ARGS"
161+    plist_set_string "$install_path" ":EnvironmentVariables:BAA_CODEXD_SERVER_CWD" "$codexd_server_cwd"
162+    plist_set_string "$install_path" ":EnvironmentVariables:BAA_CODEXD_SERVER_ENDPOINT" "$BAA_RUNTIME_DEFAULT_CODEXD_SERVER_ENDPOINT"
163+  fi
164+
165   if [[ "$service" == "status-api" ]]; then
166     plist_set_string "$install_path" ":EnvironmentVariables:BAA_STATUS_API_HOST" "$status_api_host"
167   fi
M scripts/runtime/install-mini.sh
+10, -1
 1@@ -27,7 +27,7 @@ Notes:
 2   This is the single-node mini convenience wrapper. It:
 3   1. bootstraps runtime directories
 4   2. builds the repo
 5-  3. installs conductor + status-api LaunchAgents
 6+  3. installs conductor + codexd + status-api LaunchAgents
 7   4. restarts them
 8   5. verifies the node
 9 EOF
10@@ -47,6 +47,7 @@ secrets_env=""
11 skip_build="0"
12 skip_restart="0"
13 skip_check="0"
14+codexd_api_base="${BAA_RUNTIME_DEFAULT_CODEXD_LOCAL_API}"
15 status_api_base="http://100.71.210.78:4318"
16 
17 while [[ $# -gt 0 ]]; do
18@@ -177,35 +178,41 @@ run_or_print 0 "${SCRIPT_DIR}/install-launchd.sh" \
19   --repo-dir "$repo_dir" \
20   --node mini \
21   --service conductor \
22+  --service codexd \
23   --service status-api \
24   --install-dir "$install_dir" \
25   --shared-token-file "$shared_token_file" \
26   --control-api-base "https://conductor.makefile.so" \
27   --local-api-base "http://100.71.210.78:4317" \
28   --local-api-allowed-hosts "100.71.210.78" \
29+  --codexd-local-api-base "$codexd_api_base" \
30   --status-api-host "100.71.210.78"
31 
32 if [[ "$skip_restart" != "1" ]]; then
33   run_or_print 0 "${SCRIPT_DIR}/restart-launchd.sh" \
34     --install-dir "$install_dir" \
35     --service conductor \
36+    --service codexd \
37     --service status-api
38 fi
39 
40 if [[ "$skip_check" != "1" ]]; then
41   wait_for_http "conductor" "http://100.71.210.78:4317/healthz"
42+  wait_for_http "codexd" "${codexd_api_base}/healthz"
43   wait_for_http "status-api" "${status_api_base}/healthz"
44 
45   run_or_print 0 "${SCRIPT_DIR}/check-launchd.sh" \
46     --repo-dir "$repo_dir" \
47     --node mini \
48     --service conductor \
49+    --service codexd \
50     --service status-api \
51     --install-dir "$install_dir" \
52     --shared-token-file "$shared_token_file" \
53     --control-api-base "https://conductor.makefile.so" \
54     --local-api-base "http://100.71.210.78:4317" \
55     --local-api-allowed-hosts "100.71.210.78" \
56+    --codexd-local-api-base "$codexd_api_base" \
57     --status-api-host "100.71.210.78" \
58     --check-loaded
59 
60@@ -213,11 +220,13 @@ if [[ "$skip_check" != "1" ]]; then
61     --repo-dir "$repo_dir" \
62     --node mini \
63     --service conductor \
64+    --service codexd \
65     --service status-api \
66     --install-dir "$install_dir" \
67     --shared-token-file "$shared_token_file" \
68     --local-api-base "http://100.71.210.78:4317" \
69     --local-api-allowed-hosts "100.71.210.78" \
70+    --codexd-api-base "$codexd_api_base" \
71     --status-api-base "${status_api_base}" \
72     --status-api-host "100.71.210.78" \
73     --expected-rolez leader \
M scripts/runtime/reload-launchd.sh
+69, -23
  1@@ -13,13 +13,16 @@ Usage:
  2 Options:
  3   --scope agent|daemon   launchd domain type. Defaults to agent.
  4   --service NAME         Add one service to the reload set. Repeatable.
  5-  --all-services         Reload conductor, worker-runner, and status-api.
  6+  --all-services         Reload conductor, codexd, worker-runner, and status-api.
  7   --home-dir PATH        Used only to derive the default LaunchAgents path.
  8   --install-dir PATH     Override launchd install directory.
  9   --domain TARGET        Override launchctl domain target. Defaults to gui/<uid> or system.
 10   --skip-kickstart       Skip launchctl kickstart after bootstrap.
 11   --dry-run              Print launchctl commands instead of executing them.
 12   --help                 Show this help text.
 13+
 14+Notes:
 15+  If no service is specified, conductor + codexd + status-api are reloaded.
 16 EOF
 17 }
 18 
 19@@ -200,17 +203,43 @@ print_log_tail() {
 20   tail -n 20 "$path" >&2
 21 }
 22 
 23-print_conductor_diagnostics() {
 24-  local plist_path="$1"
 25-  local label="$2"
 26-  local healthz_url="$3"
 27+read_service_healthz_url() {
 28+  local service="$1"
 29+  local plist_path="$2"
 30+  local base_url
 31+  local status_host
 32+
 33+  case "$service" in
 34+    conductor)
 35+      base_url="$(read_plist_value_or_default "$plist_path" ":EnvironmentVariables:BAA_CONDUCTOR_LOCAL_API" "$BAA_RUNTIME_DEFAULT_LOCAL_API")"
 36+      printf '%s/healthz\n' "$(normalize_url "$base_url")"
 37+      ;;
 38+    codexd)
 39+      base_url="$(read_plist_value_or_default "$plist_path" ":EnvironmentVariables:BAA_CODEXD_LOCAL_API_BASE" "$BAA_RUNTIME_DEFAULT_CODEXD_LOCAL_API")"
 40+      printf '%s/healthz\n' "$(normalize_url "$base_url")"
 41+      ;;
 42+    status-api)
 43+      status_host="$(read_plist_value_or_default "$plist_path" ":EnvironmentVariables:BAA_STATUS_API_HOST" "127.0.0.1")"
 44+      printf 'http://%s:%s/healthz\n' "$status_host" "$(service_default_port "$service")"
 45+      ;;
 46+    *)
 47+      return 1
 48+      ;;
 49+  esac
 50+}
 51+
 52+print_service_diagnostics() {
 53+  local service="$1"
 54+  local plist_path="$2"
 55+  local label="$3"
 56+  local healthz_url="$4"
 57   local stdout_path
 58   local stderr_path
 59 
 60   stdout_path="$(read_plist_value_or_default "$plist_path" ":StandardOutPath" "")"
 61   stderr_path="$(read_plist_value_or_default "$plist_path" ":StandardErrorPath" "")"
 62 
 63-  runtime_error "conductor did not recover after launchd reload"
 64+  runtime_error "${service} did not recover after launchd reload"
 65   runtime_error "health probe: ${healthz_url}"
 66   runtime_error "last curl result: exit=${CURL_LAST_EXIT_CODE} http=${CURL_LAST_HTTP_STATUS:-000}"
 67   if [[ -n "$CURL_LAST_ERROR" ]]; then
 68@@ -222,50 +251,67 @@ print_conductor_diagnostics() {
 69     runtime_error "launchctl print failed for ${domain_target}/${label}"
 70   fi
 71 
 72-  print_log_tail "conductor stdout" "$stdout_path"
 73-  print_log_tail "conductor stderr" "$stderr_path"
 74+  print_log_tail "${service} stdout" "$stdout_path"
 75+  print_log_tail "${service} stderr" "$stderr_path"
 76   runtime_error "manual recovery hint: launchctl kickstart -k ${domain_target}/${label}"
 77 }
 78 
 79-recover_conductor_after_reload() {
 80-  local service="conductor"
 81+service_has_health_probe() {
 82+  case "$1" in
 83+    conductor | codexd | status-api)
 84+      return 0
 85+      ;;
 86+    *)
 87+      return 1
 88+      ;;
 89+  esac
 90+}
 91+
 92+recover_service_after_reload() {
 93+  local service="$1"
 94   local label
 95   local plist_path
 96-  local local_api_base
 97   local healthz_url
 98 
 99-  if ! contains_value "$service" "${services[@]}"; then
100+  if ! service_has_health_probe "$service"; then
101     return 0
102   fi
103 
104   label="$(service_label "$service")"
105   plist_path="$(service_install_path "$install_dir" "$service")"
106-  local_api_base="$(read_plist_value_or_default "$plist_path" ":EnvironmentVariables:BAA_CONDUCTOR_LOCAL_API" "$BAA_RUNTIME_DEFAULT_LOCAL_API")"
107-  healthz_url="$(normalize_url "$local_api_base")/healthz"
108+  healthz_url="$(read_service_healthz_url "$service" "$plist_path")"
109 
110   if wait_for_http_healthz "$service" "$healthz_url"; then
111     return 0
112   fi
113 
114   if [[ "$skip_kickstart" == "1" ]]; then
115-    print_conductor_diagnostics "$plist_path" "$label" "$healthz_url"
116-    die "conductor stayed unhealthy after reload with --skip-kickstart"
117+    print_service_diagnostics "$service" "$plist_path" "$label" "$healthz_url"
118+    die "${service} stayed unhealthy after reload with --skip-kickstart"
119   fi
120 
121-  runtime_error "conductor was loaded but not serving; retrying launchctl kickstart -k ${domain_target}/${label}"
122+  runtime_error "${service} was loaded but not serving; retrying launchctl kickstart -k ${domain_target}/${label}"
123   if ! launchctl kickstart -k "${domain_target}/${label}"; then
124     runtime_error "retry kickstart returned non-zero for ${domain_target}/${label}"
125-    print_conductor_diagnostics "$plist_path" "$label" "$healthz_url"
126-    die "conductor retry kickstart failed after reload"
127+    print_service_diagnostics "$service" "$plist_path" "$label" "$healthz_url"
128+    die "${service} retry kickstart failed after reload"
129   fi
130 
131   if wait_for_http_healthz "$service" "$healthz_url"; then
132-    runtime_log "conductor recovered after retry kickstart"
133+    runtime_log "${service} recovered after retry kickstart"
134     return 0
135   fi
136 
137-  print_conductor_diagnostics "$plist_path" "$label" "$healthz_url"
138-  die "conductor failed to recover after reload"
139+  print_service_diagnostics "$service" "$plist_path" "$label" "$healthz_url"
140+  die "${service} failed to recover after reload"
141+}
142+
143+recover_services_after_reload() {
144+  local service
145+
146+  for service in "${services[@]}"; do
147+    recover_service_after_reload "$service"
148+  done
149 }
150 
151 for service in "${services[@]}"; do
152@@ -291,7 +337,7 @@ if [[ "$skip_kickstart" != "1" ]]; then
153 fi
154 
155 if [[ "$dry_run" != "1" ]]; then
156-  recover_conductor_after_reload
157+  recover_services_after_reload
158 fi
159 
160 runtime_log "launchd reload completed for ${domain_target}"
M scripts/runtime/start-launchd.sh
+2, -2
 1@@ -13,7 +13,7 @@ Usage:
 2 Options:
 3   --scope agent|daemon   launchd domain type. Defaults to agent.
 4   --service NAME         Add one service to the start set. Repeatable.
 5-  --all-services         Start conductor, worker-runner, and status-api.
 6+  --all-services         Start conductor, codexd, worker-runner, and status-api.
 7   --home-dir PATH        Used only to derive the default LaunchAgents path.
 8   --install-dir PATH     Override launchd install directory.
 9   --domain TARGET        Override launchctl domain target. Defaults to gui/<uid> or system.
10@@ -21,7 +21,7 @@ Options:
11   --help                 Show this help text.
12 
13 Notes:
14-  If no service is specified, conductor + status-api are started.
15+  If no service is specified, conductor + codexd + status-api are started.
16 EOF
17 }
18 
M scripts/runtime/status-launchd.sh
+39, -11
 1@@ -13,17 +13,18 @@ Usage:
 2 Options:
 3   --scope agent|daemon   launchd domain type. Defaults to agent.
 4   --service NAME         Add one service to the status set. Repeatable.
 5-  --all-services         Show conductor, worker-runner, and status-api.
 6+  --all-services         Show conductor, codexd, worker-runner, and status-api.
 7   --home-dir PATH        Used only to derive the default LaunchAgents path.
 8   --install-dir PATH     Override launchd install directory.
 9   --domain TARGET        Override launchctl domain target. Defaults to gui/<uid> or system.
10   --local-api-base URL   Conductor local API base. Defaults to http://100.71.210.78:4317
11+  --codexd-api-base URL  codexd local API base. Defaults to http://127.0.0.1:4319
12   --status-api-base URL  Status API base. Defaults to http://100.71.210.78:4318
13   --skip-http            Skip HTTP probes.
14   --help                 Show this help text.
15 
16 Notes:
17-  If no service is specified, conductor + status-api are shown.
18+  If no service is specified, conductor + codexd + status-api are shown.
19 EOF
20 }
21 
22@@ -36,6 +37,7 @@ home_dir="$(default_home_dir)"
23 install_dir=""
24 domain_target=""
25 local_api_base="http://100.71.210.78:4317"
26+codexd_api_base="${BAA_RUNTIME_DEFAULT_CODEXD_LOCAL_API}"
27 status_api_base="http://100.71.210.78:4318"
28 skip_http="0"
29 services=()
30@@ -77,6 +79,10 @@ while [[ $# -gt 0 ]]; do
31       local_api_base="$2"
32       shift 2
33       ;;
34+    --codexd-api-base)
35+      codexd_api_base="$2"
36+      shift 2
37+      ;;
38     --status-api-base)
39       status_api_base="$2"
40       shift 2
41@@ -119,8 +125,10 @@ for service in "${services[@]}"; do
42   printf 'label: %s\n' "$label"
43   printf 'plist: %s\n' "$plist_path"
44 
45-  if [[ -f "$plist_path" ]]; then
46+  if [[ -f "$plist_path" ]] && plutil -lint "$plist_path" >/dev/null 2>&1; then
47     printf 'plist_lint: ok\n'
48+  elif [[ -f "$plist_path" ]]; then
49+    printf 'plist_lint: invalid\n'
50   else
51     printf 'plist_lint: missing\n'
52   fi
53@@ -135,12 +143,32 @@ for service in "${services[@]}"; do
54 done
55 
56 if [[ "$skip_http" != "1" ]]; then
57-  printf '=== http ===\n'
58-  printf 'conductor healthz: '
59-  curl -fsS "${local_api_base%/}/healthz" || true
60-  printf '\nrolez: '
61-  curl -fsS "${local_api_base%/}/rolez" || true
62-  printf '\nstatus-api /v1/status: '
63-  curl -fsS "${status_api_base%/}/v1/status" || true
64-  printf '\n'
65+  for service in "${services[@]}"; do
66+    case "$service" in
67+      conductor)
68+        printf '=== conductor http ===\n'
69+        printf 'healthz: '
70+        curl -fsS "${local_api_base%/}/healthz" || true
71+        printf '\nrolez: '
72+        curl -fsS "${local_api_base%/}/rolez" || true
73+        printf '\n\n'
74+        ;;
75+      codexd)
76+        printf '=== codexd http ===\n'
77+        printf 'healthz: '
78+        curl -fsS "${codexd_api_base%/}/healthz" || true
79+        printf '\nstatus: '
80+        curl -fsS "${codexd_api_base%/}/v1/codexd/status" || true
81+        printf '\n\n'
82+        ;;
83+      status-api)
84+        printf '=== status-api http ===\n'
85+        printf 'healthz: '
86+        curl -fsS "${status_api_base%/}/healthz" || true
87+        printf '\n/v1/status: '
88+        curl -fsS "${status_api_base%/}/v1/status" || true
89+        printf '\n\n'
90+        ;;
91+    esac
92+  done
93 fi
M scripts/runtime/stop-launchd.sh
+2, -2
 1@@ -13,7 +13,7 @@ Usage:
 2 Options:
 3   --scope agent|daemon   launchd domain type. Defaults to agent.
 4   --service NAME         Add one service to the stop set. Repeatable.
 5-  --all-services         Stop conductor, worker-runner, and status-api.
 6+  --all-services         Stop conductor, codexd, worker-runner, and status-api.
 7   --home-dir PATH        Used only to derive the default LaunchAgents path.
 8   --install-dir PATH     Override launchd install directory.
 9   --domain TARGET        Override launchctl domain target. Defaults to gui/<uid> or system.
10@@ -21,7 +21,7 @@ Options:
11   --help                 Show this help text.
12 
13 Notes:
14-  If no service is specified, conductor + status-api are stopped.
15+  If no service is specified, conductor + codexd + status-api are stopped.
16 EOF
17 }
18