支持为 Codex 子代理指定模型且保留完整上下文继承
- Dominant language
- TypeScript
- Stars
- 2.7k
- Forks
- 395
- Avg merge
- 21h 48m
- Merged PRs (30d)
- 776
Description
## 背景
Cindy 希望在「设置 -> 个性化 -> 子代理模型」中分别指定 Claude 与 Codex 的 subagent 默认模型:
- 配置非空:subagent 固定使用指定模型;
- 留空:不注入任何覆盖,完全保留 agent 原生行为;
- 设置从新建 session 开始生效,不要求运行中的 session 热切换。
Claude 可以通过 `CLAUDE_CODE_SUBAGENT_MODEL` 确定性实现。Codex 当前 pin 为 `0.144.1`(`rust-v0.144.1`),无法在不改变现有上下文继承语义的前提下完成同等能力。
## 已验证的现状与根因
当前 Cindy 使用 Codex MultiAgent V2,默认 `spawn_agent` 采用 `fork_turns="all"`,即 subagent 完整继承父 agent 的历史上下文。
核对 Codex `rust-v0.144.1` 源码后确认:
1. MultiAgent V2 内部的 spawn 参数具备 `model` / `reasoning_effort` 表达能力;
2. Cindy 当前拿到的 `spawn_agent` 工具 schema 不暴露这两个字段;
3. 更关键的是,完整 history fork 路径会拒绝覆盖 `agent_type`、`model`、`reasoning_effort`;
4. custom agent role/default role 在完整 history fork 路径也不会应用模型覆盖。
相关上游实现:
- [`multi_agents_v2/spawn.rs`](https://github.com/openai/codex/blob/rust-v0.144.1/codex-rs/core/src/tools/handlers/multi_agents_v2/spawn.rs)
- [`multi_agents_common.rs`](https://github.com/openai/codex/blob/rust-v0.144.1/codex-rs/core/src/tools/handlers/multi_agents_common.rs)
- [`multi_agents_spec.rs`](https://github.com/openai/codex/blob/rust-v0.144.1/codex-rs/core/src/tools/handlers/multi_agents_spec.rs)
因此,这不是补一个 Cindy UI 字段或修改 Codex config 就能解决的问题。当前版本无法同时满足以下三个条件:
1. 固定 Codex subagent 模型;
2. 完整保留父 agent 上下文;
3. 使用代码级确定性配置,不依赖 prompt。
## 必须守住的产品语义
- 不改变当前默认 `fork_turns="all"` 的完整上下文继承;
- 不通过 system prompt 暗示模型自行选择;
- 不把模型选择权暴露给 LLM;这是 host/user setting,不是每次 tool call 的自由参数;
- 配置留空时行为与当前版本逐字等价;
- 不静默截断历史;目标模型上下文不足时应明确失败或采用另行确认的压缩策略;
- 第一阶段只处理 model,不同时扩展 reasoning effort。
## 已排除的兼容方案
### 1. 动态修改 system prompt
不可接受。行为不确定,还会影响 prompt cache;并且 Cindy 的 system prompt 修改需要单独评审确认。
### 2. 使用 custom role/default role
不可行。完整 history fork 路径不会应用该模型覆盖。
### 3. 强制 `fork_turns="none"` 或有限历史
技术上可以让 model override 生效,但会丢失父上下文,改变现有 subagent 语义,不能作为默认实现。
### 4. 切回 MultiAgent V1
影响工具行为、上下文继承和 UI,回退面过大。
### 5. 仅在 Cindy 外层拦截 `spawn_agent`
当前 spawn/fork 在 Codex 原生二进制内部完成,Cindy 无法仅靠 app-server 配置或 wrapper 在不修改二进制的情况下安全替换 child model。
## 推荐方案
### Phase A:补齐 Codex 上游/内部分支能力
在 Codex 增加 host 级的可选默认配置,例如 `multi_agent.default_subagent_model`(最终命名按上游规范确定):
- `None` / 未配置:保持当前 fork 行为;
- 非空:创建 subagent 时完整复制父 history,但 child 使用该默认模型;
- 该值来自 host config,不需要暴露为 LLM 可填写的 `spawn_agent` 参数;
- 优先级建议:合法的显式 spawn override > host 默认 subagent model > 父 agent model;对于完整 history fork,可继续禁止 LLM 显式 override,只允许 host 默认值;
- 保留 agent type、权限、sandbox、tool 状态和完整 turn history;仅改变 child 的 model runtime;
- 目标模型不存在、无权限或上下文窗口不足时返回结构化错误,不静默回退到父模型、不静默截断历史;
- telemetry / thread metadata 必须记录 child 实际模型,便于排查和计费。
实现上应在 fork/spawn 的核心路径支持“复制历史 + 指定 child model”,而不是通过 prompt、custom role 或 Cindy 侧篡改 tool call 绕过限制。
### Phase B:Cindy 接入
Codex 新二进制可用后:
1. 更新 `tools/codex/latest.json` pin;
2. 在 main 的 subagent model settings store 中读取 Codex 值;
3. 通过 maker-core Codex runtime config/app-server config 注入默认 subagent model;
4. 启用 Settings 中当前禁用的 Codex 模型选择器;
5. 留空时不写任何 Codex override;
6. 仅对新建 Codex session 生效,已有 session 不热切换。
## 验收标准
- [ ] 父 agent 使用模型 A,host 默认 subagent model 配置为 B,默认完整 fork 后 child 实际使用 B;
- [ ] child 获得与改动前一致的完整父历史,消息内容和顺序无丢失;
- [ ] 配置留空时 child 继续继承 A,行为与 `0.144.1` 保持一致;
- [ ] LLM 无法通过 `spawn_agent` 自行覆盖 host 指定的默认模型;
- [ ] 配置的模型不存在或无权限时返回清晰错误;
- [ ] 父历史超过 B 的上下文窗口时不静默截断,返回明确错误或执行经过确认的压缩策略;
- [ ] child thread/usage/日志记录实际模型 B;
- [ ] Cindy 设置持久化、恢复“不指定”和重启恢复均有测试;
- [ ] 覆盖 `fork_turns="all"`、`fork_turns="none"` 及 resume/new-session 回归测试;
- [ ] 不修改 Cindy system prompt,不降低主 agent prompt cache 命中率,不在 event/token 热路径增加额外网络请求。
## 非目标
- 本 Issue 不开放每次 `spawn_agent` 由模型动态选择 child model;
- 本 Issue 第一阶段不增加 subagent reasoning effort 设置;
- 本 Issue 不要求对运行中的 Codex session 热更新。
Contributor guide
Research direction
Start by reading the pinned Codex sources in multi_agents_v2/spawn.rs, multi_agents_common.rs, and multi_agents_spec.rs to understand the full-history fork restriction. Then trace tools/codex/latest.json, the main subagent model settings store, and maker-core runtime configuration for Cindy integration. Done means host-configured model selection preserves full history, empty settings preserve current behavior, and the listed fork, session, persistence, and error cases have regression coverage.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust, typescript
- Domain
- ai, tooling
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 25/100