Files
pikasTech-agentrun/docs/reference/spec-v01-agentrun-runner.md
T

221 lines
21 KiB
Markdown
Raw 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 agentrun-runner 服务规格
`agentrun-runner` 是 AgentRun `v0.1` 的手动启动执行入口。它以 per-run runner Job 方式运行,必须从 `agentrun-mgr` claim run,调用 backend adapter,并把 events、heartbeat、command ack 和 command terminal status 写回 manager。同一 runner Job 在 idle timeout 内必须继续 poll 同一 run 的后续 command,不得把每个 turn 都变成重新 bundle 和新 runner Job。
## 在系统中的职责划分
- 作为 Kubernetes Job 或受控 host process 启动;不作为普通业务客户端直接调用的长驻公共服务。
- 从 manager register、claim run、续租 lease、poll commands、ack command、append events、patch command status;只有 runner 级不可恢复失败或显式 run terminal 时才 patch run status。
- 根据 run 中的 `backendProfile``executionPolicy.secretScope` 调用 backend adapter。
- 根据 manager 解析出的 RuntimeAssembly materialize backend image、profile Secret、session 和初始资源;四要素字段权威见 [spec-v01-runtime-assembly.md](spec-v01-runtime-assembly.md),本文只规定 runner 消费边界。
- 将 backend stdout/stderr、assistant message、tool call、error 和 command terminal status 归一化为 manager event。
- 提供可定位的 job/process identity、logPath、attempt id 和 failureKind。
- 不直连 Postgres,不扩大 workspace、network、approval 或 secret scope。
## 内部架构
`v0.1` 默认 runner 形态是 `agentrun-v01` namespace 中的短生命周期 Job,Job 名称建议使用 `agentrun-v01-runner-<runId>-<attempt>`。短生命周期指 Job 不作为公共长驻服务;Job 内部必须支持同一 run 的多 command loop,直到 run 被 cancel/terminal、lease 冲突或 idle timeout。MVP 允许 CLI 启动受控本地 process,但该 process 仍必须通过 manager API claim/report。
Runner 自研代码优先使用 Bun + TypeScript。Kubernetes Job 和 CLI 启动的 host process 必须进入同一套 TS runner 模块,避免一套 Job 逻辑和一套本地调试逻辑分叉;容器镜像可以直接运行 TS 入口或运行由同一源码构建出的 JS artifact。
Runner 启动参数必须显式包含:
- manager API base URL。
- runId 和 attemptId。
- backendProfile。
- logPath 或 Kubernetes job/pod identity。
- source commit/build metadata。
Runner Secret 只能通过 Kubernetes Secret projection、ServiceAccount/RBAC 或受控 Secret API 读取获得。Codex 测试凭据投影规则见 [spec-v01-secret-distribution.md](spec-v01-secret-distribution.md) 和 [spec-v01-backend-codex.md](spec-v01-backend-codex.md)。
Kubernetes Job runner 必须把 credential source 与 runtime home 分开:Secret volume 只读挂在 `/var/run/agentrun/secrets/...``/home/agentrun``emptyDir` 提供可写空间,`CODEX_HOME` 指向当前 run/profile 的 writable runtime home`AGENTRUN_CODEX_SECRET_HOME` 指向当前 `backendProfile` 对应的只读 projection。runner/backend 在启动 provider 前只复制授权文件,不打印内容。`codex``deepseek``minimax-m3` profile 不得共享同一个可写 runtime home,除非它们运行在不同的 per-run Kubernetes Job 且该目录由 Job 独占 emptyDir 提供。
RuntimeAssembly P0 中 `SessionRef` 可以显式为 `null`runner 不得把完整 `CODEX_HOME`、Secret projection 或节点 host path 当作 session store。`ResourceBundleRef` P0 收敛为 `kind="gitbundle"`runner 已支持把 `repoUrl + ref/materialized commit + bundles[]` checkout 到 `AGENTRUN_WORKSPACE_ROOT` 下的隔离目录,按 `subpath -> target_path` 复制到 workspace,并记录 requested ref/commit、actual commit、tree 和 bundles 摘要。工具来自 workspace `tools/`skill 来自 workspace `.agents/skills`,不能把用户上传文件、inline seed、旧字段或 env dump 混入 gitbundle。
### 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/` 仍走 emptyDircodex 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/restorePR #78 已回退的 replacement 逻辑同源,禁止任何变体)。
Runner K8s manifest 增量(spec 形态参考):
```yaml
volumes:
- name: runner-home # emptyDirauth.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 gitbundle 装配
Runner materialize `ResourceBundleRef.kind="gitbundle"` 后必须按固定顺序处理资源:先从 repo/ref 解析并 checkout 实际 commit,再按 `bundles[]` 复制文件或目录,再准备 workspace `tools/`,再发现 `.agents/skills`,最后读取 `promptRefs` 并生成当前 command 的 assembled initial prompt 摘要。旧字段输入必须由 schema 校验直接拒绝,不得在 runner 里兼容。
gitbundle skill 聚合规则:
- 聚合目录固定在 materialized workspace 下的 `.agents/skills`,每个 skill 使用稳定目录名,例如 `.agents/skills/<name>/SKILL.md`
- Runner 必须把该聚合目录通过环境或 backend option 暴露给 Codex runtime;对于需要兼容 Codex skill discovery 的实现,应设置 `AGENTRUN_SKILLS_DIRS` 或等价字段,并可同步设置业务方约定的 `<PROJECT>_CODE_AGENT_SKILLS_DIRS`
- 聚合只允许 copy `bundles[]` 指定的 Git 子树,不允许引用 `/app/skills`、host path、Secret volume 或用户上传临时目录。
- Event/result 只输出 skill name、manifest path 摘要、hash、bytes、summary 和 target。
workspace `tools/` 装配规则:
- runner 把 workspace `tools/` 暴露到 `PATH`;如 runtime 需要稳定 bin 目录,只能安装执行原始 workspace tool 的 shim,不能复制 tool 文件导致相对路径语义改变。repo 内的短命令文件本身承担 wrapper 语义。
- `tools/` 顶层 `.ts` 脚本必须带 shebang;带 shebang 的脚本会被 `chmod +x`
- Event/result 只输出工具文件名、hash、bytes、shebang 摘要和 count,不输出脚本文本。
`promptRefs` 装配规则:
- Runner 只读取当前 bundle 内的 prompt 文件,按数组顺序生成 `initialPrompt`
- 对没有 `threadId`、将执行 `thread/start` 的第一条 turnrunner/backend 必须把 `initialPrompt` 注入到该 turn 的输入前缀,并记录 `initialPromptInjected=true`
- 对已有 `SessionRef.threadId` 或 command `payload.threadId`、将执行 `thread/resume` 的 turnrunner/backend 不得重复注入 `initialPrompt`,必须记录 `initialPromptInjected=false``reason=thread-resume`
- `initialPrompt` 不得包含会话历史;用户本轮 message 仍来自 command payload 原文。
## HWLAB v0.2 执行经验承接
Runner 承接的是 HWLAB v0.2 原有 Code Agent 的执行层经验,不承接 HWLAB cloud-api 的业务路由和权限判断。实现时优先参考 HWLAB 已验证的代码路径,而不是重新定义 Codex session、trace 和输出裁剪语义:
| HWLAB v0.2 参考能力 | 参考入口 | Runner 承接规则 |
| --- | --- | --- |
| Codex app-server stdio thread/turn 生命周期 | `internal/cloud/codex-stdio-session.ts` | 有 command `payload.threadId``SessionRef.threadId` 时执行 resume,再 start turn;无标准 `threadId` 时 start threadevents、result 和 session record 都以 `threadId` 为唯一 thread identityturn terminal 才能上报 completed。 |
| cancel/interrupt | `internal/cloud/server-code-agent-http.ts``internal/cloud/codex-stdio-session.ts` | runner 必须轮询 manager cancel 状态并中止 backendbackend 不支持精确 interrupt 时终止受控进程组。 |
| runnerTrace 事件可见性 | `internal/cloud/code-agent-trace-store.ts` | backend 输出必须转成 manager events;每个 terminal/错误/取消都要有事件和 final status。 |
| workspace-write 边界 | `internal/cloud/code-agent-contract.ts` | runner 只使用 ResourceBundleRef materialized workspace,不猜 HWLAB Pod 的 `/workspace/hwlab` 或 host path。 |
| prompt/skill 装配 | `internal/cloud/codex-stdio-session.ts``internal/cloud/codex-stdio-session-helpers.ts` | HWLAB 旧业务 prompt 与 skill discovery 经验迁入 ResourceBundleRef `promptRefs` 和 gitbundle `.agents/skills`runner 只按 Git commit/path 装配,不内建 HWLAB 业务。 |
| Secret 与 writable CODEX_HOME 分离 | `internal/cloud/code-agent-contract.ts``docs/reference/code-agent-chat-readiness.md` | profile Secret 只读投影,复制到当前 run/profile writable runtime home;不同 profile 不共享 runtime home。 |
| bounded stdout/stderr | `docs/reference/code-agent-chat-readiness.md` | `command_output` 记录摘要、字节数、截断标记和必要引用;不得把大输出直接塞进单个 event/result。 |
Kubernetes Job runner 必须设置有限保留时间。`v0.1` 默认 `ttlSecondsAfterFinished=86400`,用于保留最近完成 Job 的调试窗口,同时避免长期堆积 `Completed` runner Job 污染运行面观察。该 TTL 是 Job manifest 的运行面属性,不是 CI/CD 门禁;需要延长保留时间时必须通过受控 Job render/input 显式覆盖,并在 issue 或 PR 中说明原因。
## Runner 生命周期
标准状态方向:
```text
starting -> registered -> claimed -> running -> terminal
starting -> registered -> claim_failed
claimed -> running -> backend_failed
claimed -> running -> cancelled
claimed -> lease_lost
```
规则:
- runner 必须先 register,再 claim runclaim 失败不能继续调用 backend。
- lease heartbeat 必须通过 manager lease/status 可观察;不得把周期性心跳或 backend running tick 写成 durable trace event 刷屏。长 turn 只在 `backend-turn-finished` 中输出有界 progress 摘要;过期或冲突时写入 failure event 或明确退出原因。
- replacement runner 在 claim 时遇到 `runner-lease-conflict`,且 manager 响应显示旧 lease 未过期但可能来自已删除 pod 或失联 runner 时,不得立即把同一 HWLAB session 判为失败;应在 `AGENTRUN_RUNNER_CLAIM_RETRY_TIMEOUT_MS` 窗口内按 `AGENTRUN_RUNNER_CLAIM_RETRY_INTERVAL_MS` 等待旧 lease 过期并重试 claim。等待期间必须写入或输出 `runner-claim-waiting-for-stale-lease`,成功接管后写入 `runner-claim-recovered`;超过窗口才把 `runner-lease-conflict` 作为 terminal failureKind。并发双 runner 的正常冲突仍必须拒绝,不能抢占未 stale 的 active lease。
- command 只能从 manager poll;不得从本地文件或临时参数伪造正式 command。
- runner 的普通 poll 只选择 pending `turn`;当 backend adapter 暴露 active turn control 后,runner 才在同 run 内轮询 pending `steer` commandack 后调用 backend 的 steer 能力并单独终结该 steer command。`steer` completed 只表示 `turn/steer` RPC 已被 backend 接受,不表示 target turn 已产生后续 assistant/tool 事件;runner 事件必须携带 target command 和 delivery 语义,target 活性由 command result/session status 的 `liveness` 判断。active turn 结束后到达的 steer 必须结构化 blocked,不得启动新 turn,也不得把 run 标为 terminal。
- backend 产生的所有可见输出必须先经过 adapter normalization 和 redaction,再 append 到 managerbackend_status 至少包含 redacted profile/backendKind/protocol 摘要。
- 单个 command terminal 上报后 runner 不应立即退出,而应继续 poll 同一 run 的 pending command,直到 idle timeout、lease 冲突或 run terminal。退出码与 runner loop 终态必须一致或在日志中可解释。
## Manager API 交互
Runner 只使用 manager 私有 API
```http
POST /api/v1/runners/register
POST /api/v1/runs/:runId/claim
PATCH /api/v1/runs/:runId/lease
GET /api/v1/runs/:runId/commands?afterSeq=0&limit=20
POST /api/v1/runs/:runId/events
PATCH /api/v1/runs/:runId/status
POST /api/v1/commands/:commandId/ack
PATCH /api/v1/commands/:commandId/status
```
`PATCH /api/v1/commands/:commandId/status` 是普通 turn 完成的权威上报入口;它只能终结 command,并可更新 run 的 SessionRef/thread 摘要。`PATCH /api/v1/runs/:runId/status` 只用于 runner 级不可恢复失败或显式 run terminal,不得在每个成功 turn 后调用。
Runner inbound HTTP 不是业务 API。若实现本地诊断端点,只允许 `GET /health``GET /debug/status`,并且只能暴露在本地或 pod 内部调试面。
## Failure 与 Redaction
Runner 必须把以下失败归类为结构化 failureKind
- `secret-unavailable`SecretRef 缺失、RBAC 拒绝或 Secret projection 不完整。
- `provider-auth-failed`:上游 provider 鉴权失败。
- `provider-unavailable`:上游 provider 返回 HTTP 5xx/503、`Service Unavailable`、携带 5xx 的 `responseStreamDisconnected` 或明确 temporary/provider unavailable 文案;这是外部 provider availability blocker,不得归为本地 `backend-failed`
- `backend-failed`:backend 进程退出、协议错误或返回 terminal error。
- `runner-lease-conflict`claim/lease 被其他 runner 持有。replacement runner 遇到未过期旧 lease 时先按 stale lease recovery 规则等待并重试;仅在重试窗口耗尽、active lease 明确未 stale 或并发 runner 正常竞争时作为 terminal failureKind。
- `infra-failed`Job 启动、网络、manager API 或文件系统基础设施失败。
- `cancelled`:收到 interrupt/cancel 且已停止执行。
Runner 日志必须实时 flush 到文件或 pod logCLI 启动 runner 时必须返回 logPath 或 job/pod identity。日志、event、trace 和 CLI 输出不得出现 provider credential、`auth.json``config.toml` 内容、DSN password、token 或 URL credential。
## Runner Job 最小状态
HWLAB v0.2 原有 Code Agent 在 cloud-api 进程内执行,失败时依赖本地 trace 定位;AgentRun 将执行迁到 runner Job 后,runner 必须把最小定位事实交回 manager。`runner-jobs` 创建响应和后续查询至少包含:
| 字段 | 规则 |
| --- | --- |
| `attemptId` | 同一 command 的一次执行尝试;重复 idempotency key 和相同 payload 返回同一 attempt。 |
| `jobName` / `namespace` | Kubernetes Job identity;不得要求业务客户端自己构造名称。 |
| `runnerId` | runner register 后的执行者身份。 |
| `podIdentity` / `logPath` | 至少提供一种可定位 runner 日志的 redacted 引用。 |
| `phase` / `exitCode` | 若可用,返回 Job/Pod/container 的最小状态摘要;不可用时必须说明 `waitingFor` 或 failureKind。 |
| `startedAt` / `finishedAt` | 用于区分 pending、running、terminal 和 TTL retention 窗口。 |
这些字段只用于可观测性,不授予调用方直接操作 Kubernetes Job 或读取 Secret 的权限。
## 测试规格
### T1 Runner 启动可见性
阅读 `AGENTS.md`、本文和 [spec-v01-cli.md](spec-v01-cli.md),然后用正式 AgentRun CLI 为一个真实 run 启动 runner。确认 CLI 立即返回 JSON,包含 runId、attemptId、job/process identity、logPath 和后续 poll command;不得等待完整模型 turn。
### T2 Claim 与 lease 冲突
阅读本文和 [spec-v01-agentrun-mgr.md](spec-v01-agentrun-mgr.md),然后对同一个 run 启动两个 runner。确认只有一个 runner claim 成功,失败方输出结构化 failureKind,并且 manager events 中能看到冲突或拒绝原因。再模拟旧 runner pod 删除但 lease 尚未过期的 replacement runner,确认 replacement runner 等待 stale lease、输出 `runner-claim-waiting-for-stale-lease`,接管后输出 `runner-claim-recovered`,并继续使用同一 `SessionRef`/PVC/thread 执行后续 command。
### T3 Backend event round-trip
阅读本文和 [spec-v01-backend-adapter.md](spec-v01-backend-adapter.md),然后用真实 backend 执行一个最短 turn。确认 runner append assistant/output/error/backend_status/terminal_status 中的必要 eventsevent seq 单调,terminal status 可通过 manager 查询。
### T4 Missing Secret failure
阅读本文和 [spec-v01-secret-distribution.md](spec-v01-secret-distribution.md),然后分别用缺失 `codex` SecretRef、缺失 `deepseek` SecretRef 与缺失 `minimax-m3` SecretRef 的 run 启动 runner。确认 runner 不调用 providerrun 失败为 `secret-unavailable` 或等价 failureKind,不 fallback 到另一个 profile,日志和事件不泄露 Secret 值。
### T5 Profile switching
阅读本文和 [spec-v01-backend-codex.md](spec-v01-backend-codex.md),然后按 `codex -> deepseek -> minimax-m3 -> codex` 顺序启动四个真实 runner Job。确认每个 Job 只挂载和复制当前 profile 的 SecretRef`CODEX_HOME` 互相隔离,且前后两个 `codex` run 不受 `deepseek``minimax-m3` run 的 config/model/upstream 影响。
### T6 Same-run runner command loop
阅读本文和 [spec-v01-hwlab-manual-dispatch.md](spec-v01-hwlab-manual-dispatch.md),然后在同一 run 中提交两条 `turn` command,只启动一次 runner Job。确认第一条 completed 后 run 仍为 non-terminalrunner 在 idle timeout 内处理第二条 command`resource-bundle-materialized` 只记录一次,两个 command result 按 commandId 独立返回 reply/terminal。
### T7 Resource prompt/skill 装配
阅读本文和 [spec-v01-runtime-assembly.md](spec-v01-runtime-assembly.md),然后创建一个带 `ResourceBundleRef.kind="gitbundle"``bundles[]``promptRefs` 的真实或自测试 run。确认 runner 从 repo/ref 解析到同一 materialized commit 后装配 prompt、workspace tools 和 `.agents/skills`;新 thread 首轮显示 `initialPromptInjected=true`assistant 能看见 gitbundle skill 摘要;第二轮 resume 显示 `initialPromptInjected=false`,且没有拼接第一轮历史 prompt。旧字段请求必须 schema-invalidrequired prompt 缺失必须 blocked 为 `prompt-unavailable`
## 规格的实现情况
| 规格项 | 状态 | 说明 |
| --- | --- | --- |
| `agentrun-runner` 服务规格 | 已定义 | 本文为 v0.1 runner 权威。 |
| Kubernetes Job runner | 已实现/已通过主闭环 | `runner job` 通过 manager REST 创建 Kubernetes Job,固定使用 `agentrun-v01-runner` ServiceAccount、manager URL、runId/commandId/attemptId、executionPolicy、SecretRef 文件投影、writable Codex runtime home、idle timeout 和有限 TTL;真实 `agentrun-v01` runner Job 已完成 Codex turn。 |
| host process runner | 已实现 | `runner start``src/runner/main.ts` 进入同一套 `runOnce`,可通过 manager register/claim/poll/report 执行自测试,并支持 `--one-shot` 或 idle timeout 控制。 |
| claim/lease/report client | 已实现/已通过 stale lease recovery 复测 | 已拆出 runner manager API client,覆盖 register、claim、lease heartbeat、poll command、ack、append event、command status 和必要 run statusreplacement runner 遇到旧 lease 时会等待 stale lease 并重试 claimlive runtime 通过 manager 写入 Postgres durable store。 |
| cancel observation | 已实现最小闭环 | runner 在 backend 执行期间轮询 run/command cancel,触发 AbortController 中止 Codex stdio backend,并按 `cancelled` 上报 command/run 终态。 |
| SessionRef/ResourceBundleRef 消费 | 已实现最小闭环 | runner 会使用 run 中的 SessionRef threadId 执行 resume,并 materialize `kind="gitbundle"` ResourceBundleRef 到隔离 workspace 后再启动 backendper-session PVC 直接挂载已通过 HWLAB v0.2 原入口恢复复测,`tools/` PATH、`promptRefs``.agents/skills` 装配已实现。 |
| 同 run/runner 多 turn | 已实现最小闭环 | runner 在同一 Job 中 materialize bundle 一次后循环 poll command;普通 turn completed 只终结 commandrun 保持可继续接后续 turn,直到 idle timeout 或 run terminal。 |
| runner redaction | 已实现主路径 | runner/backend event 和 Job 输出使用 redaction;复杂审计仍按 [spec-v01-validation.md](spec-v01-validation.md) 的人工验收抽查。 |
| `deepseek` profile runner selection | 已实现/已通过主闭环 | Runner Job 和 host runner 已按 run `backendProfile` 选择 matching SecretRef、projection、`CODEX_HOME` 和 backend metadata;真实 Kubernetes Job 已完成 `codex -> deepseek -> codex` 切换联调。 |
| `minimax-m3` profile runner selection | 已实现/已通过 HWLAB v0.2 原入口复测 | Runner Job 和 host runner 已按 run `backendProfile=minimax-m3` 选择 matching SecretRef、projection、`CODEX_HOME` 和 backend metadata;真实 Kubernetes Job 已通过 HWLAB 显式 session CLI 原入口复测。 |