PAD-12: per-terminal transport (direct Tailscale SSH or Warpgate, pre-session fallback, pinned host keys, agent key from Vault), one-call win10 launch and one-call collect, remote call timings, agentd ping

This commit is contained in:
jj.wang
2026-10-22 02:59:37 +00:00
parent 83bc0f243c
commit 5192e04088
23 changed files with 1850 additions and 132 deletions
+14 -2
View File
@@ -19,13 +19,15 @@ cd /root/.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 \
--secret-env AGENTD_SSH_KEY=AGENTD_SSH_KEY \
--timeout 900 -- python3 /opt/plane-agentd/cli/agentd tick --once
```
`AGENTD_SSH_KEY` 是 direct-ssh 终端的专用密钥(PAD-12,design §9)。手工运行时不加这个参数不会出错,只是直连终端整次 tick 都走 Warpgate 兜底(慢),`agentd.log` 里会有一行 `direct-ssh unavailable in this process`。但加了参数而 Vault 里没有这个键,agent-access.py 会以 `access-hub: credential_unavailable` 直接失败,agentd 根本不运行(它没有可选密钥);cron 脚本遇到这种情况会去掉这个参数再跑一次,手工运行则要自己去掉。
crontab(LXC root,每 2 分钟):
```
*/2 * * * * /opt/plane-agentd/cron/agentd-tick-lxc.sh
```
`cron/agentd-tick-lxc.sh` = `flock -n /var/lib/plane-agentd/cron.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 /opt/plane-agentd/cli/agentd --config /opt/plane-agentd/config/agentd.yaml tick --once >> /var/log/plane-agentd/cron.log 2>&1`。空闲 tick 不打印任何东西(cron 日志为空是正常的),活着与否看 `heartbeat.json` 的 `at`;`skip: another tick holds …` 表示与手工 tick 撞锁,无害。
`cron/agentd-tick-lxc.sh` = 拿 `/var/lib/plane-agentd/cron.lock`(`flock -n -E 75`,撞锁退出 75)后执行 `timeout 900 python3 -I agent-access.py --config <identity> exec --secret-env PLANE_OREGON_API_TOKEN=… --secret-env PLANE_WINDOW10_API_TOKEN=… --secret-env AGENTD_SSH_KEY=AGENTD_SSH_KEY --timeout 850 -- python3 /opt/plane-agentd/cli/agentd --config /opt/plane-agentd/config/agentd.yaml tick --once`,输出追加到 `/var/log/plane-agentd/cron.log`(stderr 排在 stdout 之后)。如果这次 exec 只报了 `access-hub: credential_unavailable`/`credential_invalid`(退出 1,agentd 没运行),脚本在 cron.log 记一行 `AGENTD_SSH_KEY not available from Vault: tick repeated without it …`,去掉 `AGENTD_SSH_KEY` 再跑一次:直连终端走兜底,派单不中断。空闲 tick 不打印任何东西(cron 日志为空是正常的),活着与否看 `heartbeat.json` 的 `at`;`skip: another tick holds …` 表示与手工 tick 撞锁,无害。
实际配置 `config/agentd.yaml`(已提交,不含密钥):`state_dir: /var/lib/plane-agentd`、`log_dir: /var/log/plane-agentd`,所以下表的 `state/…` 在 LXC 上是 `/var/lib/plane-agentd/…`,`agentd.log` 在 `/var/log/plane-agentd/`。改配置/代码 = 在 oregon 提交并推送,然后 LXC 上 `cd /opt/plane-agentd && git pull`(下一个 tick 生效)。
oregon 上的 `cli/agentd` 只当 CLI 用(new/show/comment/state/assign,`smoke … --watch`);**不要在 oregon 上跑 `tick` 或不带 `--watch` 的 `smoke`**(那是第二个调度器)。
暂停一个终端(不再派新运行、也不探测,在途运行照常收尾):把它的 `concurrency` 设为 0。
@@ -48,6 +50,8 @@ cli/agentd state GWENT 12 Todo # Plane 状态名或配置键(todo/review
cli/agentd assign GWENT 12 window10
cli/agentd smoke GWENT oregon --watch # 建一个要求回复 "[review] ok" 的工作项,等排程中的调度器跑完,PASS 则移到 Done
cli/agentd smoke GWENT win10 --labels model:sol --watch # --labels 给冒烟项加标签(路由到模型/档位)
cli/agentd ping win10 [--transport direct-ssh|warpgate-ssh] [-n 3] # 在终端上跑一条空命令并计时,每次调用一行
# (direct-ssh 需要 --secret-env AGENTD_SSH_KEY=AGENTD_SSH_KEY,不需要 Plane token)
# 不带 --watch = 本机自己 tick(只在调度器所在机器上、且它的 cron 停着时用)
```
CLI 以 `--as <terminal>` / `agentd.cli_terminal` / 第一个有 token 的终端的身份操作。
@@ -78,6 +82,8 @@ cron 每 2 分钟一次的 tick 保留作兜底;另有常驻接收端,Plane
| `<log_dir>/agentd.log`(默认 `state/`) | 每次 tick 的动作与错误 |
| `state/heartbeat.json` | 最近一次 tick 的时间、在途运行、启动未确认的运行(`launching`)、本 tick 跳过的不可达终端(`down`)、没派出去的 Todo 项及原因(`skipped`:`[{project, item, terminal, reason}]`)、错误;超过 10 分钟不更新 = agentd 没在跑 |
| `<log_dir>/webhook.log` | webhook 接收端:每次投递的决定、每次触发的 tick(见上节) |
| `<log_dir>/remote.log` | 每次远程调用一行:`term=win10 via=direct\|warpgate\|fallback op=start\|fetch\|read\|busy\|probe\|kill\|forget… ms=… rc=…`,失败时末尾带 `unreached`/`hostkey`/`unknown`(PAD-12)。不回显到 cron.log;没有轮转,需要时手工截断 |
| `agentd.ssh_dir`(LXC `/run/plane-agentd/ssh`) | direct-ssh 的 ControlMaster 套接字(`<host_key 指纹 8 位>-<%C 哈希>`,空闲 60 s 后自动退出;手工关闭:`for s in /run/plane-agentd/ssh/*-*; do [ -S "$s" ] && ssh -O exit -o ControlPath="$s" x; done`)和由钉住公钥生成的 `known_hosts.<终端>`;root 0700,agentd 自己建。ssh-agent 在 `/tmp/agentd-agent-*`(每个 tick 进程一个,退出时删除) |
| `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` |
@@ -111,7 +117,13 @@ Todo + 指派给终端用户 → agentd 选最小编号、终端有空位、串
| `run GW-n-k: nothing at …; relaunching in GW-n-k-r2` | 启动没有落地:同一 run id、新目录 `<run_id>-r2`(再失败 -r3…)重新启动,旧目录不清空。运行目录里已有 pid 文件时 transport 拒绝清空(`HASPID`),下一 tick 探测后接管 |
| heartbeat `launching` 一直不消失 | 终端探测不通(中继坏),标记保留并占着该终端的槽位;中继恢复后自动接管或重启。人工处理:停 cron,在终端上看 `<run_root>/<run_id>/`,再从状态文件删掉该项的 `launching` |
| `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 次 |
| 远程终端每步都慢 | `warpgate-ssh`(或 `via=fallback`)每次调用约 9–14 s(取 Vault 凭据 + 网关握手 + 密码认证 + 二跳)。`direct-ssh` 首次握手 win10 约 0.5 s、oregon 约 2 s,之后 60 s 内复用连接。看 `remote.log` 的 `ms=`。一轮的调用次数:启动 1 次(Warpgate 上 prompt 超过约 45 KB 时多几次分块上传;win10 经 Warpgate 约 8–10 次中继调用),每 tick poll 1 次,收尾 1 次(win10 另加删计划任务 1 次) |
| `SECURITY <终端>: … host key verification FAILED …` | 终端出示的主机公钥和配置里钉住的不一致:可能是重装、sshd 重新生成了主机密钥,也可能是中间人。**不会回退到 Warpgate**,该终端本 tick 标为 down(`heartbeat.down`),每个 tick 都会再报一次。先经第二通道核对(Warpgate:`agent-access.py … run --target oregon -- cat /etc/ssh/ssh_host_ed25519_key.pub`,win10 `win10-ps.sh "Get-Content C:\ProgramData\ssh\ssh_host_ed25519_key.pub"`),确认是合法变更后再更新 `terminals.<t>.ssh.host_key`(deploy-lxc §9 第 4 步)。新的 pin 会换一个 ControlMaster 套接字名(`<pin 指纹>-%C`),不会复用按旧 pin 校验过的连接。终端根本不提供 ssh-ed25519 主机密钥(`no matching host key type`)也按这一行处理。等核对期间要继续派单:把 `transport` 改回 `warpgate-ssh` |
| `<终端>: direct-ssh failed before the command ran (…); using the warpgate-ssh fallback for the rest of this tick` | 直连在命令运行前就失败了(tailscale 断、sshd 停、authorized_keys 缺或 ACL 不对、密钥被拒)。这个操作已在 Warpgate 上重做,本 tick 余下的也走 Warpgate,下个 tick 再试直连。`remote.log` 里会出现 `via=fallback`。查原因:`cli/agentd ping <终端> --transport direct-ssh -n 1`,括号里是 ssh 的原话(如 `Permission denied (publickey)`、`connect to host … Connection timed out`) |
| `direct-ssh unavailable in this process: AGENTD_SSH_KEY is not set …` / `ssh-add failed …` | 进程没拿到密钥(手工运行没加 `--secret-env AGENTD_SSH_KEY=AGENTD_SSH_KEY`,或 cron 脚本因为 Vault 取不到键而去掉了它),或 Vault 里的值不是无口令的 OpenSSH 私钥。直连终端整次都走兜底;没配 `fallback` 的终端本 tick down |
| cron.log:`AGENTD_SSH_KEY not available from Vault: tick repeated without it …` | Vault 键 `AGENTD_SSH_KEY` 被删、改名或不可读,agent-access.py 拒绝了整个 exec。tick 已不带密钥重跑,直连终端每个 tick 都走 Warpgate 兜底(慢,但不停派单)。修复:按 deploy-lxc §9 第 1 步恢复这个键;`agent-access.py … list` 能看到键名是否还在 |
| 手工 `exec … --secret-env AGENTD_SSH_KEY=AGENTD_SSH_KEY` 只打印 `access-hub: credential_unavailable` | 同上,Vault 里没有这个键(也可能是某个 Plane token 键)。手工运行时去掉 `AGENTD_SSH_KEY` 那个参数即可 |
| `ssh administrator@… timeout after 150s` / `ssh … exit 255: …`(无 fallback 行) | 直连的命令**可能已经运行**(超时、会话中途断开、认不出的 255),按规则不换路径重试:终端本 tick down;启动的话保留 `launching` 标记,下一 tick 探测后接管或在 `-r2` 重启。win10 上每次都超时:多半是 stdin 没到 PowerShell(sshd 默认 shell 被改成了 PowerShell),见 deploy-lxc §9 第 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` |