# v0.1 HWLAB 手动调度接入规格 本文定义 AgentRun `v0.1` 面向 HWLAB v0.2 的下一阶段服务目标:不以前置自动 scheduler 为条件,而是通过 `agentrun-mgr` 的手动调度 API 为 HWLAB 提供 canary Code Agent 执行服务。实施跟踪见 [pikasTech/agentrun#31](https://github.com/pikasTech/agentrun/issues/31)。 ## 在系统中的职责划分 - HWLAB `hwlab-cloud-api` 是业务 dispatcher:继续负责用户登录、session owner、HWPOD 授权、Workbench `/v1/agent/chat` 合同、result/trace/cancel 对外接口和业务权限判断。 - AgentRun `agentrun-mgr` 是执行事实 authority:负责 run、command、event、runner job、backend profile、SecretRef、terminal status 和手动调度 API。 - Provider profile API Key 管理由 HWLAB Cloud API 鉴权后委托 AgentRun manager;AgentRun 信任 HWLAB 后端服务端调用,不实现 HWLAB 用户鉴权。完整合同见 [spec-v01-provider-profile-management.md](spec-v01-provider-profile-management.md)。 - `agentrun-runner` 是手动启动的执行者:从 manager claim run、poll command、调用 backend adapter、append events、ack command、上报 command terminal,并在同一 runner Job 生命周期内继续等待同 run 的后续 command;run terminal 只由显式 run cancel 或 runner 级不可恢复失败产生。 - HWLAB 不直接写 AgentRun Postgres、不读取 AgentRun Secret、不直接创建 Kubernetes Job;所有跨服务操作只走 AgentRun RESTful API。 - AgentRun 不内建 HWLAB HWPOD/gateway 业务授权;涉及 HWPOD 四要素、用户授权或硬件访问凭证的判断仍由 HWLAB 自己完成。 ## 非目标 - 本阶段不要求自动 scheduler、pending scan、capacity selection 或长驻调度器。 - 不改变 HWLAB 对外 `/v1/agent/chat`、`/result`、`/trace`、`/cancel` 用户合同。 - 不使用 SSE、WebSocket、long-polling 或长同步 `turn` 请求替代 durable resource 模型。 - 不把 HWLAB 的 provider Secret、HWPOD token、gateway route 或 kubeconfig 复制给业务客户端。 - 不把 mock、fixture、source-only smoke 或 dry-run 结果当作 HWLAB canary 通过证据。 - 不把 trace、日志、诊断文案、降级原因或“已观测到缺口”当作功能完成。若 HWLAB v0.2 原有 Code Agent 需要的能力在 AgentRun v0.1 中缺失,必须补齐 AgentRun 自身能力;若实现未修复,必须修复实现本身,观测只作为定位和验收证据。 ## 目标调用链 ```text HWLAB Workbench -> hwlab-cloud-api /v1/agent/chat -> HWLAB 用户/session/HWPOD 权限判断 -> AgentRun POST /api/v1/runs -> AgentRun POST /api/v1/runs/:runId/commands -> AgentRun POST /api/v1/runs/:runId/runner-jobs -> agentrun-runner claim/poll/report -> 同一 run 后续 turn 复用已 claim 的 runner Job 继续 poll command -> AgentRun events/command status/terminal_status -> hwlab-cloud-api 映射为 HWLAB result/trace ``` HWLAB 应保存 `traceId -> runId/commandId/attemptId/jobName` 映射。用户轮询 HWLAB result/trace 时,HWLAB 从 AgentRun command status 与 run events 读取进展,再转换为 HWLAB 自己的前端 schema;浏览器不直接理解 AgentRun 内部 event schema。 ## HWLAB v0.2 Code Agent 能力吸收基线 AgentRun `v0.1` 承接 HWLAB v0.2 时,只吸收原有 Code Agent 的通用执行能力,不吸收 HWLAB 的用户鉴权、HWPOD 授权、Workbench UI 或业务 trace schema。下表是后续实现时的代码追溯入口,目的是复用 HWLAB 已验证的边界和判定标准,而不是重新发明一套 Agent 行为。 | HWLAB v0.2 原有能力 | HWLAB 代码/文档追溯入口 | AgentRun 自身承接点 | SPEC 归属 | | --- | --- | --- | --- | | 短连接 submit + result/trace 轮询 | `internal/cloud/server-code-agent-http.ts`、`docs/reference/spec-v02-hwlab-cloud-api.md` | `run + command + runner-job` 三段式手动调度,所有写操作短返回 JSON | [spec-v01-agentrun-mgr.md](spec-v01-agentrun-mgr.md) | | 只有真实 terminal completed + 非空 reply 才算通过 | `docs/reference/code-agent-chat-readiness.md`、`internal/cloud/code-agent-chat.ts` | result envelope 和 terminal status 规则;partial、stdout、transport close 不升级为 completed | [spec-v01-agentrun-mgr.md](spec-v01-agentrun-mgr.md)、[spec-v01-backend-adapter.md](spec-v01-backend-adapter.md) | | runnerTrace 可见,能展示请求、工具、输出、错误和终态 | `internal/cloud/code-agent-trace-store.ts`、`web/hwlab-cloud-web/app-trace.ts` | 标准 event schema、单 run 内 seq 单调、bounded payload、Secret redaction | [spec-v01-backend-adapter.md](spec-v01-backend-adapter.md)、[spec-v01-agentrun-runner.md](spec-v01-agentrun-runner.md) | | 取消正在执行的 turn | `internal/cloud/server-code-agent-http.ts`、`internal/cloud/codex-stdio-session.ts` | durable run/command cancel、pending 阻止启动、running interrupt、terminal 幂等返回 | [spec-v01-agentrun-mgr.md](spec-v01-agentrun-mgr.md)、[spec-v01-agentrun-runner.md](spec-v01-agentrun-runner.md) | | conversation/session/thread 复用 | `internal/cloud/codex-stdio-session.ts`、`internal/cloud/code-agent-session-registry.ts` | `SessionRef` 保存 session/thread 摘要;同一 run/runner Job 处理后续 command,不重新 materialize bundle;runner 内每 turn 有 thread 则 resume,无 thread 则 start | [spec-v01-runtime-assembly.md](spec-v01-runtime-assembly.md)、[spec-v01-agentrun-runner.md](spec-v01-agentrun-runner.md) | | 固定 repo workspace 执行 | `internal/cloud/code-agent-contract.ts`、`docs/reference/code-agent-chat-readiness.md` | `ResourceBundleRef` 使用 Git-only `repoUrl + full commitId` checkout 到隔离 workspace | [spec-v01-runtime-assembly.md](spec-v01-runtime-assembly.md)、[spec-v01-agentrun-runner.md](spec-v01-agentrun-runner.md) | | 初始业务 prompt 注入 | `internal/cloud/codex-stdio-session.ts` 的 boundary instructions、`internal/cloud/codex-stdio-session-helpers.ts` 的 `buildCodexUserPrompt()` | HWLAB 把稳定业务 instruction 文件放入同一 Git bundle,并通过 `ResourceBundleRef.promptRefs` 指定;AgentRun 只在新 thread 首轮注入,resume 不重复注入 | [spec-v01-runtime-assembly.md](spec-v01-runtime-assembly.md)、[spec-v01-backend-codex.md](spec-v01-backend-codex.md) | | skill discovery 与 skill facts | `internal/cloud/skills-store.ts`、`internal/cloud/codex-stdio-session-helpers.ts` 的 `discoverSkillsForStdio()` 和 `codexSidecarSkillsPrompt()` | HWLAB 把 required skill manifest 放入同一 Git bundle,并通过 `ResourceBundleRef.skillRefs` 指定;AgentRun 聚合到 runner workspace skill registry 并向 Codex 暴露有界 skill facts | [spec-v01-runtime-assembly.md](spec-v01-runtime-assembly.md)、[spec-v01-agentrun-runner.md](spec-v01-agentrun-runner.md) | | provider profile 隔离和 Secret 不泄露 | `internal/cloud/code-agent-contract.ts`、`docs/reference/code-agent-chat-readiness.md` | `ProfileRef/SecretRef` profile-scoped 投影、缺失为 `secret-unavailable`、禁止 fallback 和泄露值 | [spec-v01-runtime-assembly.md](spec-v01-runtime-assembly.md)、[spec-v01-backend-adapter.md](spec-v01-backend-adapter.md) | | HWPOD/HWLAB runtime 短期 env 注入 | HWLAB Cloud API 的 Code Agent env assembly(历史实现名可能含 device-pod) | `runner-jobs.transientEnv` 只在本次 Kubernetes Job env 中生效;只记录 name/count,不保存或输出 value | [spec-v01-agentrun-mgr.md](spec-v01-agentrun-mgr.md)、[spec-v01-secret-distribution.md](spec-v01-secret-distribution.md) | | UniDesk SSH passthrough | HWLAB Code Agent 通过 `tran` 访问 G14/D601/HWLAB/GitHub 维护面 | `toolCredentials[].tool=unidesk-ssh` 注入 `UNIDESK_SSH_CLIENT_TOKEN`,`transientEnv` 只注入非敏感 `UNIDESK_MAIN_SERVER_IP`;UniDesk frontend 负责 route allowlist | [spec-v01-runtime-assembly.md](spec-v01-runtime-assembly.md)、[spec-v01-secret-distribution.md](spec-v01-secret-distribution.md) | | provider/backend/cancel 等失败可区分 | `scripts/src/code-agent-response-contract.mjs`、`internal/cloud/code-agent-chat.ts` | failureKind 最小矩阵和 JSON 错误响应 | [spec-v01-agentrun-mgr.md](spec-v01-agentrun-mgr.md)、[spec-v01-backend-adapter.md](spec-v01-backend-adapter.md) | | stdout/stderr/tool 输出必须有界 | `docs/reference/code-agent-chat-readiness.md`、`internal/cloud/code-agent-trace-store.ts` | `command_output`/`tool_call` 记录摘要、字节数、截断标记和必要引用 | [spec-v01-backend-adapter.md](spec-v01-backend-adapter.md) | | runner/job 失败需要定位证据 | `internal/cloud/server-code-agent-http.ts` 的 trace/result 可见性 | runner job identity、attempt、jobName、pod/log identity 和最小 phase/exit 摘要 | [spec-v01-agentrun-runner.md](spec-v01-agentrun-runner.md)、[spec-v01-agentrun-mgr.md](spec-v01-agentrun-mgr.md) | ## 手动调度 API `POST /api/v1/runs/:runId/runner-jobs` 是 HWLAB canary 的正式手动调度入口。它只负责为一个已存在 run 和 command 显式创建 runner Job,不负责扫描 pending queue。 请求最小字段: | 字段 | 规则 | | --- | --- | | `commandId` | 必填,必须属于 `runId`。 | | `attemptId` | 可选;未提供时由 manager 生成,返回值必须可持久查询。 | | `idempotencyKey` | HWLAB 必须用 `traceId`、`messageId` 或等价稳定 key;相同 key 和相同 payload 返回既有 job/attempt。 | | `image` / `backendImageRef` | 只能来自 manager allowlist、GitOps/catalog 或受控默认值;客户端不能传任意镜像扩大执行面。 | | `retention` / `ttlSecondsAfterFinished` | 可选;默认遵循 runner Job TTL 规格。 | | `transientEnv` | 可选,只用于本次 runner Job 的 Kubernetes env 渲染;不得写入 run/command/result/event 明文。用于承接 HWLAB dispatcher 生成的 owner-scoped HWPOD/runtime context,例如 `HWLAB_API_KEY`、`HWLAB_RUNTIME_API_URL`、`HWLAB_RUNTIME_WEB_URL` 和 endpoint metadata。 | 响应必须短返回 JSON,不等待完整模型 turn,至少包含:`runId`、`commandId`、`attemptId`、`jobName`、`namespace`、`runnerId`、`logPath` 或 `podIdentity`、后续 `commands show` 与 `events` 轮询入口。重复提交若 payload 不同,必须结构化失败,不能创建第二个同名业务 attempt。 `transientEnv` 是 runner-job 层的临时执行上下文,不是 AgentRun run 的 durable fact。manager 不对条目数量设固定上限,只校验数组形态、env name 合法且唯一、value 非空和单值长度;payload hash 只保存 value hash,response、event、dry-run manifest 和错误详情不得输出明文 value。业务授权仍由 HWLAB 自己负责,AgentRun 只把调度方明确提供的短期 env 交给本次 runner。UniDesk SSH passthrough 的长期 token 不得放入 `transientEnv`;HWLAB dispatcher 只能把 `UNIDESK_MAIN_SERVER_IP` 这类非敏感定位信息放入 `transientEnv`,并通过 run `executionPolicy.secretScope.toolCredentials[]` 请求 `tool=unidesk-ssh`。 ## Run / Command 映射 HWLAB canary 创建 run 时应使用以下字段口径: | 字段 | HWLAB canary 口径 | | --- | --- | | `tenantId` | `hwlab`。 | | `projectId` | `pikasTech/HWLAB`。 | | `providerId` | `G14`,只表示目标 provider,不授予 HWLAB 业务权限。 | | `backendProfile` | `deepseek`、`codex` 或 `minimax-m3`,由 HWLAB 或调度方显式选择;缺少 matching SecretRef 必须失败,不 fallback。 | | `workspaceRef` | 必须引用 ResourceBundleRef 中的 Git-only full commit;不得由 runner 猜 host path。 | | `resourceBundleRef.promptRefs[]` | 用于承接 HWLAB 稳定初始 prompt,例如 `hwlab-v02-runtime`、`hwpod-runtime-policy`;必须来自同一 full commit,`inject=thread-start`,新 thread 首轮注入,resume 不注入。 | | `resourceBundleRef.skillRefs[]` | 用于承接 HWLAB required skills,例如 `hwpod-cli`、`hwpod-ctl`;必须来自同一 full commit 的 `SKILL.md`,required skill 缺失时 blocked,不能让模型默认 skill 列表替代。 | | `resourceBundleRef.toolAliases[]` | 用于暴露短入口,例如 `hwpod`;缺 alias 时修 runner/bundle,不改走长路径 wrapper。 | | `executionPolicy` | sandbox、network、timeout、secretScope 必须显式,不得由 HWLAB 扩大 AgentRun Secret 范围。 | | `executionPolicy.secretScope.toolCredentials[]` | 需要 UniDesk SSH passthrough 时必须声明 `tool=unidesk-ssh`、`purpose=ssh-passthrough`、SecretRef `agentrun-v01-tool-unidesk-ssh`、projection env `UNIDESK_SSH_CLIENT_TOKEN`;不得把 token 放入 command payload 或 runner-job transientEnv。 | | `traceSink` | 可指向 HWLAB trace adapter;为 `null` 时 HWLAB 仍可通过 AgentRun events 轮询。 | `tenantId` / `projectId` 是 AgentRun manager 的 policy 边界,不是 HWLAB Workbench project。HWLAB adapter 可以把业务 project/workspace 写入 `metadata.hwlabProjectId`、`metadata.hwlabWorkspaceId` 或 `workspaceRef`,但不得覆盖 AgentRun `projectId=pikasTech/HWLAB`;否则 manager 必须按 `tenant-policy-denied` 拒绝。`providerProfile` 是 HWLAB 入口字段,进入 AgentRun 后必须映射为 `backendProfile`;同一个 HWLAB session 的后续 turn 应继承 session provider profile,不能被 stale workspace provider 覆盖。 Command 第一阶段要求 `type=turn` 和 `type=steer`。`turn` 保存用户原始 prompt、conversation metadata、profile 选择和 HWLAB trace correlation;稳定业务 prompt、skill 清单和工具入口不写入 command payload,而是通过 `ResourceBundleRef.promptRefs`、`skillRefs` 和 `toolAliases` 装配。`steer` 保存运行中引导文本,并由 runner 在同 run active turn 期间转发到 backend。业务 cancel 仍走 run/command cancel API,不用 `steer` 伪装。不得把 cookie、session token、provider credential、HWPOD internal token、Secret value、历史消息或大段 skill manifest 写入 payload。 ## 需要补齐的能力 ### P0 trace/result 元语 AgentRun 标准 events 必须稳定到足以被 HWLAB 转换: - `backend_status`:profile、backendKind、protocol、attempt、resource/session 摘要,不包含 Secret 值。 - `assistant_message`:用户可见 assistant 文本,允许分片但必须能聚合为最终 reply。 - `tool_call`:工具名、状态、bounded 参数摘要和 redacted correlation。 - `command_output`:stdout/stderr/diff 的 bounded summary、原始字节数、截断标记和 artifact/log 引用。 - `error`:`failureKind`、message、retryable、provider/infra/backend 分类和 redacted details。 - `terminal_status`:`completed`、`failed`、`blocked` 或 `cancelled`,是 completed 的唯一终态来源。 面向 HWLAB 的 result envelope 至少应能回答:`status`、`terminalStatus`、`reply`、`failureKind`、`blocker`、`lastSeq`、`eventCount`、`artifactSummary`、`runId`、`commandId` 和 `attemptId`。partial assistant 文本、transport close、idle timeout 或 stdout 存在都不能单独升级为 `completed`。 command terminal 与 run terminal 必须分离。普通 turn completed 只终结对应 command,并更新 SessionRef/thread 摘要;同一 run 保持可继续接收 command,已启动 runner Job 在 idle timeout 内继续 poll。只有显式 run cancel、runner 级不可恢复失败或未来 scheduler/retention 策略要求时,run 才进入 terminal。 ### P0 cancel AgentRun 需要提供 durable cancel 能力,建议形态为 `POST /api/v1/runs/:runId/cancel` 或 `POST /api/v1/commands/:commandId/cancel`。cancel 必须幂等;已 terminal 的对象返回当前终态。pending command 被 cancel 后不得再创建 runner Job。running runner 必须通过 poll、lease 或 heartbeat 观察 cancel,并传播到 backend interrupt;backend 不支持 interrupt 时终止受控进程组。cancel 最终必须写入 event、command state 和 run status,`failureKind` 使用 `cancelled`。 ### P1 SessionRef 持久化 `SessionRef` 需要从 `null/deferred` 升级为可选持久会话引用,支持 HWLAB `conversationId/sessionId/threadId` 到 AgentRun session identity 的映射。session 只能保存 backend thread/session/cache,不保存 API KEY、`auth.json`、`config.toml` 或完整 `CODEX_HOME`。session store 必须与 Secret projection、writable runtime home、Git workspace 分离。runner 启动时,只按 command `payload.threadId` 或 `SessionRef.threadId` 执行 `thread/resume`,没有标准 `threadId` 则执行 `thread/start`;events、result 和 session record 都以 `threadId` 为唯一 thread identity;profile 隔离、TTL、GC 和跨 profile 污染防护必须可见。 ### v0.1.1 Session state 真正持久化(per-session RWO PVC 直接挂载) `P1 SessionRef 持久化` 把「持久化」从「metadata-only」升级为「真实持久化」:每个 session 绑一个 RWO PVC,runner Job 把 PVC **直接挂载**到 `${CODEX_HOME}/`,codex app-server 自己管落盘,不引入 copy 钩子。 #### 关键边界 1. **不引入 copy 路径**:PR #78 已回退 replacement 逻辑(v0.1 不接受 stale `thread/resume` 蒙混)。本节同样禁止「runner Started 后 copy/restore」「热补丁写入 PVC」「伪造 `thread/resume:completed`」等任何变体;只能 PVC 直接挂载,codex 自己读写。 2. **硬验收 = 「PVC 不删就随时 resume」**:跨 runner pod 重建 / 跨 runner Job 重建 / 7 天长跨度(场景 D)都必须 `thread/resume:completed`,不能用 `idleTimeoutMs` 拉成永驻或 runner 不退蒙混。 3. **profile 隔离 + 永不复用**:每个 session 一个 PVC,PVC 内容只服务当前 `backendProfile` 的 codex rollout;profile 切换必须新 sessionId + 新 PVC。 4. **写后即一致**:`codex-rollout-storage-mounted` 是 backend adapter 在 `initialize` 之后发出的事件,含 `pvcName` / `mountPath` / `codexRolloutSubdir`;runner 通过 `POST /api/v1/sessions/:id/storage/refresh` 写回摘要。 5. **eviction 显式化**:只有 `AGENTRUN_SESSION_PVC_NAME` 已设 + codex 返回 `no rollout found for thread id` 时升级为 `session-store-evicted`;其他 `thread/resume` 失败按 T7 走 `thread-resume-failed`。 #### 行为约束 - `POST /api/v1/runs/:runId/commands` 在 `storage_kind='evicted'` 时直接短路返回 `session-store-evicted`,不创建 runner Job。 - runner Job manifest 必须含 `volumes: [{ name: agentrun-sessions, persistentVolumeClaim: { claimName: ... } }]` 与对应 `volumeMounts`;PVC `Phase=Bound` 才允许启动 turn。 - runner pod 被删除、Job 被重建或旧 runner lease 过期时,replacement runner 可以作为新的执行壳 claim 同一个 run 或 replacement run,但 resume 通过的判定仍必须是同一个 HWLAB session 映射到同一个 `SessionRef.sessionId`、`threadId`、`backendProfile` 和 PVC。不能把 replacement run/job 写成新业务 session,也不能用历史 prompt、messages 或 fake resume 代替 PVC 上的 Codex rollout。 - codex 写 `${CODEX_HOME}//YYYY/MM/DD/rollout--.jsonl`;其他路径不进入 PVC。 - GC 在 `expires_at` 过期且无 active run 时删 PVC;删 PVC 同时把 `storage_kind` 置为 `evicted`。 - HWLAB Cloud Web Workbench(PR D)收到 `session-store-evicted` 时走「同 conversationId + 新 sessionId + `threadId=null`」的 reset UX,不假装续接;新 session 走 `POST /api/v1/sessions` 重新创建 PVC。 ### P1 ResourceBundleRef / bundle materialization `ResourceBundleRef` 必须按 Git-only 模型落地:`repoUrl + full commitId` 是唯一内容身份。runner 只能 checkout 到允许 workspace 前缀,不能覆盖 `/app`、Secret projection、profile runtime home 或 session 目录。第一阶段支持 `subdir`、`sparsePaths`、`submodules=false`、`lfs=false`、`credentialRef` 的最小字段即可。HWLAB canary 只需要 `pikasTech/HWLAB` 固定 full commit 的普通 checkout;用户上传文件和对象存储 artifact 不进入 `v0.1`。 ### P1 Resource prompt/skill assembly HWLAB 旧 Code Agent 的业务 prompt 和 skill 注入必须迁移为 `ResourceBundleRef` 子资源,而不是继续依赖 cloud-api 进程内硬编码 prompt、`/app/skills` 镜像目录、用户长 prompt 或 Codex 默认 skill registry。HWLAB dispatcher 创建 run 时应指定: ```json { "resourceBundleRef": { "kind": "git", "repoUrl": "git@github.com:pikasTech/HWLAB.git", "commitId": "", "toolAliases": [ { "name": "hwpod", "path": "tools/hwpod-cli.ts", "kind": "bun-script" } ], "promptRefs": [ { "name": "hwlab-v02-runtime", "path": "internal/agent/prompts/hwlab-v02-runtime.md", "inject": "thread-start", "required": true }, { "name": "hwpod-runtime-policy", "path": "internal/agent/prompts/hwpod-runtime-policy.md", "inject": "thread-start", "required": true } ], "skillRefs": [ { "name": "hwpod-cli", "path": "skills/hwpod-cli/SKILL.md", "required": true, "aggregateAs": "hwpod-cli" }, { "name": "hwpod-ctl", "path": "skills/hwpod-ctl/SKILL.md", "required": true, "aggregateAs": "hwpod-ctl" } ] } } ``` 这些 prompt 文件只写稳定规则,例如 HWLAB Cloud Workbench Code Agent 身份、HWPOD 四要素(target device、workspace、debug probe、io probe)、D601-F103-V2/Keil/build/download/UART 的标准 `hwpod` 路径、禁止旧 Device Pod/profile/device-pod-cli fallback、禁止 Cloud Web 业务 API 替代 runner 内 HWPOD CLI、禁止 session-token fallback、禁止长路径 wrapper,以及历史上下文只走 Codex stdio 原生 `thread/resume`。它们不写用户本轮任务、不写会话历史、不写 Secret 或一次性 issue 过程。 首轮新 thread 必须自动注入这些 prompt 和 skill facts,使 Web 简短 prompt 也能识别 HWLAB 标准能力。后续 turn/resume 不重复注入;若 resume 失败,按 AgentRun Codex stdio 规则失败,不拼接历史 prompt 模拟继续。 ## 分阶段增强计划 | 阶段 | 目标 | 主要交付 | 验收重点 | | --- | --- | --- | --- | | 1 | 手动调度 API 固化 | `runner-jobs` request/response schema、idempotency、job identity、CLI 调同一 REST API | 重复 key 不重复创建;短返回;manager 重启后可查。 | | 2 | trace/result 元语 | 标准 event 子集、terminal result envelope、bounded output metadata | HWLAB 可由 events 稳定生成 result/trace;partial 不误报 completed。 | | 3 | cancel 闭环 | durable cancel API、runner cancel poll、backend interrupt/process group stop | pending/running/terminal 后 cancel 均幂等且可见。 | | 4 | ResourceBundleRef materialization | Git-only checkout、workspace 前缀、commit/tree 摘要、failureKind | 使用 full commit;不接受 branch/tag/HEAD;不覆盖 Secret/session/runtime home。 | | 5 | Resource prompt/skill assembly | `promptRefs` thread-start 注入、`skillRefs` registry 聚合、required blocker、hash/bytes 可见 | 简短 HWLAB prompt 能看到业务 instruction 和 required skill;resume 不重复注入;缺失直接 blocked。 | | 6 | SessionRef 持久化与 runner 多 turn | session record/store、thread resume、runner command loop、TTL/GC、profile 隔离 | 同一 conversation 连续两轮复用同一 run/runner Job;第二轮不重新 materialize bundle、不重复注入 initial prompt;不同 profile 不污染。 | | 7 | HWLAB v0.2 canary | HWLAB dispatcher adapter、traceId 映射、result/trace 转换 | 普通自然语言最短 turn 真实 completed 且 reply 非空;HWPOD 仍由 HWLAB 授权。 | ## 测试规格 ### T1 手动调度 API 阅读本文和 [spec-v01-agentrun-mgr.md](spec-v01-agentrun-mgr.md),然后在真实 `agentrun-v01` runtime 中用 RESTful API 创建 `tenantId=hwlab` 的 run、提交 `turn` command、调用 `POST /api/v1/runs/:runId/runner-jobs`。确认每个 API 返回 JSON、60 秒内返回、不等待完整 turn,并返回 job identity 与后续 poll 入口。 ### T2 幂等和重复提交 阅读本文,然后用相同 `idempotencyKey` 重复调用 runner job API。相同 payload 必须返回同一 attempt/job;不同 payload 必须结构化失败,且不能创建第二个 runner Job。 ### T3 trace/result 映射 阅读本文和 [spec-v01-backend-adapter.md](spec-v01-backend-adapter.md),然后执行真实 Codex stdio turn,确认 events 中存在可转换为 HWLAB result/trace 的 `backend_status`、assistant 或 error、`terminal_status`,且 bounded output metadata 足够判断截断和 artifact 引用。 ### T4 cancel 阅读本文,然后分别验证 pending cancel、running cancel、重复 cancel 和 terminal 后 cancel。确认 command/run 终态、events 和 failureKind 均为 `cancelled` 或当前既有 terminal 状态,日志不泄露 Secret。 ### T5 SessionRef 与 ResourceBundleRef 阅读本文和 [spec-v01-runtime-assembly.md](spec-v01-runtime-assembly.md),然后验证一次带 SessionRef 和 Git-only ResourceBundleRef 的 runner Job。确认 session 不含 credential 文件,bundle 使用 full commit checkout 到允许 workspace,event/result 能回答 session id、repo、commit 和 checkout 摘要。 ### T6 同 run/runner 后续 turn 阅读本文和 [spec-v01-agentrun-runner.md](spec-v01-agentrun-runner.md),然后在同一 run 中先提交第一条 turn command 并启动一次 runner Job;第一条 command completed 后,在同一 run 中提交第二条 turn command。确认第二条由同一 runner Job 在 idle timeout 内处理,run 未因第一条 completed 而 terminal,events 中 `resource-bundle-materialized` 只出现一次,两个 command result 分别可查且 reply 不互相串联。 ### T7 HWLAB prompt/skill 装配 阅读本文和 [spec-v01-runtime-assembly.md](spec-v01-runtime-assembly.md),然后用 HWLAB canary `ResourceBundleRef` 指定 `promptRefs`、`skillRefs` 和 `toolAliases`。首轮 Web/CLI 简短 prompt 只写“编译 D601-F103-V2”或等价自然语言,确认 Codex turn 能看到 HWLAB runtime prompt、`hwpod-cli`/`hwpod-ctl` skill facts 和 `hwpod` alias;第二轮 continuation 使用同一 thread resume,确认 `initialPromptInjected=false`,没有手工拼接历史;删除 required skill/prompt 后确认 blocked 而不是 fallback 到默认 Codex 5 个系统 skill。 ### T8 DS 短 prompt 真实验收 阅读本文、[spec-v01-backend-codex.md](spec-v01-backend-codex.md) 和 HWLAB `spec-v02-hwlab-cli.md`,然后必须用正式 HWLAB CLI/Web 等价短连接入口提交真实 `backendProfile=deepseek` 的短 prompt 到 AgentRun runtime,不允许用长提示词把规则补给模型。验收至少包含三条 prompt: 1. “不调用工具的情况下,你可见的 skill 有哪些?”确认回复包含 `ResourceBundleRef.skillRefs` 中的 HWLAB required skill,例如 `hwpod-cli` 和 `hwpod-ctl`,并且不只返回 Codex 默认 5 个系统 skill。 2. “你当前 HWLAB 初始规则里,D601-F103-V2 应该走哪个标准入口?请只回答入口和禁止路径。”确认回复能说出 `hwpod`、`hwpod-cli`/`hwpod-ctl`、assembled runtime env、禁止旧 Device Pod/profile/device-pod-cli fallback、禁止 session-token fallback 和禁止长路径 wrapper。 3. “编译 D601-F103-V2。”确认短 prompt 能按注入规则触发 `hwpod-cli -> hwpod-compiler-cli -> /v1/hwpod-node-ops -> hwpod-node` 正向链路,而不是要求用户补充长 prompt。 上述三条必须来自真实 DS/DeepSeek profile 的 Codex stdio `thread/start`/`turn/start` 和后续 `thread/resume` 事件;trace/result 必须显示 `initialPromptInjected=true` 的首轮、skill refs 摘要、真实 provider profile、terminal status,以及 continuation 时 `initialPromptInjected=false`。如果回复只列出 Codex 默认系统 skill、不能识别 HWLAB 初始规则或需要用户长 prompt 才能触发 `hwpod`,验收失败。 ## 实现状态 | 能力 | v0.1 状态 | 说明 | | --- | --- | --- | | 手动 runner Job API | 已实现 | `runner job` 通过 manager REST 创建 Kubernetes Job,支持 `idempotencyKey`、持久 runner job record、job identity、attempt/runner/jobName 返回和重复 payload 冲突保护。 | | trace/result 元语 | 已实现最小合同 | 新增 run/command result envelope,聚合 terminal status、reply、failureKind、event cursor、artifact summary、attempt、SessionRef 和 ResourceBundleRef 摘要。 | | cancel | 已实现最小闭环 | 已提供 run/command cancel API;pending cancel 会阻止新 runner Job,running runner 通过轮询触发 backend abort,终态写入 event、command state 和 run status。 | | SessionRef | 已实现最小持久化 | run 可携带 `sessionRef`,manager 保存 session/thread,runner 会按 threadId resume,result envelope 暴露脱敏 session 摘要;TTL/GC 仍按后续运维策略细化。 | | SessionRef | v0.1.1 已实现/已通过 HWLAB v0.2 原入口复测 | 在「metadata-only 最小持久化」基础上把 session 真实持久化:每个 session 绑 RWO PVC(`agentrun-v01-session-`),runner Job 把 PVC 直接挂到 `${CODEX_HOME}/`,codex app-server 自己落盘;HWLAB 原入口已验证 runner pod 删除后同 session/thread/PVC 可以恢复,仍禁止 fake 续接。 | | ResourceBundleRef | 已实现 Git-only materialization/promptRefs/skillRefs 装配 | run 可携带 `repoUrl + full commitId`,runner checkout 到 `AGENTRUN_WORKSPACE_ROOT` 下的隔离目录并记录 commit/tree/workspace 摘要;`toolAliases`、`promptRefs` thread-start 注入和 `skillRefs` registry 聚合已实现;上传文件和对象存储仍不进入 v0.1。 | | 同 run/runner 多 turn | 已实现最小闭环 | runner Job 在 idle timeout 内持续 poll 同一 run 的后续 command;普通 turn completed 不终结 run,bundle 只 materialize 一次,command result 按 commandId 独立聚合。 | | HWLAB v0.2 canary | 已实现/已通过 HWLAB v0.2 原入口复测 | HWLAB dispatcher adapter 已调 AgentRun 手动调度 API,并能转换 result/trace;MiniMax-M3 显式 session、provider profile 继承和 runner pod 删除后的同 session resume 已通过原入口 CLI 复测。 |