# R2.7.4 任务报告

## 调查范围

- 官方仓库：`Wei-Shaw/sub2api`。
- 最新主分支：`b1a6b8026764dedf6a5f76d02bdc292468d22753`。
- 当前部署相关版本：`v0.1.159`，对应提交 `2a75d7d2387587d86ca3c5e5cd8ca96cf3d104c6`。
- 调查只读取官方源码与 Codex 官方手册，没有修改 Sub2API、UniDesk CLI、配置、版本或运行面。

## 结论

- 现有 Sub2API 不能按下游 API Key 配置或自动加载 Codex skill。
- 真正的 Codex skill 是客户端文件能力：
  - Codex 从 repo `.agents/skills`、用户 `$HOME/.agents/skills`、管理员 `/etc/codex/skills` 等位置发现 skill；
  - 首先加载名称、描述和路径，命中后再读取完整 `SKILL.md`、references 与 scripts；
  - API 请求中的 `instructions` 只能附加文本，不能把 skill 文件、脚本、参考资料或插件安装到员工机器。
- 因此“请求时由 API Key 自动加载真正 skill”不符合 Codex 当前发现模型。真正 skill 必须在 Codex 启动或会话开始前已经安装或随仓库存在。
- 如果“skill”实际只指强制公司提示词，则可以设计按 Key 的 instruction profile，但最新版仍缺该能力，严格按 Key 配置至少需要外部网关或 Sub2API 源码扩展。

## 官方源码证据

- API Key 鉴权已经具备请求级身份：
  - 中间件按 Bearer Key 查询完整 API Key；
  - 成功后把 `*service.APIKey` 放入 Gin context；
  - Responses handler 可读取 `apiKey.ID`、名称、用户和分组。
- API Key 数据模型当前没有 skill、instruction profile 或 metadata 字段：
  - 只有名称、分组、状态、IP 规则、配额、有效期和限速；
  - 用户与管理员 API Key CRUD 也没有对应字段。
- API Key 鉴权使用 L1/L2 快照缓存：
  - 当前快照版本为 15；
  - 若新增按 Key profile，必须进入最小查询、快照、版本升级和更新后的跨实例缓存失效链。
- `v0.1.159` 和最新 main 已有全局
  `gateway.forced_codex_instructions_template_file`：
  - 模板启动时读盘并缓存；
  - 模板上下文只有已有 instructions 和四个模型字段；
  - 没有 API Key ID、名称、用户、分组或 profile；
  - 代码只在 `/v1/messages` 转 OpenAI OAuth Responses 的兼容路径调用；
  - 原生 `/v1/responses` 只在 instructions 为空时补默认 Codex instructions，没有调用该强制模板。
- 所以该原生配置可以做全局、文本型、特定兼容路径注入，但不能满足每 Key 配置，也不能代表真正加载 skill。

## 候选方案

### 方案 A：零 Sub2API 源码改动，客户端分发真正 skill，推荐

- 公司级通用 skill：
  - 打包为 Codex plugin；
  - 发放 API Key 时同时给出一次安装或受控 bootstrap；
  - 安装到用户或管理员 skill 目录，后续由 Codex 隐式匹配。
- 项目级 skill：
  - 直接随公司代码仓库提交到 `.agents/skills/<name>/SKILL.md`；
  - 员工 clone 仓库后自动进入 Codex skill 发现范围。
- 永久、每次都必须执行的规则使用 `AGENTS.md`，不要包装成按请求触发的 skill。
- 每 Key 差异由发 Key 的管理流程选择不同 plugin/profile 包，而不是让模型请求携带或解析 Key。
- 优点：
  - 无 Sub2API 修改；
  - 是真正的 progressive-disclosure skill；
  - scripts、references、MCP 依赖都能工作；
  - 不增加每次请求 Token、TTFT 或网关改写风险。
