Files
plane-agentd/docs/runbook.md
T
jj.wang dbe305103c Runbook: remote oregon (P1-6) cutover, relay failure rows, tier defaults
Smoke through the Warpgate relay on oregon: round 1 and a resumed round 2 pass;
the Linux kill path was verified directly through the relay. The cancel smoke via
Plane is pending (Plane returned 502 from 08:14).
2026-10-21 09:36:31 +00:00

105 lines
14 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.
# 运行手册
## 安装
```bash
git clone https://git.kappiee.com/kappiee/plane-agentd.git && cd plane-agentd
python3 -m pip install --user pyyaml # 唯一依赖;有 jsonschema 时用它校验,否则用内置子集校验器
cp config/agentd.example.yaml config/agentd.local.yaml # git-ignored;或用 AGENTD_CONFIG 指向别处
cli/agentd check-config # 校验 + 列出各终端 token 环境变量是否已注入 + cron 所需 --secret-env 参数
python3 -m unittest # 离线单测
```
调度器迁到不装 agent CLI 的 LXC:见 `docs/deploy-lxc.md`。
配置里只写 Vault 键名(`token_key`)。agentd 读同名环境变量,token 只由 `agent-access.py exec --secret-env KEY=KEY` 注入进程内存;不要 export 到 shell、不要写文件。agentd 起的所有子进程(本机 runner/执行器、win10 中继脚本)拿到的环境都去掉了全部 `token_key` 与 `webhook_secret_key` 变量。
## 运行
单次 tick(cron 推荐):
```bash
cd ~/.local/share/server-access-client && python3 -I agent-access.py \
--config https://nas.tail9531b.ts.net:8443/identity/agent.json exec \
--secret-env PLANE_OREGON_API_TOKEN=PLANE_OREGON_API_TOKEN \
--secret-env PLANE_WINDOW10_API_TOKEN=PLANE_WINDOW10_API_TOKEN \
--timeout 900 -- /home/ubuntu/Documents/wjj/project/plane-agentd/cli/agentd tick --once
```
crontab(oregon 实际在用,每 2 分钟):
```
*/2 * * * * /home/ubuntu/Documents/wjj/project/plane-agentd/cron/agentd-tick.sh
```
`cron/agentd-tick.sh` = `flock -n ~/.gwent-board/agentd.lock timeout 900 python3 -I agent-access.py --config <identity> exec --secret-env PLANE_OREGON_API_TOKEN=… --secret-env PLANE_WINDOW10_API_TOKEN=… --timeout 850 -- python3 cli/agentd tick --once >> ~/.gwent-board/agentd-cron.log 2>&1`。空闲 tick 不打印任何东西(cron 日志为空是正常的),活着与否看 `heartbeat.json` 的 `at`;`skip: another tick holds …` 表示与手工 tick/smoke 撞锁,无害。
实际配置 `config/agentd.yaml`(已提交,不含密钥):`state_dir: ~/.gwent-board/agentd`,所以下表的 `state/…` 在 oregon 上是 `~/.gwent-board/agentd/…`。
暂停一个终端(不再派新运行、也不探测,在途运行照常收尾):把它的 `concurrency` 设为 0。
常驻替代:`... exec ... -- cli/agentd tick --loop --interval 120`(vault 会话超时要够长,cron 更稳)。
执行器的二进制一律用终端 `bins` 写绝对路径(如 `claude: /home/ubuntu/.local/bin/claude`):cron 和 SSH 非登录 shell 的 PATH 都只有系统目录。
### 执行器二进制的解析(`bins` 或 PATH)
命令模板的首词(`codex`、`claude`…)若在该终端的 `bins` 里,替换为那里的绝对路径;否则**原样交给终端的 shell 按 PATH 查找**。没有任何其他探测或回退:
- oregon(2026-10-21 起 `warpgate-ssh`,os linux):agentd 经中继 `agent-access.py run --target oregon -- bash -c …` 在 oregon 上 `setsid nohup bash run.sh`,runner 的环境是 sshd 给 ubuntu 用户的非登录环境(PATH = 系统默认,没有 `~/.local/bin`),所以 `claude` 必须写进 `bins`。`transport: local`(runner 继承 agentd 进程环境,cron 下 PATH 只有 `/usr/bin:/bin`)仍可用于调度器与执行器同机的部署和测试。**示例配置的 oregon 列了 `codex` 执行器但没有 `bins.codex`,即假定 `codex` 在 PATH 上——目前 oregon 没装 codex**(`which codex` 为空),带 `exec:codex` 的 oregon 工作项会以 exit 127(`command not found`,类型=crash,非临时性)转 Waiting。要用就装 codex 并加 `bins: {codex: <绝对路径>}`,否则从 oregon 的 `executors` 去掉 codex。
- win10(warpgate-ssh):runner 是计划任务里的 `powershell.exe -File run.ps1`,以创建任务的用户(Administrator,交互登录)运行,PATH 取该用户的环境。示例配置用 `bins.codex` 写死 `codex.exe` 绝对路径;去掉它就等于假定 Administrator 的 PATH 里有 `codex`。
- `check-config` 不检查二进制是否存在;首次接入一个终端用 `agentd smoke <PROJECT> <terminal>` 验证。
人工操作(同样在 `exec --secret-env` 下):
```
cli/agentd new GWENT "T-010 标题" --file brief.md --assign window10 --labels model:astra,needs-game --module M1 --state Todo
cli/agentd show GWENT GW-12 3 # 状态/指派/标签 + 最近 3 条评论
cli/agentd comment GWENT 12 reply.md # 或 - 读 stdin
cli/agentd state GWENT 12 Todo # Plane 状态名或配置键(todo/review…)
cli/agentd assign GWENT 12 window10
cli/agentd smoke GWENT oregon # 建一个要求回复 "[review] ok" 的工作项,tick 到结束,PASS 则移到 Done
cli/agentd smoke GWENT win10 --labels model:sol # --labels 给冒烟项加标签(路由到模型/档位)
```
CLI 以 `--as <terminal>` / `agentd.cli_terminal` / 第一个有 token 的终端的身份操作。
## 文件
| 路径 | 内容 |
|---|---|
| `state/agentd-state.json` | 每项目每工作项:轮次、线程 ID 与所在终端/执行器、线程轮数、各轮标签、已见评论、重试计数/下次重试、在途运行(handle、deadline) |
| `state/agentd.log` | 每次 tick 的动作与错误 |
| `state/heartbeat.json` | 最近一次 tick 的时间、在途运行、错误;超过 10 分钟不更新 = agentd 没在跑 |
| `state/agentd.lock` | tick 互斥锁 |
| `state/runs/<run_id>/` | `transport: local` 终端的运行目录:`prompt.md run.sh pid.txt started.txt exit.txt stderr.txt` + 执行器输出 |
| oregon `/home/ubuntu/.gwent-board/agentd-runs/<run_id>/` | 远程 Linux 终端(`run_root`):同上,另有 `launch.pid`(启动时的 `$!`,与 `pid.txt` 相同);claude 输出 `out.json` |
| `<repo>\runtime\agentd\<run_id>\`(win10) | 同上,`run.ps1`;codex 输出 `events.jsonl last.md` |
## 工作项生命周期
Todo + 指派给终端用户 → agentd 选最小编号、终端有空位、串行组空闲的项 → In Progress,评论 `[worker] 第 k 轮开始…` → 执行器结束后回帖 `[<executor>] 第 k 轮结束(exit, run, 终端, 日志)` + 最后消息:
- `[review]` → Review,指派 `reviewer`;`[question]`/`[blocked]` → Waiting,指派 `answerer`(默认 reviewer);`[progress]` → Todo,留在原终端,下一轮续线程;首行无标签 → 按 review 处理并注明。
- 回到 Todo 的项总会被重新拾起;续线程时只发送上次之后的新评论(按评论 ID 判断,`[worker]` 评论永远跳过)。
- 线程重建:换终端/执行器、续线程失败、线程轮数 ≥ `max_rounds_per_thread` → 新线程,首轮输入附交接摘要(各轮标签 + 上一轮最后消息),开始评论里注明原因与"附交接摘要"。临时性错误的自动重试算同一轮,不计入线程轮数(run id 照常递增)。
- 连续 `max_progress_rounds`(项目配置,默认 12)轮 `[progress]` → 不再续跑,回帖说明并转 Waiting 指派 answerer;人工移回 Todo 后重新计数。
- 运行中工作项被移出 In Progress(任何状态)→ 下一 tick 结束该运行(kill),回帖 `[worker] 运行已取消:工作项状态被改为 <状态>`,释放终端槽位与串行组,保留线程 ID(移回 Todo 时续原线程)。kill 失败则保留槽位,下一 tick 再试。
- **未实现(TODO)**:Plane 的 "blocked by" 关系不参与调度——被阻塞的项只要在 Todo 且指派给终端就会被拾起。需要先后顺序时,先只把前一项放进 Todo。
## 常见故障
| 现象 | 原因 / 处理 |
|---|---|
| `no Plane token in the environment` / tick 报 `no Plane token` | 没在 `agent-access.py exec --secret-env …` 下运行,或键名不对;`check-config` 看 MISSING |
| `skip: another tick holds state/agentd.lock` | 上一 tick 还在跑(win10 上传慢);正常。持续出现则查是否有卡死的 agentd 进程 |
| `[worker] 第 k 轮失败:类型=crash(临时性)…重试 n/N` | 网络/中继/429/5xx:按 `backoff_minutes` 自动重试,续原线程;超过 `transient_max` 转 Waiting |
| `类型=timeout` | 超过 `timeout_hours`(或 `budget:2h` 标签);进程树已结束,转 Waiting。看运行目录日志决定拆分任务或加预算 |
| `类型=crash`(非临时)且是续线程 | 自动在新线程重跑一次(附交接摘要);再失败转 Waiting |
| `类型=env` | 匹配 `retry.env_patterns`(焦点丢失/弹窗/断网等),转 Waiting,同串行组暂停到该项离开 Waiting |
| `win10 unreachable; skipping` / `busy (N codex running…)` | 中继不通,或 win10 上有不是 agentd 起的 codex(`busy_processes`);等它结束 |
| `schtasks launch … failed` / `upload … failed` | 中继或 Windows 计划任务问题;手工 `win10-ps.sh "schtasks /Query /TN agentd-<run>"` 查看 |
| `relay oregon exit 255: …` / `relay timeout` | 中继(Vault、Warpgate、目标 sshd)不通;启动失败时项留在 Todo,下一 tick 重新启动(同一 run id、目录清空重来),poll 失败下一 tick 再试。手工:`agent-access.py --config … run --target oregon --timeout 30 -- bash -c 'echo ok'` |
| `upload of … size mismatch` / `staging … size mismatch` | 远程写文件不完整(磁盘满、`run_root` 不可写);看远程 `run_root` |
| 远程 Linux 终端每步都慢 | 每次中继调用约 10–14 s(取 Vault 凭据 + SSH 握手)。一轮:启动 1 次(prompt 超过约 45 KB 时多几次分块上传)、每 tick poll 1 次、收尾 claude 约 3 次 |
| 工作项在运行中被人改了状态 | 运行还没结束:下一 tick 取消(kill + `[worker] 运行已取消…` 评论),不改状态。已经结束(`exit.txt` 已写):结果照常回帖,但不再改状态/重试 |
| Plane 回帖失败 | 在途记录保留,下一 tick 重新 collect 并回帖(可能重复一条评论) |
| 想手工终止一轮 | 把工作项移出 In Progress(如 Waiting)即可,下一 tick agentd 自己 kill 并释放槽位。中继不通、kill 一直失败时:在终端上手工结束(Linux 终端 `kill -TERM -- -$(cat pid.txt); pkill -TERM -s $(cat pid.txt)`,win10 `taskkill /T /F /PID <pid.txt>`),停 cron 后从 `state/agentd-state.json` 删掉该项的 `inflight` |
| `已连续 N 轮 [progress]` | 达到 `max_progress_rounds`:看最近几轮是否在原地打转;拆分任务或补充说明后移回 Todo |
## 切换记录(2026-10-21,P1-5)
- 旧 worker `gwent-design/tools/board-worker-plane.py`(cron `board-worker-plane-cron.sh`)已从 crontab 移除;脚本与 `~/.gwent-board/plane-state.json`、`plane-worker.log` 保留作历史,不要再启用(两者会抢同一批 window10 的 Todo 项)。
- 切换时旧 worker 无在途运行(GW-6 的 board-6-2 已于 06:30 以 `[blocked]` 结束,项在 Waiting)。GW-6 的 codex 线程 `01a15ce3…` 已迁入 agentd 状态(`runs=2`、`thread_on=[win10, codex]`、旧评论标为已见),移回 Todo 并指派 window10 时 agentd 续这个线程,run id 从 `GW-6-3` 起。
- 运行目录变了:旧 worker 用 win10 `runtime\board\board-<n>-<k>`,agentd 用 `runtime\agentd\GW-<n>-<k>`。
- 冒烟:oregon(GW-7,claude-code/sonnet,约 30 s)与 win10(GW-8,codex/gpt-6.1-sol/fast,约 3.5 min)均 PASS。win10 上验证过:`codex exec resume` 也会输出 `thread.started`(与原线程同 ID);续线程时 `-c sandbox_mode="workspace-write"` 生效,能在仓库内写文件;把运行中的项移出 In Progress 后,`taskkill /T /F /PID <pid.txt>` 结束了整棵树(runner powershell → codex.exe → code-mode-host / command-runner → powershell),`Get-Process codex` 归零,计划任务被删除。
- 差异(P1-5 时):旧 worker 对没有 model 标签的项用 sol + `service_tier="fast"`;agentd 当时只在有 `model:sol` 标签时加 fast。P1-6 起 codex 执行器有 `tier_defaults: {gpt-6.1-sol: fast}`,无标签项也是 sol + fast;`tier:<x>` 标签改档位,`tier:none` 去掉档位参数。
- win10 上 codex 的 stderr 常见 websocket 连接被拒(`os error 10061`,`wss://chatgpt.com/...`)后回退成功;只有运行失败时它才出现在失败说明的尾部,不影响分类(不匹配临时性模式)。
## 切换记录(2026-10-21,P1-6:oregon 改为远程启动)
- `config/agentd.yaml` 的 oregon 从 `transport: local` 改为 `warpgate-ssh`(`target: oregon, os: linux, launch: nohup, run_root: /home/ubuntu/.gwent-board/agentd-runs`)。agentd 仍在 oregon 上跑,但启动执行器走 Warpgate 回到本机,与将来 LXC 上的路径完全相同。切换时 oregon 无在途运行;GW-7/GW-10 的 claude 线程(`thread_on=[oregon, claude-code]`)不受影响——会话存在 oregon 的 `~/.claude`,与 transport 无关。
- win10 不变(helper 脚本 + schtasks),只是中继子进程改由共用的 `call()` 启动;切换后 GW-9 的 poll/collect 已经走新代码并正常结束。
- 冒烟(经 Warpgate 远程启动):GW-10 第 1 轮 claude-code/sonnet PASS(`[review] ok`,启动 08:08:54 → 结束 08:10:17,session `fa3be0f7…`);评论"请回复 `[review] ok 2`"并移回 Todo 后第 2 轮续同一 session(`out.json` 的 `session_id` 不变),回 `[review] ok 2` 转 Review。启动只用 1 次中继调用(约 12 s);72 KB 的 prompt 走分块上传(约 75 s)。
- kill:直接经中继对 oregon 起 `sleep 600 & sleep 500` 的运行再 kill,两个 sleep 都被结束(进程组 + 会话),`pid.txt` 与 `launch.pid` 相同(setsid 没有再 fork)。经 Plane 的取消冒烟(运行中移到 Waiting)因 Plane 当时 502(08:14 起)未做,待补。