Merge pull request #23 from pikasTech/fix/v01-spec-closure

fix: 收口 v0.1 规格缺口
This commit is contained in:
Lyon
2026-05-29 17:39:38 +08:00
committed by GitHub
17 changed files with 127 additions and 70 deletions
+1
View File
@@ -1,4 +1,5 @@
.state/
.worktree/
logs/
node_modules/
package-lock.json
+2 -2
View File
@@ -36,12 +36,12 @@ AgentRun 是面向 UniDesk 与 HWLAB 的共享 Agent 执行基础设施。本仓
## Critical CLI Spec Rule
- P0: AgentRun CLI 和服务开发必须遵循 UniDesk `cli-spec` 原则:默认 JSON 输出、禁止空输出伪成功、禁止长阻塞 CLI、日志可见、配置显式校验、稳定跨服务边界优先使用 RESTful API。
- P0: AgentRun CLI 和服务开发必须遵循 UniDesk `cli-spec` 原则:默认 JSON 输出、禁止空输出伪成功、禁止长阻塞 CLI、日志可见、配置显式校验、稳定跨服务边界优先使用 RESTful API。G14 非交互 route 优先使用 `./scripts/agentrun ...` launcher;它只负责定位 Bun 并转入 `scripts/agentrun-cli.ts`
- P0: 一旦新增 CLI,入口文件必须保持轻量,具体实现拆入 `scripts/src/`;长任务必须快速返回,并提供 status/log/event 轮询。
## Critical v0.1 Implementation Stack Rule
- P0: AgentRun `v0.1` 自研 runtime、CLI、manager、runner、backend adapter、Codex backend 和后续 scheduler 的优先实现语言是 Bun + TypeScript;官方 CLI 入口是 `bun scripts/agentrun-cli.ts`,复杂逻辑拆入 `scripts/src/``src/`
- P0: AgentRun `v0.1` 自研 runtime、CLI、manager、runner、backend adapter、Codex backend 和后续 scheduler 的优先实现语言是 Bun + TypeScript;官方 TypeScript CLI 入口是 `scripts/agentrun-cli.ts`G14/CI/人工非交互命令使用 `./scripts/agentrun` 启动同一入口,复杂逻辑拆入 `scripts/src/``src/`
- P0: `backendProfile=codex` 必须通过 Codex CLI app-server stdio 协议执行,启动受控 `codex app-server --listen stdio://`,使用 JSON-RPC 方法 `initialize``thread/start``thread/resume``turn/start`;不得在 `v0.1` 把 Codex backend 实现为直接 Responses HTTP 代理、文本 fallback 或 fake provider。
- P0: 实现 Codex backend 前必须参考 UniDesk Code Queue 的 `src/components/microservices/code-queue/src/code-agent/codex.ts``common.ts`,以及 HWLAB 的 `internal/cloud/codex-stdio-session.mjs``scripts/code-agent-chat-smoke.mjs`;复用协议、redaction、trace、failure 分类和 Secret projection 经验,不复制环境专用路径或明文密钥。
+5 -3
View File
@@ -31,10 +31,12 @@
"serviceAccount": "agentrun-v01-runner",
"secretMounts": [
{
"name": "codex-home",
"name": "codex-secret-projection",
"secretRef": { "name": "agentrun-v01-provider-codex", "keys": ["auth.json", "config.toml"] },
"mountPath": "/home/agentrun/.codex",
"readOnly": true
"projectionPath": "/var/run/agentrun/secrets/codex-0",
"runtimeCopyPath": "/home/agentrun/.codex",
"readOnly": true,
"writableCopy": true
}
],
"env": [
+4 -4
View File
@@ -108,7 +108,7 @@ POST /api/v1/commands/:commandId/ack
| 规格项 | 状态 | 说明 |
| --- | --- | --- |
| `agentrun-mgr` 服务规格 | 已定义 | 本文为 v0.1 manager 权威。 |
| Manager REST API | 已实现骨架 | 已有 run、command、event、backends、runner register、claim、lease heartbeat、poll、ack、status 和 health/readiness 的 HTTP JSON 骨架;仍需后续真实部署验收。 |
| Tenant policy boundary | 已定义/未实现 | v0.1 只做最小 schema/allowlist/secretScope 边界。 |
| Postgres durable adapter | 已实现骨架 | live runtime 通过 `DATABASE_URL` 使用 Postgres durable storememory store 仅用于显式 self-test/dev。见 [spec-v01-postgres.md](spec-v01-postgres.md)。 |
| Observability 最小合同 | 已实现骨架 | events append-only、terminal status、failureKind、health/readiness store 状态、runner claim/lease/backend events 和 Secret/DSN redaction 已进入 manager 骨架;集中 trace 和部署级观测仍属后续工作。 |
| Manager REST API | 已实现/已通过主闭环 | 已有 run、command、event、backends、runner register、claim、lease heartbeat、poll、ack、status、runner Job 创建和 health/readiness 的 HTTP JSON API;真实 runtime 已通过 RESTful API 主闭环。 |
| Tenant policy boundary | 已实现最小边界 | v0.1 已做 schema、tenant/backend allowlist、executionPolicy 和 secretScope 结构校验;业务授权仍由 UniDesk/HWLAB 自己判定。 |
| Postgres durable adapter | 已实现/已通过主闭环 | live runtime 通过 `DATABASE_URL` 使用 Postgres durable storememory store 仅用于显式 self-test/dev。见 [spec-v01-postgres.md](spec-v01-postgres.md)。 |
| Observability 最小合同 | 已实现主路径 | events append-only、terminal status、failureKind、health/readiness store 状态、runner claim/lease/backend events 和 Secret/DSN redaction 已进入 manager;集中 trace 和部署级观测仍属后续工作。 |
+6 -4
View File
@@ -29,6 +29,8 @@ Runner Secret 只能通过 Kubernetes Secret projection、ServiceAccount/RBAC
Kubernetes Job runner 必须把 credential source 与 runtime home 分开:Secret volume 只读挂在 `/var/run/agentrun/secrets/...``/home/agentrun``emptyDir` 提供可写空间,`CODEX_HOME` 指向 `/home/agentrun/.codex``AGENTRUN_CODEX_SECRET_HOME` 指向只读 projection。runner/backend 在启动 provider 前只复制授权文件,不打印内容。
Kubernetes Job runner 必须设置有限保留时间。`v0.1` 默认 `ttlSecondsAfterFinished=86400`,用于保留最近完成 Job 的调试窗口,同时避免长期堆积 `Completed` runner Job 污染运行面观察。该 TTL 是 Job manifest 的运行面属性,不是 CI/CD 门禁;需要延长保留时间时必须通过受控 Job render/input 显式覆盖,并在 issue 或 PR 中说明原因。
## Runner 生命周期
标准状态方向:
@@ -102,7 +104,7 @@ Runner 日志必须实时 flush 到文件或 pod logCLI 启动 runner 时必
| 规格项 | 状态 | 说明 |
| --- | --- | --- |
| `agentrun-runner` 服务规格 | 已定义 | 本文为 v0.1 runner 权威。 |
| Kubernetes Job runner | 部分实现 | 已提供 `runner job --dry-run` Job manifest 渲染骨架;正式 `runner job` 通过 manager REST 创建 Kubernetes Job,固定使用 `agentrun-v01-runner` ServiceAccount、manager URL、runId/commandId/attemptId、executionPolicy、SecretRef 文件投影writable Codex runtime home;真实集群综合联调仍需验收。 |
| host process runner | 部分实现 | `runner start``src/runner/main.ts` 进入同一套 `runOnce`,可通过 manager register/claim/poll/report 执行自测试。 |
| claim/lease/report client | 部分实现 | 已拆出 runner manager API client,覆盖 register、claim、lease heartbeat、poll command、ack、append event 和 terminal statusdurable store 仍待 Postgres adapter 接入。 |
| runner redaction | 已定义/未实现 | 需与 backend adapter 共同实现。 |
| Kubernetes Job runner | 已实现/已通过主闭环 | `runner job` 通过 manager REST 创建 Kubernetes Job,固定使用 `agentrun-v01-runner` ServiceAccount、manager URL、runId/commandId/attemptId、executionPolicy、SecretRef 文件投影writable Codex runtime home 和有限 TTL;真实 `agentrun-v01` runner Job 已完成 Codex turn。 |
| host process runner | 实现 | `runner start``src/runner/main.ts` 进入同一套 `runOnce`,可通过 manager register/claim/poll/report 执行自测试。 |
| claim/lease/report client | 实现 | 已拆出 runner manager API client,覆盖 register、claim、lease heartbeat、poll command、ack、append event 和 terminal statuslive runtime 通过 manager 写入 Postgres durable store。 |
| runner redaction | 已实现主路径 | runner/backend event 和 Job 输出使用 redaction;复杂审计仍按 [spec-v01-validation.md](spec-v01-validation.md) 的人工验收抽查。 |
+3 -3
View File
@@ -86,7 +86,7 @@ Adapter 必须把 backend 错误映射为稳定 failureKind
| 规格项 | 状态 | 说明 |
| --- | --- | --- |
| Backend adapter 合同 | 已定义 | 本文为 v0.1 adapter 权威。 |
| 通用 adapter 模块 | 实现 | 需要后续代码实现。 |
| event normalization | 已定义/未实现 | 需后续实现和测试。 |
| failure mapping | 已定义/未实现 | 需后续实现和测试。 |
| 通用 adapter 模块 | 实现最小形态 | `src/backend/adapter.ts` 作为 runner 进程内 adapter 入口,`v0.1` 只路由真实 `codex` profile;多 backend 路由仍 deferred。 |
| event normalization | 已实现主路径 | Codex backend 已把 backend_status、assistant_message、tool_call、command_output、error 和 terminal_status 归一化为 manager events;复杂事件审计按人工验收抽查。 |
| failure mapping | 已实现主路径 | Codex backend 已覆盖 missing secret、auth/rate/availability、protocol、JSON parse、invalid response、spawn、timeout 和 cancel 分类;真实负向场景按 [spec-v01-validation.md](spec-v01-validation.md) T7 手动验收。 |
| 多 backend 路由 | Deferred | v0.1 只要求一个真实 backend 闭环。 |
+4 -4
View File
@@ -99,8 +99,8 @@ Run 的 `executionPolicy.secretScope` 应引用 `agentrun-v01-provider-codex`
| 规格项 | 状态 | 说明 |
| --- | --- | --- |
| Codex backend 规格 | 已定义 | 本文为 v0.1 第一真实 backend 权威。 |
| Codex Secret projection | 实现 | 需要后续 Kubernetes Secret 和 runner/backend manifest。 |
| Codex adapter | 已部分实现 | 当前代码已实现受控 `codex app-server --listen stdio://``initialize`/`thread/start`/`thread/resume`/`turn/start` response 校验、stderr 有界诊断、spawn/JSON parse/response invalid/timeout/provider 5xx availability failureKind,以及包含 retry error notification 的 fake app-server 自测试。 |
| 错误可观测与脱敏 | 已部分实现 | child env、cwd、workspace 和 Codex home 只输出摘要;stderr tail 有界且标记截断;事件和 failure 统一走 redaction。 |
| 真实 provider turn | 未实现 | 综合联调必须真实完成后才能发布通过。 |
| Codex Secret projection | 实现/已通过主闭环 | runner Job 使用只读 Secret projection 和 writable `CODEX_HOME`Codex 测试凭据来自 `agentrun-v01-provider-codex``auth.json`/`config.toml`。 |
| Codex adapter | 已实现/已通过主闭环 | 当前代码已实现受控 `codex app-server --listen stdio://``initialize`/`thread/start`/`thread/resume`/`turn/start` response 校验、stderr 有界诊断、spawn/JSON parse/response invalid/timeout/provider 5xx availability failureKind,以及包含 retry error notification 的 fake app-server 自测试。 |
| 错误可观测与脱敏 | 已实现主路径 | 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) 手动复验。 |
| hostPath `~/.codex` | 不采用 | 只能通过 Kubernetes Secret projection 注入。 |
+5 -5
View File
@@ -144,7 +144,7 @@ Tekton promotion 可以读取 `deploy/deploy.json` 来 render runtime desired st
## 验收标准
- `G14:/root/agentrun-v01` source worktree 为 `v0.1...origin/v0.1` 且 clean。
- `G14:/root/agentrun-v01` source worktree 为 `v0.1...origin/v0.1` 且 clean`/root/agentrun-v01/.worktree/` 是受控并行 worktree 根,必须被 `.gitignore` 忽略,不得污染固定 source worktree 状态
- `AGENTS.md``docs/reference/` 不得把 `agentrun_dev``agentrun_prod` 写成 `v0.1` 当前 namespace、Argo destination、Pipeline target 或验收目标;只允许在废弃说明和历史背景中提及。
- `agentrun-v01` namespace 存在,且 `agentrun_dev`/`agentrun_prod` 不参与 `v0.1` 发布判定。
- `v0.1-gitops` branch 和 `deploy/gitops/g14/runtime-v01` 成为 Argo desired state 来源。
@@ -158,8 +158,8 @@ Tekton promotion 可以读取 `deploy/deploy.json` 来 render runtime desired st
| 规格项 | 状态 | 说明 |
| --- | --- | --- |
| `v0.1` source branch | 已建立 | `origin/v0.1` 存在。 |
| `G14:/root/agentrun-v01` source worktree | 已建立 | 固定 source worktree 使用 `v0.1` 分支。 |
| `agentrun-v01` namespace | 部分实现 | GitOps rendered manifest 已声明 namespace;运行面初始化和 Argo 同步仍需综合联调验收。 |
| `v0.1-gitops` branch | 部分实现 | Tekton promotion 生成 artifact catalog 与 runtime desired state;后续每个 source commit 仍需由 PipelineRun 推进。 |
| 纯 Tekton/Argo lane | 部分实现 | 已有 `agentrun-ci` Pipeline、BuildKit 镜像发布、GitOps promotion 模板和 Argo Application 模板;真实 runtime sync 与综合联调仍需收口。 |
| `G14:/root/agentrun-v01` source worktree | 已建立/待保持 clean | 固定 source worktree 使用 `v0.1` 分支`.worktree/` 必须忽略,确保并行 worktree 不让固定 source worktree 变 dirty。 |
| `agentrun-v01` namespace | 已实现/已通过主闭环 | GitOps lane 已同步 manager、Postgres、ServiceAccount、SecretRef 和 runner Job 所需对象;发布前仍按 [spec-v01-validation.md](spec-v01-validation.md) 手动复验。 |
| `v0.1-gitops` branch | 实现 | Tekton promotion 生成 artifact catalog 与 runtime desired stateArgo 从 `deploy/gitops/g14/runtime-v01` 同步。 |
| 纯 Tekton/Argo lane | 已实现/已通过主闭环 | `agentrun-ci` Pipeline、BuildKit 镜像发布、GitOps promotion 和 Argo Application 已形成闭环;不得回退到自定义 runner 或 dev/prod。 |
| `dev/prod` 废弃口径 | 已定义 | 本文明确 `agentrun_dev``agentrun_prod` 不作为当前规格目标。 |
+25 -17
View File
@@ -19,22 +19,30 @@ scripts/agentrun-cli.ts
scripts/src/*.ts
```
CLI 官方运行方式固定为 Bun + TypeScript`bun scripts/agentrun-cli.ts ...``node``tsx` 或 shell wrapper 只能作为本地开发辅助,不能成为 `v0.1` 用户文档、CI/CD 或综合联调的权威入口。入口文件只负责参数解析和路由,具体实现拆到 `scripts/src/`。CLI 命令默认输出 JSON;任何命令无 stdout 都是失败。单次 CLI 调用必须在 60 秒内返回,长时间模型 turn 通过 status/events 后续命令观察。
CLI 官方 TypeScript 入口固定为 `scripts/agentrun-cli.ts`。在 G14 非交互 SSH route、CI task 和人工验收命令中,推荐使用仓库内 launcher:
```bash
./scripts/agentrun ...
```
该 launcher 只负责定位 `bun`(优先 `BUN_BIN`、其次 PATH、`$HOME/.bun/bin/bun` 和 G14 默认 `/root/.bun/bin/bun`),然后转入同一个 `scripts/agentrun-cli.ts`。它不实现第二套路由、不读取额外配置、不绕过 TypeScript CLI。`bun scripts/agentrun-cli.ts ...` 仍可在 PATH 已包含 Bun 的交互 shell 中使用;`node``tsx` 或其他 wrapper 只能作为本地开发辅助,不能成为 `v0.1` 综合联调的权威入口。
入口文件只负责参数解析和路由,具体实现拆到 `scripts/src/`。CLI 命令默认输出 JSON;任何命令无 stdout 都是失败。单次 CLI 调用必须在 60 秒内返回,长时间模型 turn 通过 status/events 后续命令观察。
## 初始命令族
```bash
bun scripts/agentrun-cli.ts runs create --json-file <run.json>
bun scripts/agentrun-cli.ts runs show <runId>
bun scripts/agentrun-cli.ts runs events <runId> --after-seq <n> --limit <n>
bun scripts/agentrun-cli.ts commands create <runId> --type turn --json-file <payload.json>
bun scripts/agentrun-cli.ts commands show <commandId>
bun scripts/agentrun-cli.ts runner start --run-id <runId> --backend <backendProfile>
bun scripts/agentrun-cli.ts runner job --run-id <runId> --command-id <commandId>
bun scripts/agentrun-cli.ts runner job --dry-run --run-id <runId> --command-id <commandId> --image <image>
bun scripts/agentrun-cli.ts secrets codex render --dry-run [--codex-home <dir>]
bun scripts/agentrun-cli.ts backends list
bun scripts/agentrun-cli.ts server start|status|stop|logs
./scripts/agentrun runs create --json-file <run.json>
./scripts/agentrun runs show <runId>
./scripts/agentrun runs events <runId> --after-seq <n> --limit <n>
./scripts/agentrun commands create <runId> --type turn --json-file <payload.json>
./scripts/agentrun commands show <commandId> --run-id <runId>
./scripts/agentrun runner start --run-id <runId> --backend <backendProfile>
./scripts/agentrun runner job --run-id <runId> --command-id <commandId>
./scripts/agentrun runner job --dry-run --run-id <runId> --command-id <commandId> --image <image>
./scripts/agentrun secrets codex render --dry-run [--codex-home <dir>]
./scripts/agentrun backends list
./scripts/agentrun server start|status
```
具体参数可以在实现时按代码结构微调,但行为必须保持:
@@ -57,7 +65,7 @@ bun scripts/agentrun-cli.ts server start|status|stop|logs
### T1 CLI run 生命周期
阅读 `AGENTS.md`、本文和 [spec-v01-validation.md](spec-v01-validation.md),然后用正式 AgentRun CLI 创建 run、提交 `turn` command、启动 runner、轮询 command 和 events 直到 terminal_status。确认每个命令输出非空 JSON,60 秒内返回,并给出下一步可执行命令或 logPath。
阅读 `AGENTS.md`、本文和 [spec-v01-validation.md](spec-v01-validation.md),然后用 `./scripts/agentrun` 创建 run、提交 `turn` command、启动 runner、轮询 command 和 events 直到 terminal_status。确认每个命令输出非空 JSON,60 秒内返回,并给出下一步可执行命令或 logPath。若直接执行 `bun scripts/agentrun-cli.ts`,必须先证明当前 shell 的 PATH 能找到 BunG14 route 默认以 `./scripts/agentrun` 为准。
### T2 CLI 错误可见性
@@ -76,7 +84,7 @@ bun scripts/agentrun-cli.ts server start|status|stop|logs
| 规格项 | 状态 | 说明 |
| --- | --- | --- |
| AgentRun CLI 规格 | 已定义 | 本文为 v0.1 CLI 权威。 |
| `scripts/agentrun-cli.ts` | 部分实现 | 已提供 run/command/event/backend/server 基础命令和 JSON envelope。 |
| CLI 调 manager REST | 部分实现 | CLI 通过 `ManagerClient` 调 manager REST当前自测试使用内存 manager。 |
| runner start/job | 部分实现 | `runner start` 可执行 host process runner`runner job --dry-run` 可渲染 Kubernetes Job JSON`runner job` 正式路径通过 manager REST 创建 Kubernetes Job 并快速返回 job identity、SecretRef 和轮询命令。 |
| CLI 测试规格 | 已定义 | 综合联调见 [spec-v01-validation.md](spec-v01-validation.md)。 |
| `scripts/agentrun-cli.ts` | 实现 | 已提供 run/command/event/backend/server 基础命令和 JSON envelope`scripts/agentrun` 是同一入口的 Bun 定位 launcher。 |
| CLI 调 manager REST | 实现 | CLI 通过 `ManagerClient` 调 manager REST;自测试用内存 manager,综合联调必须指向真实 `agentrun-v01` manager。 |
| runner start/job | 实现 | `runner start` 可执行 host process runner`runner job --dry-run` 可渲染 Kubernetes Job JSON`runner job` 正式路径通过 manager REST 创建 Kubernetes Job 并快速返回 job identity、SecretRef、retention 和轮询命令。 |
| CLI 测试规格 | 已定义/已验证主闭环 | 综合联调见 [spec-v01-validation.md](spec-v01-validation.md);每次发布仍按手动交互验收复跑。 |
+4 -4
View File
@@ -73,8 +73,8 @@ Secret 名称和 key 可以在实现时按 Kubernetes 命名限制微调,但
| 规格项 | 状态 | 说明 |
| --- | --- | --- |
| Postgres durable store 规格 | 已定义 | 本文为 v0.1 存储权威。 |
| StatefulSet/Service/PVC | 实现 | 需要后续 GitOps lane 初始化。 |
| migration ledger | 已实现骨架 | `agentrun-mgr` 启动 Postgres adapter 时幂等创建 `agentrun_schema_migrations` 并记录 migration id/checksumlive DB 迁移验收仍依赖后续 GitOps lane 初始化。 |
| manager Postgres adapter | 已实现骨架 | `agentrun-mgr` 通过 `DATABASE_URL` 启用 Postgres adapter,持久化 runs、commands、events、runners、backends 和 leases;缺少 `DATABASE_URL` 时 live runtime fail fastmemory 只允许显式 self-test/dev。 |
| health/readiness store 状态 | 已实现骨架 | health/readiness 返回 adapter、reachable、migrationReady、migrationId、failureKind 和 redacted Secret 状态,不输出 DSN 明文。 |
| StatefulSet/Service/PVC | 实现/已通过主闭环 | `agentrun-v01-postgres` StatefulSet、Service 和 PVC 已由 GitOps runtime 提供,作为 `agentrun-v01` durable store。 |
| migration ledger | 已实现/已通过主闭环 | `agentrun-mgr` 启动 Postgres adapter 时幂等创建 `agentrun_schema_migrations` 并记录 migration id/checksumreadiness 必须显示 migration ready。 |
| manager Postgres adapter | 已实现/已通过主闭环 | `agentrun-mgr` 通过 `DATABASE_URL` 启用 Postgres adapter,持久化 runs、commands、events、runners、backends 和 leases;缺少 `DATABASE_URL` 时 live runtime fail fastmemory 只允许显式 self-test/dev。 |
| health/readiness store 状态 | 已实现 | health/readiness 返回 adapter、reachable、migrationReady、migrationId、failureKind 和 redacted Secret 状态,不输出 DSN 明文。 |
| file/sqlite durable store | 不采用 | 只可用于临时本地测试,不作为 v0.1 runtime truth。 |
@@ -114,7 +114,7 @@ Secret 创建和轮换不由 source branch 自动生成;source branch 只声
`v0.1` 提供只读 CLI 工具,用 operator 本地 `~/.codex/auth.json``~/.codex/config.toml` 构造 Kubernetes Secret 创建计划:
```bash
bun scripts/agentrun-cli.ts secrets codex render --dry-run
./scripts/agentrun secrets codex render --dry-run
```
可选参数:
@@ -154,8 +154,8 @@ bun scripts/agentrun-cli.ts secrets codex render --dry-run
| 规格项 | 状态 | 说明 |
| --- | --- | --- |
| Secret 分发规格 | 已定义 | 本文为 v0.1 provider credential 分发权威。 |
| Kubernetes SecretRef 注入 | 部分实现 | runner Job dry-run 和正式 Job 创建路径已按 run `executionPolicy.secretScope.providerCredentials` 生成 Secret volume projection、writable runtime home 和 `AGENTRUN_CODEX_SECRET_HOME`;真实 Secret 与 Codex turn 仍需综合联调验收。 |
| Codex Secret dry-run 工具 | 已实现 | `bun scripts/agentrun-cli.ts secrets codex render --dry-run` 只输出 Secret 创建计划、hash 和 redacted manifest 摘要,不执行 apply。 |
| Codex auth/config file projection | 部分实现 | backend readiness 检查 `auth.json`/`config.toml` 可读性,缺失时返回 `secret-unavailable`self-test 使用临时文件模拟投影。 |
| redaction 最小规则 | 部分实现 | Secret dry-run 工具、event、Job dry-run 输出self-test 已验证不打印测试 token;复杂审计仍待后续补齐。 |
| Kubernetes SecretRef 注入 | 已实现/已通过主闭环 | runner Job dry-run 和正式 Job 创建路径已按 run `executionPolicy.secretScope.providerCredentials` 生成 Secret volume projection、writable runtime home 和 `AGENTRUN_CODEX_SECRET_HOME`;真实 Secret 与 Codex turn 已通过主闭环。 |
| Codex Secret dry-run 工具 | 已实现 | `./scripts/agentrun secrets codex render --dry-run` 只输出 Secret 创建计划、hash 和 redacted manifest 摘要,不执行 apply。 |
| Codex auth/config file projection | 已实现主路径 | backend readiness 检查 `auth.json`/`config.toml` 可读性,缺失时返回 `secret-unavailable`真实 runner Job 将只读 projection 复制到 writable `CODEX_HOME`。 |
| redaction 最小规则 | 已实现主路径 | Secret dry-run 工具、event、Job dry-run 输出self-test 和真实主闭环均不打印 Secret value;复杂审计按 [spec-v01-validation.md](spec-v01-validation.md) 人工抽查。 |
| 外部 secret manager | 未采用 | 如需 Vault/ExternalSecrets/SOPS,后续单独更新规格。 |
+4 -4
View File
@@ -16,7 +16,7 @@ AgentRun 是面向 UniDesk 与 HWLAB 的共享 Code Agent 执行基础设施。`
## 语言与协议选型
AgentRun `v0.1` 的自研组件优先使用 Bun + TypeScript 实现:`agentrun-mgr``agentrun-runner`、backend adapter、Codex backend、AgentRun CLI 和后续 scheduler 都属于该边界。官方 CLI 入口固定为 `bun scripts/agentrun-cli.ts`,入口只做参数解析和路由,复杂逻辑拆到 `scripts/src/``src/`。Postgres、Kubernetes、Tekton、Argo CD、YAML manifest 和 shell 级容器启动命令属于外部运行面或部署面,不受“必须 TypeScript 实现”的约束。
AgentRun `v0.1` 的自研组件优先使用 Bun + TypeScript 实现:`agentrun-mgr``agentrun-runner`、backend adapter、Codex backend、AgentRun CLI 和后续 scheduler 都属于该边界。官方 TypeScript CLI 入口固定为 `scripts/agentrun-cli.ts`,入口只做参数解析和路由,复杂逻辑拆到 `scripts/src/``src/`G14/CI/人工非交互命令使用 `./scripts/agentrun` launcher 启动同一入口。Postgres、Kubernetes、Tekton、Argo CD、YAML manifest 和 shell 级容器启动命令属于外部运行面或部署面,不受“必须 TypeScript 实现”的约束。
`backendProfile=codex``v0.1` 协议固定为 Codex CLI app-server JSON-RPC over stdiorunner/backend adapter 启动受控 `codex app-server --listen stdio://` 子进程,经 stdin/stdout 发送换行分隔 JSON-RPC,请求顺序至少覆盖 `initialize``thread/start``thread/resume``turn/start`。直接调用 Responses HTTP、OpenAI SDK、`codex exec` 一次性输出或文本 fallback 只能作为诊断/自测试辅助,不能作为 Codex backend 综合联调通过证据。
@@ -167,7 +167,7 @@ Manager 负责校验、保存和返回这些字段;runner 只能消费已保
| Codex backend 规格 | 已定义 | 见 [spec-v01-backend-codex.md](spec-v01-backend-codex.md)。 |
| AgentRun CLI 规格 | 已定义 | 见 [spec-v01-cli.md](spec-v01-cli.md)。 |
| Scheduler deferred 规格 | 已定义 | 见 [spec-v01-scheduler.md](spec-v01-scheduler.md)。 |
| `agentrun-mgr` 实现 | 已实现骨架 | 已有 REST API、Postgres durable store 选择、migration ledger、runner claim/lease/report、health/readiness 和 self-test memory 模式骨架;仍需 G14 `agentrun-v01` 真实 Postgres/GitOps 验收。 |
| `agentrun-runner` 实现 | 已实现骨架 | 已有 host process runner 与 Kubernetes Job dry-run 渲染骨架runner 通过 manager API claim/poll/report,不直连 Postgres;真实 Kubernetes Job 联调仍需验收。 |
| 第一真实 backend | 已实现骨架 | Codex app-server stdio backend 已有协议、失败分类、脱敏和 fake self-test;真实 Codex provider turn 仍需综合联调验收。 |
| `agentrun-mgr` 实现 | 已实现/已通过主闭环 | 已有 REST API、Postgres durable store、migration ledger、runner claim/lease/report、health/readiness 和 self-test memory 模式;真实 `agentrun-v01` runtime 已通过 Postgres/GitOps/readiness 和 run lifecycle 主闭环。 |
| `agentrun-runner` 实现 | 已实现/已通过主闭环 | host process runner 与 Kubernetes Job 共用 `runOnce`runner 通过 manager API claim/poll/report,不直连 Postgres;真实 Kubernetes Job 已完成 Codex turn。 |
| 第一真实 backend | 已实现/已通过主闭环 | Codex app-server stdio backend 已有协议、失败分类、脱敏和 fake self-test;真实 Codex provider turn 已通过 RESTful API 与 CLI 主闭环。 |
| 自动 scheduler | Deferred | 不作为 `v0.1` 第一阶段验收目标。 |
+18 -5
View File
@@ -63,7 +63,7 @@ CLI 交互联调验证正式操作入口是否能支撑人工使用和自动脚
CLI 交互联调必须满足:
- 使用 `bun scripts/agentrun-cli.ts` 完成 create run、create command、start runner、show command 和 events polling。
- 使用 `./scripts/agentrun` 完成 create run、create command、start runner、show command 和 events polling;直接使用 `bun scripts/agentrun-cli.ts` 前必须证明当前 shell 的 PATH 能找到 Bun
- 每个 CLI 调用默认输出 JSON,stdout 不为空,且 60 秒内返回;长时间模型 turn 必须通过后续 status/events 命令观察。
- 创建类命令必须返回 `runId``commandId`、status、job/process identity、logPath 和下一步 poll command;查询类命令必须返回当前 state、terminal_status、failureKind 和 event cursor。
- CLI 输出、日志摘要和错误信息必须保留可观测性,不能只返回成功/失败布尔值;错误必须能区分 missing SecretRef、provider auth failure、runner lease conflict、backend failure 和 infra failure。
@@ -120,7 +120,7 @@ CLI 与 RESTful API 可以复用同一个真实 run 做联调。若两者观察
### T4 CLI 交互联调
阅读 `AGENTS.md`、本文和 [cli.md](cli.md),然后用 AgentRun CLI 手动测试以下内容:在真实 `agentrun-v01` 运行面创建 run,提交一个 `turn` command,启动或触发真实 runner,并轮询 command status 与 run events 直到出现 terminal_status。确认每个步骤只使用正式 CLI 命令,不使用 debug 子命令或 mock/fake backend;每次调用 60 秒内返回非空 JSON,包含 runId、commandId、status、job/process identity、logPath 或下一步 poll command;日志、event 和 CLI 输出不得出现 provider credential、DSN password、token 或 URL credential 明文。最后重启或滚动 `agentrun-mgr`,再用 CLI 查询同一个 run,确认 command state、events 和 terminal_status 仍可见。
阅读 `AGENTS.md`、本文和 [spec-v01-cli.md](spec-v01-cli.md),然后用 `./scripts/agentrun` 手动测试以下内容:在真实 `agentrun-v01` 运行面创建 run,提交一个 `turn` command,启动或触发真实 runner,并轮询 command status 与 run events 直到出现 terminal_status。确认每个步骤只使用正式 CLI 命令,不使用 debug 子命令或 mock/fake backend;每次调用 60 秒内返回非空 JSON,包含 runId、commandId、status、job/process identity、logPath 或下一步 poll command;日志、event 和 CLI 输出不得出现 provider credential、DSN password、token 或 URL credential 明文。最后重启或滚动 `agentrun-mgr`,再用 CLI 查询同一个 run,确认 command state、events 和 terminal_status 仍可见。
### T5 RESTful API 交互联调
@@ -128,15 +128,28 @@ CLI 与 RESTful API 可以复用同一个真实 run 做联调。若两者观察
### T6 CLI 与 RESTful 观察一致性
阅读本文、[cli.md](cli.md) 和 [spec-v01-services.md](spec-v01-services.md),然后对同一个真实 run 分别用 CLI 和 RESTful API 查询 run、command、events 和 terminal_status。确认 CLI 只是正式 API 的受控操作入口,不维护一套独立状态;两种交互面看到的 run id、command id、state、terminal_status、failureKind、event seq 和 redaction 结果必须一致。
阅读本文、[spec-v01-cli.md](spec-v01-cli.md) 和 [spec-v01-services.md](spec-v01-services.md),然后对同一个真实 run 分别用 CLI 和 RESTful API 查询 run、command、events 和 terminal_status。确认 CLI 只是正式 API 的受控操作入口,不维护一套独立状态;两种交互面看到的 run id、command id、state、terminal_status、failureKind、event seq 和 redaction 结果必须一致。
### T7 手动负向交互验收
阅读本文和各组件 spec,然后在真实 `agentrun-v01` 运行面用正式 CLI 或 RESTful API 手动验证以下负向场景。该项是人工验收标准,不是自动化脚本、CI 门禁或固定 gate:
- 使用非法 `tenantId`、非法 `backendProfile` 或缺失必填 run 字段创建 run,确认返回 JSON,包含 `failureKind``message`
- 对同一 run 使用相同 idempotency key 提交相同 command 两次,再用相同 key 提交不同 payload,确认前两次返回同一个 command,第三次结构化失败。
- 对同一 pending run 启动两个真实 runner 或重复 claim,确认只有一个 owner 成功,失败方为 `runner-lease-conflict` 或等价 failureKind,且不继续调用 backend。
- 用不存在的 provider SecretRef 创建 run 并启动 runner,确认失败为 `secret-unavailable`,不会降级为 mock pass,也不会打印 Secret value。
- 对同一 run 分页读取 events,确认 `seq` 单调、`afterSeq` 翻页无重复、重复读取同一页不会改变 durable facts。
T7 只定义人工验收的检查面和判定口径。若后续为减少人工操作引入 helper 命令,它只能输出手动步骤、当前证据或 dry-run 计划,不能把这些负向场景改造成阻断发布的自动门禁。
## 规格的实现情况
| 规格项 | 状态 | 说明 |
| --- | --- | --- |
| 两层验证模型 | 已定义 | 本文为 v0.1 验证权威。 |
| 自测试 task | 实现 | 需要后续随组件实现补齐。 |
| 综合联调 task/runbook | 未实现 | 需要 runtime、Postgres、SecretRef 和真实 backend 就绪后补齐。 |
| 自测试 task | 实现 | `src/selftest/run.ts` 自动发现 `src/selftest/cases/*.ts`;覆盖 redaction/Postgres contract、manager memory、runner Job render/create、Codex fake app-server stdio 和 Secret render。 |
| 综合联调验收规格 | 已增强 | 本文保留人工交互验收模型;T4-T7 定义 CLI、RESTful、一致性和负向场景的手动验收标准,不新增自动脚本或门禁。 |
| CLI 交互联调标准 | 已定义 | 必须只使用正式 CLI,验证真实 run 生命周期和可观测输出。 |
| RESTful API 交互联调标准 | 已定义 | 必须直连真实 manager HTTP JSON API,验证服务合同和 durable facts。 |
| 真实主闭环 | 已通过 | 当前 v0.1 已通过真实 Tekton/Argo、Postgres、SecretRef、Kubernetes runner Job、Codex stdio turn、RESTful API 和 CLI 主闭环;每次发布仍需按本文手动复验。 |
| mock 作为发布证据 | 不采用 | mock 只能证明自测试通过。 |
+20
View File
@@ -0,0 +1,20 @@
#!/usr/bin/env sh
set -eu
SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
ROOT_DIR=$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd)
if [ -n "${BUN_BIN:-}" ] && [ -x "${BUN_BIN:-}" ]; then
BUN="$BUN_BIN"
elif command -v bun >/dev/null 2>&1; then
BUN="$(command -v bun)"
elif [ -n "${HOME:-}" ] && [ -x "${HOME:-}/.bun/bin/bun" ]; then
BUN="${HOME:-}/.bun/bin/bun"
elif [ -x "/root/.bun/bin/bun" ]; then
BUN="/root/.bun/bin/bun"
else
printf '%s\n' '{"ok":false,"failureKind":"infra-failed","message":"bun runtime not found; set BUN_BIN or install Bun"}'
exit 127
fi
exec "$BUN" "$ROOT_DIR/scripts/agentrun-cli.ts" "$@"
+6 -3
View File
@@ -74,10 +74,13 @@ export async function createKubernetesRunnerJob(options: { store: AgentRunStore;
logPath: `kubectl -n ${render.namespace} logs job/${render.jobName}`,
},
secretRefs: render.secretRefs.map((item) => ({ profile: item.profile, name: item.secretRef.name, namespace: item.secretRef.namespace ?? render.namespace, keys: item.secretRef.keys ?? [], mountPath: item.runtimeMountPath, projectionPath: item.projectionMountPath, writableCopy: true, valuesPrinted: false })),
retention: {
ttlSecondsAfterFinished: render.ttlSecondsAfterFinished,
},
pollCommands: {
run: `bun scripts/agentrun-cli.ts runs show ${run.id} --manager-url ${managerUrl}`,
command: `bun scripts/agentrun-cli.ts commands show ${commandId} --run-id ${run.id} --manager-url ${managerUrl}`,
events: `bun scripts/agentrun-cli.ts runs events ${run.id} --manager-url ${managerUrl} --after-seq 0 --limit 100`,
run: `./scripts/agentrun runs show ${run.id} --manager-url ${managerUrl}`,
command: `./scripts/agentrun commands show ${commandId} --run-id ${run.id} --manager-url ${managerUrl}`,
events: `./scripts/agentrun runs events ${run.id} --manager-url ${managerUrl} --after-seq 0 --limit 100`,
},
warnings: render.warnings,
kubernetes: {
+9 -5
View File
@@ -47,21 +47,25 @@ export function renderRunnerJobDryRun(options: RunnerJobRenderOptions): JsonReco
sourceCommit: render.sourceCommit,
},
secretRefs: render.secretRefs.map((item) => ({ profile: item.profile, name: item.secretRef.name, namespace: item.secretRef.namespace ?? render.namespace, keys: item.secretRef.keys ?? [], mountPath: item.runtimeMountPath, projectionPath: item.projectionMountPath, writableCopy: true, valuesPrinted: false })),
retention: {
ttlSecondsAfterFinished: render.ttlSecondsAfterFinished,
},
pollCommands: {
run: `bun scripts/agentrun-cli.ts runs show ${options.run.id} --manager-url ${options.managerUrl}`,
events: `bun scripts/agentrun-cli.ts runs events ${options.run.id} --manager-url ${options.managerUrl} --after-seq 0 --limit 100`,
run: `./scripts/agentrun runs show ${options.run.id} --manager-url ${options.managerUrl}`,
events: `./scripts/agentrun runs events ${options.run.id} --manager-url ${options.managerUrl} --after-seq 0 --limit 100`,
},
warnings: render.warnings,
manifest: render.manifest,
};
}
export function renderRunnerJobManifest(options: RunnerJobRenderOptions): { manifest: JsonRecord; namespace: string; jobName: string; runnerId: string; attemptId: string; sourceCommit: string; serviceAccountName: string; secretRefs: CredentialProjection[]; warnings: string[] } {
export function renderRunnerJobManifest(options: RunnerJobRenderOptions): { manifest: JsonRecord; namespace: string; jobName: string; runnerId: string; attemptId: string; sourceCommit: string; serviceAccountName: string; secretRefs: CredentialProjection[]; warnings: string[]; ttlSecondsAfterFinished: number } {
const namespace = options.namespace ?? "agentrun-v01";
const attemptId = options.attemptId ?? `attempt_${Date.now().toString(36)}`;
const runnerId = options.runnerId ?? `runner_${shortHash(`${options.run.id}:${attemptId}:${options.commandId}`)}`;
const sourceCommit = options.sourceCommit ?? process.env.AGENTRUN_SOURCE_COMMIT ?? "unknown";
const serviceAccountName = options.serviceAccountName ?? "agentrun-v01-runner";
const ttlSecondsAfterFinished = options.ttlSecondsAfterFinished ?? 86_400;
const jobName = `agentrun-v01-runner-${shortDnsHash(options.run.id, attemptId)}`;
const secretRefs = credentialProjections(options.run, namespace);
const warnings: string[] = [];
@@ -82,7 +86,7 @@ export function renderRunnerJobManifest(options: RunnerJobRenderOptions): { mani
},
spec: {
backoffLimit: options.backoffLimit ?? 0,
ttlSecondsAfterFinished: options.ttlSecondsAfterFinished ?? 86_400,
ttlSecondsAfterFinished,
template: {
metadata: {
labels: labels(options.run, jobName),
@@ -122,7 +126,7 @@ export function renderRunnerJobManifest(options: RunnerJobRenderOptions): { mani
},
},
};
return { manifest, namespace, jobName, runnerId, attemptId, sourceCommit, serviceAccountName, secretRefs, warnings };
return { manifest, namespace, jobName, runnerId, attemptId, sourceCommit, serviceAccountName, secretRefs, warnings, ttlSecondsAfterFinished };
}
function runnerEnv(options: RunnerJobRenderOptions, context: { namespace: string; jobName: string; runnerId: string; attemptId: string; sourceCommit: string; secretRefs: CredentialProjection[] }): JsonRecord[] {
+6 -2
View File
@@ -1,5 +1,5 @@
import assert from "node:assert/strict";
import { chmod, mkdir, writeFile } from "node:fs/promises";
import { chmod, mkdir, readFile, writeFile } from "node:fs/promises";
import path from "node:path";
import { startManagerServer } from "../../mgr/server.js";
import { MemoryAgentRunStore } from "../../mgr/store.js";
@@ -23,6 +23,7 @@ const selfTest: SelfTestCase = async (context) => {
});
assert.equal(rendered.dryRun, true);
assert.equal(rendered.mutation, false);
assert.equal(((rendered.retention as JsonRecord).ttlSecondsAfterFinished), 86_400);
assert.equal((rendered.jobIdentity as { serviceAccountName?: string }).serviceAccountName, "agentrun-v01-runner");
assertRunnerJobUsesWritableCodexHome(rendered.manifest as JsonRecord, context.codexHome);
assertNoSecretLeak(rendered);
@@ -56,11 +57,14 @@ console.log(JSON.stringify({ apiVersion: manifest.apiVersion, kind: manifest.kin
const jobItem = await createRunWithCommand(jobClient, context, "job create smoke", "selftest-job-create", 15_000);
const created = await jobClient.post(`/api/v1/runs/${jobItem.runId}/runner-jobs`, { commandId: jobItem.commandId, attemptId: "attempt_selftest_create" });
assert.equal((created as { mutation?: unknown }).mutation, true);
assert.equal(((created as JsonRecord).retention as JsonRecord).ttlSecondsAfterFinished, 86_400);
const manifest = JSON.parse(await readFile(createdManifest, "utf8")) as JsonRecord;
assert.equal((manifest.spec as JsonRecord).ttlSecondsAfterFinished, 86_400);
assertNoSecretLeak(created);
} finally {
await new Promise<void>((resolve) => serverWithKubectl.server.close(() => resolve()));
}
return { name: "runner-k8s-job", tests: ["runner-k8s-job-dry-run", "runner-k8s-job-create-api"] };
return { name: "runner-k8s-job", tests: ["runner-k8s-job-dry-run", "runner-k8s-job-create-api", "runner-k8s-job-retention-ttl"] };
} finally {
await new Promise<void>((resolve) => server.server.close(() => resolve()));
}