- 限制：
  - API Key 本身不会远程改变已经启动的 Codex 会话；
  - skill 更新需要客户端自动同步、插件更新或重新启动/刷新 Codex。

### 方案 B：零 Sub2API 源码改动，外部网关按 Key 注入 instructions，不推荐作为 skill

- 在 Sub2API 前增加薄反向代理：
  - 按 Bearer Key 的哈希映射 instruction profile；
  - 修改 Responses JSON 的 `instructions`；
  - 保留客户端原 instructions。
- 优点：
  - 不改 Sub2API；
  - 可以真正按 Key 选择文本。
- 缺点：
  - 只是提示词注入，不是 skill；
  - 必须覆盖 `/v1/responses`、compact、WebSocket、`/v1/messages` 和错误/流式语义；
  - 新增敏感 Key 处理面、请求体改写面和运维故障点；
  - 不能提供本地 scripts、references 或 MCP；
  - 不应作为首选。

### 方案 C：最小 Sub2API 源码扩展，按 Key instruction profile

- 适用于只需要按 Key 强制公司文本规则，不需要真正 skill 资产。
- 推荐数据模型：
  - API Key 只保存 nullable `codex_instruction_profile` 标识；
  - profile 到模板文件的映射由服务配置管理；
  - 不把大段提示词复制进每条 API Key 或鉴权缓存。
- 最小改动面：
  - migration 与 Ent APIKey schema；
  - service/repository/DTO；
  - 管理员 API Key CRUD，默认不允许员工自行切换 profile；
  - auth 最小查询与快照字段，提升快照版本并复用缓存失效；
  - 在安全审计前对原生 Responses、compact 和 messages bridge 统一组合 instructions；
  - 保留客户端 `ExistingInstructions`，输出只记录 profile ID 和内容 hash，不记录正文。
- 该方案可复用现有 Go template 渲染器，但必须扩展到原生 Responses，并传入 API Key profile。
- 这仍应命名为 instruction profile，而不是 skill auto-load。

### 方案 D：真正按 Key 管理 skill，完整方案

- Sub2API 保存每 Key 的 `skill_profile_id`。
- 增加经 API Key 鉴权的只读 manifest：
  - profile ID、版本、兼容范围、bundle URL、SHA-256、签名和 ETag；
  - 不直接返回或记录完整 API Key。
- 配套 Codex bootstrap/plugin 在启动时：
  - 用 Key 获取 manifest；
  - 验证签名与 hash；
  - 原子同步到用户或管理员 skill 目录；
  - 完成后启动/刷新 Codex。
- 这能满足每 Key 管理和真正 skill，但同时涉及 Sub2API、分发服务、签名制品和客户端工具，不属于最小修改。

## 推荐顺序

1. 首选方案 A：
   - 公司通用工作流做 plugin；
   - 项目工作流放 repo `.agents/skills`；
   - 必须始终生效的规则放 `AGENTS.md`；
   - API Key 发放流程同时选择并安装相应公司 plugin。
2. 若确实需要管理员在服务端立即控制每 Key 的文本规则，再做方案 C，并明确叫 instruction profile。
3. 只有需要按 Key 远程版本管理 scripts/references/MCP 时才做方案 D。
4. 不建议用 API Key 名称承载 profile、把完整 skill 塞入每次 instructions、让模板按原始 Key 分支，或把 skill bundle 暴露为持 Key 即可下载的无签名资源。

## 安全与行为边界

- API Key 是计费与访问凭据，不应成为可日志化的 skill selector 或下载 URL 参数。
- 提示词注入不是策略强制；机械约束仍应放 hooks、CI、权限和审批。
- skill scripts 在员工机器执行，必须固定来源、签名和 digest，更新需要可回退。
- 每次请求动态拉 skill 会增加 TTFT、可用性依赖和 Token 成本，也与 Codex 会话启动时的 skill 发现模型冲突。
- 任何网关注入必须保留客户端 instructions，并确保安全审计看到最终请求内容。
