feat(v0.1): add per-session RWO PVC foundation for true session state persistence
PR A for #770: docs + migration 007 + RBAC + types foundation. - 新增 failureKind session-store-evicted,用于区分 PVC 缺失与真协议错误 - 新增 migration 007_v01_session_state_storage:sessions 表增加 storage_* 列 + 索引 - mgr SA RBAC 增量:persistentvolumeclaims: [create, get, list, watch, delete] - 6 份 SPEC 升级(runtime-assembly / hwlab-manual-dispatch / backend-codex T7b / agentrun-runner / agentrun-mgr / services) - 显式禁止:fake app-server mock、replacement threadId、runner 启动后 copy/restore、idleTimeoutMs 拉永驻 - selftest 断言更新到 007_v01_session_state_storage 后续 PR B/C 在此基础上接入 mgr 端 PVC 生命周期 + runner 端 mount + backend 端 observability。
This commit is contained in:
@@ -71,6 +71,28 @@ PATCH /api/v1/commands/:commandId/status
|
||||
|
||||
所有 API 成功和失败响应都必须是 JSON。失败响应至少包含 `failureKind`、`message` 和 trace correlation;不得出现空 stdout/空 response 被误判为成功的情况。
|
||||
|
||||
### v0.1.1 Session state 存储 API
|
||||
|
||||
在 `P1 SessionRef 持久化` 升级到「per-session RWO PVC 直接挂载」后,manager 必须提供下列受控 API 来管理 session 的 PVC 生命周期:
|
||||
|
||||
```http
|
||||
POST /api/v1/sessions # 创建 session + 同步创建 PVC
|
||||
GET /api/v1/sessions/:id/storage # 查询 PVC 摘要(不返回内容)
|
||||
DELETE /api/v1/sessions/:id/storage # 删 PVC + 标记 storage_kind=evicted
|
||||
POST /api/v1/sessions/:id/storage/refresh # runner 上报 PVC 摘要
|
||||
```
|
||||
|
||||
边界:
|
||||
|
||||
- `POST /api/v1/sessions` 同步创建 `agentrun-v01-session-<sessionId>` PVC(1Gi RWO,StorageClass 走 `AGENTRUN_SESSION_STORAGE_CLASS`),再返回 session record。
|
||||
- `GET /api/v1/sessions/:id/storage` 只返回 `pvcName` / `pvcPhase` / `storage_size_bytes` / `storage_files_count` / `storage_sha256` / `storage_updated_at` 摘要。
|
||||
- `DELETE /api/v1/sessions/:id/storage` 删 PVC 并回写 `storage_kind='evicted'` 与 `storage_evicted_at`。
|
||||
- `POST /api/v1/sessions/:id/storage/refresh` 写回 runner 报告的 PVC 摘要,写到 `agentrun_sessions` 表的 `storage_*` 列。
|
||||
- GC 循环(默认 5min,可调 `AGENTRUN_SESSION_GC_INTERVAL_MS`)扫到 `expires_at` 过 + 无 active run 的 session 删 PVC。
|
||||
- run 渲染前 `get pvc`:不存在且 `storage_kind='pvc'` 时短路返回 `session-store-evicted`,不创建 runner Job。
|
||||
- mgr SA 必须有 `persistentvolumeclaims: [create, get, list, watch, delete]` 权限(RBAC 由 deploy 模板提供)。
|
||||
- failureKind 矩阵新增 `session-store-evicted`,仅当 `AGENTRUN_SESSION_PVC_NAME` 已设 + codex 报 `no rollout found for thread id` 时使用;其他 `thread/resume` 失败按 T7 走 `thread-resume-failed`。
|
||||
|
||||
## HWLAB v0.2 基线承接
|
||||
|
||||
Manager 只承接 HWLAB v0.2 Code Agent 的通用执行事实,不承接 HWLAB 的用户鉴权、device-pod 授权或 Workbench schema。HWLAB 侧能力吸收总表见 [spec-v01-hwlab-manual-dispatch.md](spec-v01-hwlab-manual-dispatch.md)。本服务需要把以下能力固化为 AgentRun 自身合同:
|
||||
|
||||
@@ -32,6 +32,38 @@ Kubernetes Job runner 必须把 credential source 与 runtime home 分开:Secr
|
||||
|
||||
RuntimeAssembly P0 中 `SessionRef` 可以显式为 `null`,runner 不得把完整 `CODEX_HOME`、Secret projection 或节点 host path 当作 session store。`ResourceBundleRef` P0 收敛为 Git-only;runner 已支持把 `repoUrl + full commitId` checkout 到 `AGENTRUN_WORKSPACE_ROOT` 下的隔离目录,并记录 commit/tree 摘要,不能把用户上传文件或 env dump 混入 Git-only bundle。`toolAliases`、`promptRefs` 和 `skillRefs` 都属于该 Git-only bundle 的非敏感子资源:runner 必须在同一 checkout 中解析、校验和装配,不能从镜像默认目录、host path、旧 prompt 常量或用户长 prompt 中补齐。
|
||||
|
||||
### v0.1.1 Session state 持久化(per-session RWO PVC 直接挂载)
|
||||
|
||||
Runner 启动时若 run 携带 `storage_kind='pvc'` 的 session 引用,必须在 Job manifest 渲染阶段加一个 `agentrun-sessions` volume,把 `agentrun-v01-session-<sessionId>` PVC 直接挂到 `${CODEX_HOME}/<codex_rollout_subdir>`,并把以下 env 透传给 backend:
|
||||
|
||||
- `AGENTRUN_SESSION_PVC_NAME`:PVC 名(`agentrun-v01-session-<sessionId>`)。
|
||||
- `AGENTRUN_SESSION_PVC_NAMESPACE`:默认 `agentrun-v01`。
|
||||
- `AGENTRUN_SESSION_PVC_MOUNT_PATH`:默认 `${CODEX_HOME}/<codex_rollout_subdir>`。
|
||||
- `AGENTRUN_CODEX_ROLLOUT_SUBDIR`:默认 `sessions`。
|
||||
|
||||
边界:
|
||||
|
||||
- PVC 与 `runner-home` emptyDir 父目录共存;`auth.json` / `config.toml` / `state_*.sqlite` / `memories/` / `tmp/` / `skills/` 仍走 emptyDir;codex rollout JSONL 走 PVC。
|
||||
- `automountServiceAccountToken=false` 不变;runner 不通过 k8s API 读 PVC 内容,只把 PVC 摘要通过 `POST /api/v1/sessions/:id/storage/refresh` 写回 manager。
|
||||
- PVC 不存在 + `storage_kind='pvc'` 时 runner 启动前 manager 端短路返回 `session-store-evicted`;runner 不知道该 session。
|
||||
- 禁止 runner 启动后做 copy/restore(PR #78 已回退的 replacement 逻辑同源,禁止任何变体)。
|
||||
|
||||
Runner K8s manifest 增量(spec 形态参考):
|
||||
|
||||
```yaml
|
||||
volumes:
|
||||
- name: runner-home # emptyDir(auth.json, config.toml, state_*.sqlite...)
|
||||
emptyDir: {}
|
||||
- name: agentrun-sessions # 新增 PVC RWO
|
||||
persistentVolumeClaim:
|
||||
claimName: agentrun-v01-session-<sessionId>
|
||||
volumeMounts:
|
||||
- name: runner-home
|
||||
mountPath: /home/agentrun
|
||||
- name: agentrun-sessions
|
||||
mountPath: /home/agentrun/.codex-<profile>/<codex_rollout_subdir> # <-- 唯一改这一行
|
||||
```
|
||||
|
||||
### ResourceBundle 子资源装配
|
||||
|
||||
Runner materialize `ResourceBundleRef` 后必须按固定顺序处理子资源:先创建 `toolAliases` wrapper,再装配 `skillRefs` registry,最后读取 `promptRefs` 并生成当前 command 的 assembled initial prompt 摘要。任何子资源缺失或非法都必须按对应 failureKind 阻塞当前 command,不得继续运行后让模型自行猜测。
|
||||
|
||||
@@ -136,6 +136,17 @@ Run 的 `executionPolicy.secretScope` 应引用与 `backendProfile` 匹配的 pr
|
||||
|
||||
阅读本文和 [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` 并终止当前 turn;events/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 skill refs
|
||||
|
||||
阅读本文和 [spec-v01-runtime-assembly.md](spec-v01-runtime-assembly.md),然后用 fake app-server 或真实 Codex stdio run 验证 `ResourceBundleRef.promptRefs` 与 `skillRefs`。首轮无 threadId 时,`turn/start` input 必须包含 initial prompt 与 skill facts,并记录 `initialPromptInjected=true`;第二轮带同一 `threadId` resume 时,`turn/start` input 只能包含当前用户 message,记录 `initialPromptInjected=false`,且不得拼接第一轮 prompt、assistant 回复或旧 skill facts。required prompt/skill 缺失时不得调用 Codex provider。
|
||||
|
||||
@@ -121,6 +121,26 @@ AgentRun 需要提供 durable cancel 能力,建议形态为 `POST /api/v1/runs
|
||||
|
||||
`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_rollout_subdir>`,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。
|
||||
- codex 写 `${CODEX_HOME}/<subdir>/YYYY/MM/DD/rollout-<timestamp>-<threadId>.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`。
|
||||
@@ -213,6 +233,7 @@ HWLAB 旧 Code Agent 的业务 prompt 和 skill 注入必须迁移为 `ResourceB
|
||||
| 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 实现中/未发布 | 在「metadata-only 最小持久化」基础上把 session 真实持久化:每个 session 绑 RWO PVC(`agentrun-v01-session-<sessionId>`),runner Job 把 PVC 直接挂到 `${CODEX_HOME}/<codex_rollout_subdir>`,codex app-server 自己落盘;HWLAB Cloud Web Workbench 收到 `session-store-evicted` 走 reset UX,**不**写 fake 续接。 |
|
||||
| ResourceBundleRef | 已实现 Git-only materialization/待 promptRefs 与 skillRefs | run 可携带 `repoUrl + full commitId`,runner checkout 到 `AGENTRUN_WORKSPACE_ROOT` 下的隔离目录并记录 commit/tree/workspace 摘要;`toolAliases` 已实现;上传文件和对象存储仍不进入 v0.1;HWLAB 初始 prompt 与 skill 注入按本规格待补齐。 |
|
||||
| 同 run/runner 多 turn | 已实现最小闭环 | runner Job 在 idle timeout 内持续 poll 同一 run 的后续 command;普通 turn completed 不终结 run,bundle 只 materialize 一次,command result 按 commandId 独立聚合。 |
|
||||
| HWLAB v0.2 canary | 待实现 | 需要 HWLAB dispatcher adapter 调 AgentRun 手动调度 API,并转换 result/trace。 |
|
||||
|
||||
@@ -127,6 +127,27 @@ HWLAB v0.2 原有 Code Agent 已经验证了 profile、session、workspace 和 S
|
||||
- runner 启动时,有 SessionRef 则执行 `thread/resume`,没有 SessionRef 则执行 `thread/start`;profile 切换不得复用另一 profile 的 session。
|
||||
- v0.1 先定义边界;持久 session store、TTL、GC 和 resume 验收按 [spec-v01-hwlab-manual-dispatch.md](spec-v01-hwlab-manual-dispatch.md) 分阶段推进。
|
||||
|
||||
#### v0.1.1 SessionRef 持久化(per-session RWO PVC 直接挂载)
|
||||
|
||||
`v0.1` 的 P0 把 SessionRef 收敛为「只持久化 `sessionId`/`threadId`/metadata」。`v0.1.1` 必须把 SessionRef 升级为真正持久化,让 `codex app-server thread / resume` 跨 runner pod 重建继续成功,且「PVC 不删就随时 resume」是硬验收。
|
||||
|
||||
边界:
|
||||
|
||||
- 每个 session 绑定一个 RWO PVC:`agentrun-v01-session-<sessionId>`,固定 namespace `agentrun-v01`、StorageClass 走 `AGENTRUN_SESSION_STORAGE_CLASS`(默认 `local-path`)、默认容量 1Gi。
|
||||
- runner Job 必须在 `${CODEX_HOME}/<codex_rollout_subdir>` 位置把 PVC **直接挂载**到 runner pod;`auth.json`、`config.toml`、`state_*.sqlite`、`memories/`、`tmp/`、`skills/` 仍由 `emptyDir` 父目录提供,不持久化。
|
||||
- 完整 CODEX_HOME 不进入 PVC,profile Secret 投影仍走 `agentrun-v01-runner-secret-reader`,与 session 持久化路径分离。
|
||||
- runner 启动时把 PVC 名、namespace、mount path、codex rollout subdir 写入 env(`AGENTRUN_SESSION_PVC_NAME`、`AGENTRUN_SESSION_PVC_NAMESPACE`、`AGENTRUN_SESSION_PVC_MOUNT_PATH`、`AGENTRUN_CODEX_ROLLOUT_SUBDIR`),backend adapter 不需要直接读 k8s API。
|
||||
- `codex_rollout_subdir` 走 env,默认 `sessions`,codex CLI 升级改子目录时只改该 env,不改 runner/backend 装配。
|
||||
- `POST /api/v1/sessions` 创建 session 时同步创建 PVC;`POST /api/v1/runs/:runId/commands` 命中已有 session 且 `storage_kind='evicted'` 时短路返回 `session-store-evicted`,不创建 runner Job。
|
||||
- GC 循环(默认 5min,可调 `AGENTRUN_SESSION_GC_INTERVAL_MS`)扫到 `expires_at` 过 + 无 active run 的 session 删 PVC。
|
||||
- runner Job 启动后把 PVC 摘要(`pvcName`/`pvcPhase`/`storage_size_bytes`/`storage_files_count`/`storage_sha256`/`storage_updated_at`)通过 `POST /api/v1/sessions/:id/storage/refresh` 写回 manager;manager 不读 PVC 内容,只读摘要。
|
||||
|
||||
禁止路径:
|
||||
|
||||
- 不引入「runner 启动后 copy/restore」path,撤掉的 copy 方案不允许复活(PR #78 已回退 replacement 逻辑,本节同样禁止任何「持久化卷 + 热修脚手架」变体)。
|
||||
- 不允许 fake `thread/resume:completed`,PVC 真的存在且 codex 真的能 resume 才算通过。
|
||||
- 不写 fallback、不写 replacement、不拼接历史。`thread/resume` 失败时按 [spec-v01-backend-codex.md](spec-v01-backend-codex.md) T7 直接失败;只有 `AGENTRUN_SESSION_PVC_NAME` 已设 + codex 报 `no rollout found for thread id` 时升级为 `session-store-evicted`,用于上层区分「PVC 被删」与「真协议错误」。
|
||||
|
||||
### ResourceBundleRef
|
||||
|
||||
- P0 固定 Git-only,由 `repoUrl + full commitId` 决定内容身份。
|
||||
@@ -261,5 +282,6 @@ HWLAB v0.2 原有 Code Agent 已经验证了 profile、session、workspace 和 S
|
||||
| `BackendImageRef` | 部分实现 | CI/CD 已使用 digest-pinned runtime image;当前 runner/backend 仍复用 agentrun 镜像。 |
|
||||
| `ProfileRef` | 已实现/待 MiniMax-M3 主闭环 | `codex` 与 `deepseek` 已通过 SecretRef、writable runtime home 和真实 stdio turn 验证;`minimax-m3` 已进入 profile/SecretRef 装配,需要完成真实 CLI 手动验收。 |
|
||||
| `SessionRef` | 已实现最小持久化 | manager 持久化 `sessionId/conversationId/threadId`,run 创建会解析既有 session,runner 按 threadId resume;session 不保存 credential 文件,TTL/GC 后续细化。 |
|
||||
| `SessionRef` | v0.1.1 实现中/未发布 | manager 持久化 `sessionId/conversationId/threadId` + 每个 session 绑 RWO PVC(`agentrun-v01-session-<sessionId>`),runner Job 把 PVC 直接挂到 `${CODEX_HOME}/<codex_rollout_subdir>`,codex app-server 自己落盘;不允许 copy/restore,不允许 replacement threadId;TTL/GC 由 mgr 周期任务管理。 |
|
||||
| `ResourceBundleRef` | 已实现 Git-only materialization/待 promptRefs 与 skillRefs 实现 | `repoUrl + full commitId` 已进入 run schema 和 runner checkout,workspace 受 `AGENTRUN_WORKSPACE_ROOT` 限制,event/result 记录 commit/tree/workspace 摘要;`toolAliases` 已实现,`promptRefs` thread-start 注入和 `skillRefs` registry 聚合待实现。 |
|
||||
| `toolCredentials` | 已实现最小 env projection | GitHub PR 和 UniDesk SSH passthrough 等 agent shell/tool 授权通过装配 SPEC 的 SecretRef 进入 runner;v0.1 支持 `tool=github` 与 `tool=unidesk-ssh`、`projection.kind=env`,runner Job 使用 `valueFrom.secretKeyRef` 注入,不用 `transientEnv` 绕过。 |
|
||||
|
||||
@@ -90,6 +90,19 @@ Runner inbound API 只允许本地或私有诊断,不作为业务客户端入
|
||||
| Tenant policy boundary | Run schema 合同 | 保留,P0 | 作为 `Run` 的必填字段和最小校验存在,不做独立 policy engine;tenant 的业务授权仍由 UniDesk/HWLAB 判定。 | 并入 `spec-v01-agentrun-mgr.md` |
|
||||
| Observability | 最小事件/日志合同 | 保留,P1 子项 | 作为 manager/runner 的 event、terminal status、failureKind、logPath 和 redaction 最小合同,不拆独立观测系统。 | 并入 `spec-v01-agentrun-mgr.md`、`spec-v01-agentrun-runner.md` |
|
||||
| `agentrun-scheduler` | 长驻调度器 | Deferred | M1-M3 稳定后再实现自动 pending scan、capacity selection 和 runner Job 创建。 | `spec-v01-scheduler.md` |
|
||||
|
||||
### v0.1.1 Session state 持久化
|
||||
|
||||
`v0.1` 的 `P1 SessionRef 持久化` 已从「metadata-only」升级为「per-session RWO PVC 直接挂载」:
|
||||
|
||||
- `agentrun-mgr`:负责 session 创建时同步创建 PVC(`POST /api/v1/sessions`)、删 PVC(`DELETE /api/v1/sessions/:id/storage`)、runner 上报摘要(`POST /api/v1/sessions/:id/storage/refresh`)、GC 循环、运行前 PVC 存在性检查。
|
||||
- `agentrun-runner`:Job manifest 渲染 `agentrun-sessions` PVC volume,把 PVC 直接挂到 `${CODEX_HOME}/<codex_rollout_subdir>`,env 透传 PVC name / namespace / mount path / codex rollout subdir。
|
||||
- `codex-app-server-stdio` backend:在 `initialize` 之后 emit `codex-rollout-storage-mounted` 事件;`AGENTRUN_SESSION_PVC_NAME` 已设 + `thread/resume` 返回 `no rollout found` 时升级为 `session-store-evicted`。
|
||||
- 接受方(HWLAB Cloud Web Workbench):收到 `session-store-evicted` 时走 reset UX,**不**写入 fake 续接。
|
||||
|
||||
Manager SA RBAC 增量:`persistentvolumeclaims: [create, get, list, watch, delete]`。Runner SA 不需新增 PVC 相关权限(挂载走 Pod spec)。PVC storage class / size / 命名规则由 env 收敛(`AGENTRUN_SESSION_STORAGE_CLASS`、`AGENTRUN_SESSION_STORAGE_SIZE`)。
|
||||
|
||||
禁止路径:fake app-server mock 通过;强制重发同 sessionId 同 threadId 蒙混;`thread/resume:no rollout found` 改写为 `thread/start` + replacement threadId(PR #78 已否决);`idleTimeoutMs` 拉成永驻当成本特性;runner Job 启动后再做 copy/restore(撤掉的路径,禁止复活)。
|
||||
| AgentRun Queue | 系统能力 | 新增,P0 规格 | 直接吸收 UniDesk Code Queue 的队列能力;第一版只做 RESTful API、CLI 和轻量轮询,不做 Event/OA/integrations,不迁移历史数据。 | `spec-v01-queue.md` |
|
||||
| 多 backend 路由 | 系统能力 | Deferred | `v0.1` 不做跨 backend kind 的自动路由和调度;仅支持同一 Codex stdio backend kind 下的 `codex`/`deepseek`/`minimax-m3` profile 手动选择。旧 MiniMax/OpenCode 直连路线不进入 Queue 首版。 | 后续版本 spec |
|
||||
| UI | 前端 | Deferred | `v0.1` 不要求独立 UI;UniDesk/HWLAB canary 可通过 CLI/API 验证。 | 后续版本 spec |
|
||||
|
||||
@@ -342,6 +342,9 @@ rules:
|
||||
- apiGroups: [""]
|
||||
resources: ["pods"]
|
||||
verbs: ["get", "list", "watch"]
|
||||
- apiGroups: [""]
|
||||
resources: ["persistentvolumeclaims"]
|
||||
verbs: ["create", "get", "list", "watch", "delete"]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
|
||||
@@ -22,6 +22,7 @@ export type FailureKind =
|
||||
| "provider-invalid-tool-call"
|
||||
| "provider-unavailable"
|
||||
| "infra-failed"
|
||||
| "session-store-evicted"
|
||||
| "cancelled";
|
||||
|
||||
export type RunStatus = "pending" | "claimed" | "running" | "completed" | "failed" | "blocked" | "cancelled";
|
||||
|
||||
@@ -175,6 +175,26 @@ CREATE INDEX IF NOT EXISTS agentrun_sessions_version_idx ON agentrun_sessions (v
|
||||
CREATE INDEX IF NOT EXISTS agentrun_runs_session_idx ON agentrun_runs ((session_ref->>'sessionId'), updated_at DESC);
|
||||
`;
|
||||
|
||||
const sessionStateStorageMigrationSql = `
|
||||
ALTER TABLE agentrun_sessions ADD COLUMN IF NOT EXISTS storage_kind text NOT NULL DEFAULT 'none';
|
||||
ALTER TABLE agentrun_sessions ADD COLUMN IF NOT EXISTS storage_pvc_name text;
|
||||
ALTER TABLE agentrun_sessions ADD COLUMN IF NOT EXISTS storage_namespace text;
|
||||
ALTER TABLE agentrun_sessions ADD COLUMN IF NOT EXISTS storage_size_bytes bigint;
|
||||
ALTER TABLE agentrun_sessions ADD COLUMN IF NOT EXISTS storage_files_count integer;
|
||||
ALTER TABLE agentrun_sessions ADD COLUMN IF NOT EXISTS storage_sha256 text;
|
||||
ALTER TABLE agentrun_sessions ADD COLUMN IF NOT EXISTS storage_updated_at timestamptz;
|
||||
ALTER TABLE agentrun_sessions ADD COLUMN IF NOT EXISTS codex_rollout_subdir text NOT NULL DEFAULT 'sessions';
|
||||
ALTER TABLE agentrun_sessions ADD COLUMN IF NOT EXISTS storage_pvc_phase text;
|
||||
ALTER TABLE agentrun_sessions ADD COLUMN IF NOT EXISTS storage_evicted_at timestamptz;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS agentrun_sessions_storage_pvc_idx
|
||||
ON agentrun_sessions (storage_pvc_name)
|
||||
WHERE storage_pvc_name IS NOT NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS agentrun_sessions_storage_kind_idx
|
||||
ON agentrun_sessions (storage_kind, expires_at);
|
||||
`;
|
||||
|
||||
const hwlabManualDispatchMigrationSql = `
|
||||
ALTER TABLE agentrun_runs ADD COLUMN IF NOT EXISTS session_ref jsonb;
|
||||
ALTER TABLE agentrun_runs ADD COLUMN IF NOT EXISTS resource_bundle_ref jsonb;
|
||||
@@ -297,6 +317,11 @@ const postgresMigrations: MigrationDefinition[] = [
|
||||
checksum: checksumSql(sessionControlMigrationSql),
|
||||
sql: sessionControlMigrationSql,
|
||||
},
|
||||
{
|
||||
id: "007_v01_session_state_storage",
|
||||
checksum: checksumSql(sessionStateStorageMigrationSql),
|
||||
sql: sessionStateStorageMigrationSql,
|
||||
},
|
||||
];
|
||||
|
||||
export function postgresMigrationContract(): JsonRecord {
|
||||
|
||||
@@ -13,7 +13,9 @@ const selfTest: SelfTestCase = async () => {
|
||||
(error) => error instanceof AgentRunError && error.failureKind === "infra-failed" && error.message.includes("DATABASE_URL is required"),
|
||||
);
|
||||
const postgresContract = postgresMigrationContract();
|
||||
assert.equal(postgresContract.latestMigrationId, "006_v01_session_control");
|
||||
assert.equal(postgresContract.latestMigrationId, "007_v01_session_state_storage");
|
||||
assert.equal((postgresContract.migrationIds as string[]).includes("007_v01_session_state_storage"), true);
|
||||
assert.ok(typeof (postgresContract.checksums as Record<string, string>)["007_v01_session_state_storage"] === "string" && (postgresContract.checksums as Record<string, string>)["007_v01_session_state_storage"].length > 0);
|
||||
assert.equal((postgresContract.checksums as Record<string, string>)["002_v01_backend_profiles"], "928b5c490cc4539cb64ecef34784557601b2724fa2870570f16a53576804e49c");
|
||||
assert.ok(Array.isArray(postgresContract.requiredTables));
|
||||
assert.ok(postgresContract.requiredTables.includes("agentrun_schema_migrations"));
|
||||
|
||||
Reference in New Issue
Block a user