Files

174 lines
22 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# v0.1 Codex Stdio Backend/Profile 规格
Codex stdio backend 是 AgentRun `v0.1` 的第一真实 Code Agent backend kind。它用于证明 runner、backend adapter、Kubernetes Secret projection、真实 provider 调用、event normalization 和 terminal status 的完整闭环。`v0.1` 在同一个 backend kind 下支持 `codex``deepseek``minimax-m3``dsflash-go` 四个 profile;四者共享 Codex CLI app-server stdio 协议,只通过 profile/config/SecretRef/model catalog 隔离上游和模型。
## 在系统中的职责划分
- 作为 `backendProfile=codex``backendProfile=deepseek``backendProfile=minimax-m3``backendProfile=dsflash-go` 的共同具体实现。
- 使用真实 Codex/Codex-compatible 配置执行最短 turn,不使用 fake provider 作为综合联调通过证据。
- 消费 Kubernetes Secret projection 提供的 profile 专属 Codex `auth.json``config.toml` 和 profile-local `model-catalog.json`
- 把 Codex 输出归一化为 AgentRun 标准 events 和 terminal status。
- 将 provider/auth/protocol/timeout/cancel 错误映射为 [spec-v01-backend-adapter.md](spec-v01-backend-adapter.md) 定义的 failureKind。
## 协议选型与实现参考
`v0.1` Codex stdio backend 的协议固定为 Codex CLI app-server JSON-RPC over stdio。Backend adapter 必须启动受控子进程:
```bash
codex app-server --listen stdio://
```
Adapter 通过 stdin 写入换行分隔 JSON-RPC 请求,通过 stdout 逐行读取 JSON-RPC response 和 notificationstderr 只作为有界诊断日志。最小请求序列是 `initialize``thread/start``thread/resume``turn/start`response 中必须提取 thread/turn identitynotification 和后续输出必须归一化为 `backend_status``assistant_message``tool_call``command_output``error``terminal_status` events。运行中 steer 使用同一 app-server 进程的 `turn/steer` JSON-RPC 方法,参数为 `threadId``expectedTurnId` 和文本 `input` 数组;取消/中断使用 `turn/interrupt`,参数为 `threadId``turnId`。已有 `SessionRef.threadId` 时只能执行 Codex stdio 原生 `thread/resume` 后接 `turn/start`;当 `thread/resume` 返回 `no rollout found for thread id` 或任何其他协议错误时,adapter 必须输出 `thread-resume-failed` 并终止当前 turn。adapter 不得启动替代 `thread/start`、拼接历史 prompt、回写新 threadId 或用其他上下文模拟继续会话。
若 run 的 `ResourceBundleRef` 包含 `promptRefs` 或 gitbundle `.agents/skills`Codex adapter 只能消费 runner 已装配好的有界 `initialPrompt`、skill summary 和 skill registry path。对新 threadadapter 在首个 `turn/start` 中把 `initialPrompt` 和 skill facts 放在用户 message 之前;对 `thread/resume`adapter 不重复注入 `initialPrompt`,只发送当前 command 的用户 message。当前 Codex app-server 若只有 `input: [{ type: "text", text }]`,允许使用结构化文本前缀承载 initial prompt;若后续 app-server 支持 developer/runtime instruction item,优先映射到该标准 item。无论哪种 wire shapeevents 只记录 prompt/skill 的 path/hash/bytes/injected 状态,不输出全文。
不得把以下路径作为 `v0.1` Codex stdio backend 的正式实现或综合联调通过证据:直接 Responses HTTP 代理、OpenAI SDK wrapper、`codex exec` 一次性命令输出、fake provider、固定文本回复、只读 shortcut 或本地 shell 模拟。裸 HTTP 或 `codex exec --json` 可以作为 provider/upstream 诊断,但最终通过必须来自 app-server stdio turn。
实现必须参考成熟代码:
| 参考 | 需要吸收的经验 |
| --- | --- |
| UniDesk `src/components/microservices/code-queue/src/code-agent/codex.ts` | Bun/TS 中 spawn `codex app-server --listen stdio://`、JSON-RPC request/response、thread start/resume、turn start、stderr 有界采集、exit/failure 分类。 |
| UniDesk `src/components/microservices/code-queue/src/code-agent/common.ts` | backend port/capability、model/profile 边界、文本 input shape、Git/proxy env 处理和 provider 端口归一化。 |
| HWLAB `internal/cloud/codex-stdio-session.mjs` | long-lived stdio session readiness、Codex home/workspace/protocol gate、child env redaction、trace recorder、cancel/timeout/failure kind。 |
| HWLAB `scripts/code-agent-chat-smoke.mjs` | fake app-server 自测试方式、`thread/start` + `turn/start` 调用顺序、session reuse、tool trace 和 Secret 不泄露断言。 |
| HWLAB `docs/reference/spec-v02-deepseek-proxy.md` | DeepSeek profile 通过稳定 bridge/Responses-compatible 入口接入,而不是把 DeepSeek 做成绕过 Codex stdio 的直接 HTTP backend。 |
| HWLAB `docs/reference/code-agent-chat-readiness.md` | provider/profile 切换时按 profile overlay、认证、网络、模型、Codex CLI/app-server 分层验证,并防止一个 profile 的成功掩盖另一个 profile 的退化。 |
这些参考用于协议和质量标准,不复制 UniDesk/HWLAB 的业务 prompt、硬件路径、tenant policy、hostPath Secret 做法或任何明文密钥。
业务 prompt 与 skill 必须通过 [spec-v01-runtime-assembly.md](spec-v01-runtime-assembly.md) 的 `ResourceBundleRef.promptRefs` 和 gitbundle `.agents/skills` 完成。Codex backend 不内建 HWLAB 或 UniDesk 的业务文本;缺少 required prompt 时必须由 runner/manager 返回装配 blocker,不能落到 Codex 默认 skill 列表、用户长 prompt 或文本 fallback。
## v0.1 Profile 定义
| backendProfile | SecretRef | 配置来源 | 规则 |
| --- | --- | --- | --- |
| `codex` | `agentrun-v01-provider-codex` | operator 当前 Codex `auth.json`/`config.toml` | 现有默认 profile;实现 DeepSeek 时不得改变其默认模型、config authority 或真实联调路径。 |
| `deepseek` | `agentrun-v01-provider-deepseek` | operator 准备的 DeepSeek-compatible Codex `auth.json`/`config.toml` | 使用同一 `codex app-server --listen stdio://` 协议,通过 `config.toml` 或等价 profile overlay 指向 DeepSeek-compatible upstream/model。 |
| `minimax-m3` | `agentrun-v01-provider-minimax-m3` | 从 HWLAB Code Queue 现有 MiniMax API key 派生的 MiniMax-M3 Codex `auth.json`/`config.toml` | 沿 DeepSeek 相同路径使用 `codex app-server --listen stdio://``config.toml` 指向 MiniMax OpenAI-compatible upstream,模型为 `MiniMax-M3`wire API 必须使用当前 Codex app-server 支持的 `responses`,不得继续使用已废弃的 `chat`。 |
| `dsflash-go` | `agentrun-v01-provider-dsflash-go` | DeepSeek V4 Flash / OpenCode Zen Go Codex `auth.json`/`config.toml`/`model-catalog.json` | 沿 DeepSeek 相同路径使用 `codex app-server --listen stdio://``config.toml` 必须使用 `deepseek-v4-flash``model_context_window=1000000``model_auto_compact_token_limit=900000` 和固定 `model_catalog_json=/home/agentrun/.codex-dsflash-go/model-catalog.json`。 |
`deepseek` 的上游形态借鉴 HWLAB v0.2DeepSeek 是 provider profile,通过 Responses-compatible bridge、Moon Bridge 或等价稳定服务暴露给 Codex CLIAgentRun 不在 backend adapter 里手写 DeepSeek HTTP 转换器,也不把 DeepSeek 作为绕过 Codex app-server 的独立 backend kind。`minimax-m3` 也遵循同一原则:MiniMax-M3 是 Codex-compatible provider profile,不恢复旧 UniDesk Code Queue 的 MiniMax/OpenCode 直连路线,不新增独立 HTTP backend,不作为 fallback 或 judge backend。`dsflash-go` 是同一 Codex profile 机制下的 DeepSeek V4 Flash 1M context profile,必须通过 Moon Bridge / OpenCode Zen Go compatible path 和 model catalog 暴露模型元数据;若 compact path 不支持,adapter 必须输出 `provider-compact-unsupported`。上游 base URL、模型和 provider 名称可以作为 redacted metadata 输出;API Key、`auth.json``config.toml``model-catalog.json` 原文不得输出。
Profile 切换规则:
- `backendProfile` 是 run 的显式字段,manager 不得静默改写。
- runner/backend 只读取与 `backendProfile` 同名的 provider credential;缺失则 `secret-unavailable`
- 每次 run 必须使用 profile-scoped writable `CODEX_HOME`。Kubernetes Job 默认把选中 profile 的 Secret projection 复制到该 Job 独占的 `/home/agentrun/.codex-<profile>`host process 或复用进程必须使用 run/profile 独占目录,避免 `codex``deepseek``minimax-m3``dsflash-go` 互相污染。
- `deepseek``minimax-m3``dsflash-go` 不得 fallback 到 `codex` Secret、模型或 upstream`codex` 也不得读取其他 profile Secret。
- command payload 中显式提供 model 时可以透传给 Codex turn;未显式提供时以 profile `config.toml` 为 authority,不在 adapter 中写死默认模型。
## 测试凭据来源
`v0.1` 综合联调用的 Codex stdio profile 测试凭据源固定为 operator 环境中的以下两个文件形态:
```text
~/.codex/auth.json
~/.codex/config.toml
```
这些文件只能作为 Kubernetes Secret 创建或轮换的输入源,不能通过 hostPath 挂载进 Pod,不能复制进镜像,不能提交到 source branch、GitOps branch、artifact catalog、issue、PR、event、trace、日志或 CLI 输出。`codex``deepseek``minimax-m3``dsflash-go` 可以来自不同 operator profile 目录或显式文件参数,但进入 Kubernetes 后必须是不同 SecretRef,除非后续规格明确批准某个共享 SecretRef 场景。`minimax-m3` 的 API key 输入源为 HWLAB Code Queue 现有 MiniMax secret`dsflash-go``model-catalog.json` 可由 AgentRun manager 生成或由 operator 显式提供;迁移时只允许把值写入 Kubernetes Secret,不得打印或落库。
`v0.1` 默认 Kubernetes Secret
| 对象 | v0.1 规格 |
| --- | --- |
| Namespace | `agentrun-v01` |
| Secret name | `agentrun-v01-provider-codex``agentrun-v01-provider-deepseek``agentrun-v01-provider-minimax-m3``agentrun-v01-provider-dsflash-go` |
| Secret key | `auth.json`,来自 `~/.codex/auth.json` |
| Secret key | `config.toml`,来自 `~/.codex/config.toml` |
| Secret key | `model-catalog.json`,仅 `dsflash-go` 必需 |
| Consumer | runner 或 backend adapter Pod |
| Projection target | 只读 projection,再复制到当前 run/profile 的 writable `CODEX_HOME/auth.json``CODEX_HOME/config.toml` 和 profile 需要的额外文件 |
| File mode | 只读,建议 `0400` 或等价最小权限 |
Kubernetes Secret 的创建、轮换和权限控制属于集群密钥管理流程;source branch 只声明 SecretRef 名称、key 和 mount intent。`deploy/deploy.json` 和 rendered GitOps manifest 不得包含 Secret data。
## Runtime 行为
- Adapter 必须在调用 Codex 前验证 `auth.json``config.toml` 均存在且可读;`config.toml` 声明 `model_catalog_json` 时还必须验证目标文件可读;`dsflash-go` 缺少 `model_catalog_json` 时也必须在 provider 调用前返回 `secret-unavailable`
- Codex 运行时必须使用被投影的 `.codex` 目录;不得 fallback 到镜像内默认凭据或节点宿主机 `~/.codex`
- Codex stdio backend 不得设置 turn/session/conversation 的总时长 timeout`executionPolicy.timeoutMs` 只能作为无 app-server 响应、无 notification、无 assistant/tool/event activity 的 idle timeout。长程任务只要持续产生可见 activity,就必须继续等待 `turn/completed`、取消或真实 transport failure。
- idle timeout 的活动源至少包括 Codex app-server notification、assistant/message delta、tool call 状态、command output、stderr/diagnostic event 和 backend status event;这些活动必须刷新 `lastActivityAt/lastActivitySeq`。只有超过 idle budget 没有任何活动时,才允许把当前 command 归为 idle timeoutresult/session 仍必须标记 `timeoutKind="idle"``hardTimeout=false`,避免调用方把它误判成固定 wall-clock backend-timeout。
- 普通 turn command 失败只终结当前 command,不得把 reusable run/session 置为 terminal;后续 command 必须仍可进入同一个 run/runner。只有显式 cancel、runner lease/claim 失效、资源装配不可恢复或运行面退出才允许终结 run。
-`config.toml` 指向 hyueapi 或其他 OpenAI-compatible upstreamrunner/backend Pod 的 proxy 与 `NO_PROXY` 必须保持该配置可用;不得在日志中打印完整 auth/config 内容。
- 模型名、provider profile、upstream host 可以作为 redacted metadata 输出;provider credential、token、Authorization header 和文件内容不得输出。
- 一个最短 turn 至少要产生 `backend_status`、一个 assistant 或 error event、以及 `terminal_status`
## 与 Secret 分发规格的关系
[spec-v01-secret-distribution.md](spec-v01-secret-distribution.md) 是 SecretRef、Kubernetes projection、redaction 和 missing secret failure 的权威。本文件只定义 Codex backend 对测试凭据文件的消费方式。
Run 的 `executionPolicy.secretScope` 应引用与 `backendProfile` 匹配的 provider SecretRef 的 `auth.json``config.toml` 和 profile 需要的额外文件,尤其是 `dsflash-go``model-catalog.json`,而不是携带 provider credential 或文件内容。
## 测试规格
### T1 Codex Secret projection
阅读 `AGENTS.md`、本文和 [spec-v01-secret-distribution.md](spec-v01-secret-distribution.md),然后在 `agentrun-v01` 中通过 Kubernetes Secret 管理把 operator 的 `~/.codex/auth.json``~/.codex/config.toml` 注入为 `agentrun-v01-provider-codex`。确认 runner/backend Pod 只能看到投影后的 `~/.codex/auth.json``~/.codex/config.toml`,没有 hostPath,日志和 event 不显示文件内容。
### T2 真实 Codex 最短 turn
阅读本文和 [spec-v01-validation.md](spec-v01-validation.md),然后用 `backendProfile=codex` 创建真实 run 并提交一个最短 `turn` command。确认 runner 调用真实 Codex providermanager 可查询 backend_status、assistant 或 error event、terminal_status,且 Secret value 未泄露。
### T2b 真实 DeepSeek profile 最短 turn
阅读本文、HWLAB v0.2 DeepSeek profile 参考和 [spec-v01-validation.md](spec-v01-validation.md),然后用 `backendProfile=deepseek` 创建真实 run 并提交一个最短 `turn` command。确认 runner 仍调用 `codex app-server --listen stdio://`,但使用 `agentrun-v01-provider-deepseek` 的 profile SecretRef 和独立 `CODEX_HOME`manager 可查询 profile 为 `deepseek` 的 backend_status、assistant 或 error event、terminal_status,且 Secret value 未泄露。
### T2c 真实 MiniMax-M3 profile 最短 turn
阅读本文、[spec-v01-secret-distribution.md](spec-v01-secret-distribution.md) 和 [spec-v01-validation.md](spec-v01-validation.md),然后用 `backendProfile=minimax-m3` 创建真实 run 并提交一个最短 `turn` command。确认 runner 仍调用 `codex app-server --listen stdio://`,但使用 `agentrun-v01-provider-minimax-m3` 的 profile SecretRef 和独立 `CODEX_HOME`manager 可查询 profile 为 `minimax-m3` 的 backend_status、assistant 或 error event、terminal_status,且 Secret value 未泄露。
### T2d 真实 dsflash-go profile 最短 turn
阅读本文、[spec-v01-provider-profile-management.md](spec-v01-provider-profile-management.md) 和 [spec-v01-validation.md](spec-v01-validation.md),然后用 `backendProfile=dsflash-go` 创建真实 run 并提交一个最短 `turn` command。确认 runner 仍调用 `codex app-server --listen stdio://`,但使用 `agentrun-v01-provider-dsflash-go` 的 profile SecretRef、独立 `CODEX_HOME``model-catalog.json`manager 可查询 profile 为 `dsflash-go`、model 为 `deepseek-v4-flash`、context window 为 1M 的 backend_status、assistant 或 error event、terminal_status,且 Secret value 未泄露。
### T3 Missing auth/config failure
阅读本文,然后分别移除或改名 Secret 中的 `auth.json``config.toml` key,启动真实 run。确认 adapter 在调用 provider 前失败为 `secret-unavailable`failure response 为 JSON,日志不包含 Secret value。
### T4 Provider auth failure
阅读本文,然后使用无效的 Codex Secret 创建 run。确认 backend 返回 `provider-auth-failed` 或等价 failureKind,记录上游状态分类和 trace correlation,但不打印 Authorization header、token 或 auth/config 文件内容。
### T5 Provider availability failure
阅读本文,然后用 mock/fake Codex app-server 自测试 HTTP 503 `Service Unavailable`、携带 5xx 的 `responseStreamDisconnected``method=error` retry notification 中 `willRetry=true` 且嵌套 `codexErrorInfo.responseStreamDisconnected.httpStatusCode=503` 的结构,或明确 temporary/provider unavailable 文案。确认 Codex adapter 返回 `provider-unavailable`,不会落到 `backend-failed`;综合联调若真实 provider 返回同类错误,应记录为外部 provider blocker,而不是本地 runner/backend 执行面 blocker。
### T6 Profile switching isolation
阅读本文,然后在真实 `agentrun-v01` 运行面按顺序执行 `backendProfile=codex``backendProfile=deepseek``backendProfile=minimax-m3``backendProfile=dsflash-go``backendProfile=codex` 五个最短 turn。确认第二个 run 使用 DeepSeek profile,第三个 run 使用 MiniMax-M3 profile,第四个 run 使用 dsflash-go profile,前后两个 `codex` run 仍使用原 Codex profile;五者的 event、log、backend_status、model/upstream metadata 和 failureKind 不互相污染,且任何一个 profile SecretRef 缺失都不会 fallback 到另一个 profile。
### T7 Stale thread resume failed
阅读本文和 [spec-v01-runtime-assembly.md](spec-v01-runtime-assembly.md),然后构造一个带旧 `SessionRef.threadId` 的真实或 fake app-server run,使 `thread/resume` 返回 `no rollout found for thread id`。确认 adapter 输出 `thread-resume-failed` 并终止当前 turnevents/result/sessionRef 不得出现 `thread/resume:non-resumable`、替代 `thread/start`、新 threadId 回写或历史 prompt 拼接。确认 provider auth、rate limit、model config 或其他 protocol error 仍按各自 failureKind 直接失败,不走替代路径。
### T7b Session state 持久化与 eviction
阅读本文和 [spec-v01-runtime-assembly.md](spec-v01-runtime-assembly.md) v0.1.1 SessionRef 持久化节,然后构造一个带 `AGENTRUN_SESSION_PVC_NAME` 环境变量的真实或 fake app-server run。验证:
1. `initialize` 之后 adapter emit `codex-rollout-storage-mounted` 事件,事件 payload 含 `pvcName` / `mountPath` / `codexRolloutSubdir``valuesPrinted=false`
2. codex 写 `${CODEX_HOME}/<subdir>/YYYY/MM/DD/rollout-<timestamp>-<threadId>.jsonl`adapter 不写 PVC 内容,但 `runtimeSummary` 必须能回答 mount 是否成功。
3. 删 PVC 后再次 turn`AGENTRUN_SESSION_PVC_NAME` 已设 + `thread/resume` 返回 `no rollout found for thread id` 时,failureKind 升级为 `session-store-evicted`**不是** `thread-resume-failed`
4. 同一 session 跨 runner pod 重建,PVC 不动:adapter 仍 emit `codex-rollout-storage-mounted``thread/resume:completed` 路径走通,events 不出现 `thread-resume-failed` / `thread/replacement-start:completed`
5. `AGENTRUN_SESSION_PVC_NAME` 未设 + `thread/resume` 失败:仍走 `thread-resume-failed`,不升级为 `session-store-evicted`
6. `codex_rollout_subdir` 走 env `AGENTRUN_CODEX_ROLLOUT_SUBDIR`,默认 `sessions`codex CLI 改子目录时只改 env,不改装配。
### T8 Initial prompt and gitbundle skills
阅读本文和 [spec-v01-runtime-assembly.md](spec-v01-runtime-assembly.md),然后用 fake app-server 或真实 Codex stdio run 验证 `ResourceBundleRef.promptRefs` 与 gitbundle `.agents/skills`。首轮无 threadId 时,`turn/start` input 必须包含 initial prompt 与 skill facts,并记录 `initialPromptInjected=true`;第二轮带同一 `threadId` resume 时,`turn/start` input 只能包含当前用户 message,记录 `initialPromptInjected=false`,且不得拼接第一轮 prompt、assistant 回复或旧 skill facts。required prompt 缺失时不得调用 Codex provider。
## 规格的实现情况
| 规格项 | 状态 | 说明 |
| --- | --- | --- |
| Codex stdio backend/profile 规格 | 已定义 | 本文为 v0.1 Codex app-server stdio backend kind 和 profile 权威。 |
| Codex Secret projection | 已实现/已通过主闭环 | runner Job 使用只读 Secret projection 和 writable `CODEX_HOME`Codex 测试凭据来自 `agentrun-v01-provider-codex``auth.json`/`config.toml`。 |
| Codex adapter | 已实现主路径和 initial prompt/gitbundle skills 接入 | 当前代码已实现受控 `codex app-server --listen stdio://``initialize`/`thread/start`/`thread/resume`/`turn/start` response 校验、stale rollout `thread-resume-failed`、stderr 有界诊断、spawn/JSON parse/response invalid/timeout/provider 5xx/invalid tool-call availability failureKind,以及包含 retry error notification 的 fake app-server 自测试;`ResourceBundleRef.promptRefs` thread-start 注入和 gitbundle skill facts/registry 消费已接入。 |
| 错误可观测与脱敏 | 已实现主路径 | child env、cwd、workspace 和 Codex home 只输出摘要;stderr tail 有界且标记截断;事件和 failure 统一走 redaction。 |
| 真实 provider turn | 已通过主闭环 | 真实 Codex provider turn 已经通过 RESTful API 和 CLI 综合联调;每次发布仍按 [spec-v01-validation.md](spec-v01-validation.md) 手动复验。 |
| `deepseek` profile | 已实现/已通过主闭环 | 代码已支持 `agentrun-v01-provider-deepseek`、独立 `CODEX_HOME`、同一 `codex app-server --listen stdio://` 协议和 profile metadata;真实 Kubernetes SecretRef、runner Job 和 Codex stdio turn 已通过主闭环。 |
| `minimax-m3` profile | 已实现/待真实主闭环 | 代码已支持 `agentrun-v01-provider-minimax-m3`、独立 `CODEX_HOME`、同一 `codex app-server --listen stdio://` 协议和 profile metadata;真实 Kubernetes SecretRef、runner Job 和 Codex stdio turn 需要完成 AgentRun CLI 手动验收。 |
| `dsflash-go` profile | 已实现/待真实主闭环 | 代码已支持 `agentrun-v01-provider-dsflash-go`、独立 `CODEX_HOME``model-catalog.json`、同一 `codex app-server --listen stdio://` 协议和 profile metadata;真实 Kubernetes SecretRef、runner Job、Codex stdio turn 和 HWLAB 原入口 CaseRun 需要完成验收。 |
| hostPath `~/.codex` | 不采用 | 只能通过 Kubernetes Secret projection 注入。 |