Docs: webhook receiver (runbook, design §10, deploy-lxc §8), PAD-7 rollout record
Runbook: service, endpoint, signature, logs, secret rotation, disabling, heartbeat skipped reasons, new failure rows. Design: webhook trigger design, notify hook, Plane SSRF guard (WEBHOOK_ALLOWED_IPS). Deploy: install steps. Record: synthetic-delivery acceptance and measured latencies.
This commit is contained in:
+32
-2
@@ -52,12 +52,32 @@ cli/agentd smoke GWENT win10 --labels model:sol --watch # --labels 给冒烟
|
||||
```
|
||||
CLI 以 `--as <terminal>` / `agentd.cli_terminal` / 第一个有 token 的终端的身份操作。
|
||||
|
||||
## Webhook(PAD-7:Plane 事件即时触发 tick)
|
||||
cron 每 2 分钟一次的 tick 保留作兜底;另有常驻接收端,Plane 一有工作项/评论变化就立刻触发一次 tick。
|
||||
- **服务**:LXC `plane-agentd-webhook.service`(单元文件在仓库 `systemd/`,`Restart=always`)= `agent-access.py … exec --secret-env PLANE_WEBHOOK_SECRET=PLANE_WEBHOOK_SECRET --timeout 315360000 -- python3 /opt/plane-agentd/cli/agentd webhook-serve --bind 10.77.10.249 --port 8790`。只听内网地址;密钥只在进程环境里,agentd 起的子进程(tick 脚本)环境里去掉了它。
|
||||
- **端点**:`POST http://10.77.10.249:8790/plane`;`GET /healthz` → `{"ok": true, "pending", "queued", "running", "retrying_since", "ticks", "last": {rc, seconds, deliveries, at}}`。
|
||||
- **Plane 侧**:工作区级 webhook(kappiee 工作区设置 → Webhooks,或 `tools/plane-webhook.py`),订阅"工作项"(`issue`)和"评论"(`issue_comment`)。Plane 会把所有项目的事件都发来,接收端按 `data.project` 只认配置里的项目。
|
||||
- **签名**:头 `X-Plane-Signature` = 用 webhook 密钥对请求体做 HMAC-SHA256 的十六进制摘要(Plane 签的是 `json.dumps(payload)`,发出的正是这串字节;接收端先比原始请求体,再比重新序列化的结果,均为常量时间比较)。缺失/不对 → 401,什么都不触发。其他头:`X-Plane-Event`(issue / issue_comment)、`X-Plane-Delivery`(每次投递的 UUID)。请求体:`{"event","action"(created/updated/deleted),"webhook_id","workspace_id","workspace_slug","data":{…,"project":<uuid>},"activity":{field,new_value,old_value,actor…}}`;删除事件的 `data` 只有 `id`(没有项目 → 202 忽略,由 cron 兜底)。
|
||||
- **触发逻辑**:配置项目的投递 → 200,3 s(`debounce_seconds`)内的投递合并成一次 tick;tick 运行期间又来的投递 → 它结束后再跑一次(正在跑的 tick 可能在变化之前就读过 Plane)。tick 命令就是 cron 脚本(`systemd-run --scope … cron/agentd-tick-lxc.sh`,在独立 scope 里跑,重启接收端不会杀掉正在跑的 tick)。脚本持 `flock -n -E 75`:锁被 cron/手工 tick 占着 → 退出码 75 → 每 15 s 重试,最长 2 min,之后交给下一个 cron tick。tick 本身的输出照旧进 `cron.log`/`agentd.log`。
|
||||
- **日志** `/var/log/plane-agentd/webhook.log`:每次投递一行(`delivery=<X-Plane-Delivery> event=issue/updated project=GWENT item=<uuid> decision=tick|202 ignored|401 bad signature|401 missing signature`),每次 tick 两行(`tick start (n deliveries: …)`、`tick done rc=0 in 12.3s` / `tick busy …, retry in 15s` / `giving up`)。Plane 侧的投递记录:`tools/plane-webhook.py logs <webhook id>`。
|
||||
- **查看**:`systemctl status plane-agentd-webhook`、`journalctl -u plane-agentd-webhook`、`curl -s http://10.77.10.249:8790/healthz`、`tail -f /var/log/plane-agentd/webhook.log`。
|
||||
- **轮换密钥**(在 oregon 上,kappiee 管理员会话,密钥不出进程、直接写进 Vault):
|
||||
```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_KAPPIEE_PASSWORD=PLANE_KAPPIEE_PASSWORD --timeout 90 -- \
|
||||
python3 -I /home/ubuntu/Documents/wjj/project/plane-agentd/tools/plane-webhook.py regenerate <webhook id> # id: … plane-webhook.py list
|
||||
```
|
||||
然后 LXC 上 `systemctl restart plane-agentd-webhook`(接收端只在启动时取一次密钥;轮换到重启之间的投递会 401,cron 兜底)。核对:`agent-access.py … exec --secret-env S=PLANE_WEBHOOK_SECRET -- python3 -c 'import os;print(len(os.environ["S"]))'`。
|
||||
- **停用**:只停接收端 `systemctl disable --now plane-agentd-webhook`(Plane 投递会失败并重试,多次失败后 Plane 自动停用该 webhook 并给管理员发邮件);连 Plane 侧一起停:`plane-webhook.py patch <id> is_active=false`。cron 不受影响。
|
||||
- **通知**:配置里的 `notify: {kind: none}` 是钩子,核心每次改状态都调用 `Agentd.notify()`,目前什么都不做(没有 ntfy 服务)。
|
||||
|
||||
## 文件
|
||||
| 路径 | 内容 |
|
||||
|---|---|
|
||||
| `state/agentd-state.json` | 每项目每工作项:轮次、线程 ID 与所在终端/执行器、线程轮数、各轮标签、已见评论、重试计数/下次重试、在途运行(handle、deadline) |
|
||||
| `<log_dir>/agentd.log`(默认 `state/`) | 每次 tick 的动作与错误 |
|
||||
| `state/heartbeat.json` | 最近一次 tick 的时间、在途运行、启动未确认的运行(`launching`)、本 tick 跳过的不可达终端(`down`)、错误;超过 10 分钟不更新 = agentd 没在跑 |
|
||||
| `state/heartbeat.json` | 最近一次 tick 的时间、在途运行、启动未确认的运行(`launching`)、本 tick 跳过的不可达终端(`down`)、没派出去的 Todo 项及原因(`skipped`:`[{project, item, terminal, reason}]`)、错误;超过 10 分钟不更新 = agentd 没在跑 |
|
||||
| `<log_dir>/webhook.log` | webhook 接收端:每次投递的决定、每次触发的 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` |
|
||||
@@ -76,7 +96,10 @@ Todo + 指派给终端用户 → agentd 选最小编号、终端有空位、串
|
||||
| 现象 | 原因 / 处理 |
|
||||
|---|---|
|
||||
| `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 进程 |
|
||||
| `skip: another tick holds state/agentd.lock` | 上一 tick 还在跑(win10 上传慢);正常(`tick` 此时退出码 75)。持续出现则查是否有卡死的 agentd 进程 |
|
||||
| Todo 项一直不动、心跳看着空闲 | 看 `heartbeat.json` 的 `skipped`:`concurrency n/m (GW-x)` 槽位被谁占着;`serialize_group … held by …`;`busy_process codex (… not started by agentd)` = win10 上有不是 agentd 起的(或上一轮还没退干净的)codex.exe;`relay down`;`no route for assignee …` = 指派的用户不是终端。原因第一次出现时 `agentd.log`/`cron.log` 有一行 `GW-n not dispatched (<终端>): <原因>` |
|
||||
| `webhook.log` 里 `decision=401` | 签名不对:Plane 侧密钥轮换后接收端没重启,或不是 Plane 发的。`systemctl restart plane-agentd-webhook` |
|
||||
| Plane 建 webhook 报 "The webhook URL is not allowed…" | Plane 的 SSRF 防护拒绝内网地址:Plane api/worker 环境需 `WEBHOOK_ALLOWED_IPS=10.77.10.249/32`(design §10) |
|
||||
| `[worker] 第 k 轮失败:类型=crash(临时性)…重试 n/N` | 网络/中继/429/5xx:按 `backoff_minutes` 自动重试,续原线程;超过 `transient_max` 转 Waiting |
|
||||
| `类型=timeout` | 超过 `timeout_hours`(或 `budget:2h` 标签);进程树已结束,转 Waiting。看运行目录日志决定拆分任务或加预算 |
|
||||
| `类型=crash`(非临时)且是续线程 | 自动在新线程重跑一次(附交接摘要);再失败转 Waiting |
|
||||
@@ -100,6 +123,13 @@ Todo + 指派给终端用户 → agentd 选最小编号、终端有空位、串
|
||||
3. oregon 恢复 cron:`crontab ~/.gwent-board/crontab.backup-20261021-pad11`(即 `*/2 * * * * /home/ubuntu/Documents/wjj/project/plane-agentd/cron/agentd-tick.sh`)。`cron/agentd-tick.sh` 用 `config/agentd.oregon.yaml`(`state_dir: ~/.gwent-board/agentd`,日志 `~/.gwent-board/agentd/agentd.log`、`~/.gwent-board/agentd-cron.log`),与 LXC 配置只差路径。
|
||||
4. 看 oregon 的 `~/.gwent-board/agentd/heartbeat.json` 更新。再切回 LXC = 反过来做一遍(docs/deploy-lxc.md §5)。
|
||||
|
||||
## 上线记录(2026-10-21,PAD-7:webhook 接收端)
|
||||
- 13:25 LXC 上 `plane-agentd-webhook` 启用(`6dc7e72`),cron 不变。**Plane 侧 webhook 未建成**:Plane 3.3.3 对 `http://10.77.10.249:8790/plane` 返回 400 "The webhook URL is not allowed. Provide a publicly reachable HTTP or HTTPS URL."(SSRF 防护拒绝内网地址,见 design §10);需要在 Plane 的 api/worker 环境加 `WEBHOOK_ALLOWED_IPS=10.77.10.249/32`,之后按 deploy-lxc §8 第 2–5 步建 webhook。在此之前 Vault 里的 `PLANE_WEBHOOK_SECRET` 是临时随机值(`plane_wh_interim_…`),`plane-webhook.py create` 会用 Plane 生成的密钥覆盖它。
|
||||
- 验收用的是在 LXC 上按 Plane 格式签名的合成投递(`/root/lxc_send.sh good|bad|missing|other`):错签名、缺签名 → 401,不触发;别的项目 → 202;正确签名 → 200,3 s 后 tick 开始。tick 运行中又来一条 → 结束后补跑一次(13:27 两条投递:224 s 的 tick——收尾 GW-15、在 win10 启动 GW-5——之后接着 24 s 的补跑);那段时间 cron 的 tick 撞锁立即退出,cron 本身照常每 2 分钟触发。
|
||||
- 冒烟 GW-17(oregon claude-code):建项 13:43:15.8 → 合成投递 13:43:41(经 oregon→LXC 中继发出,人为多了约 25 s)→ tick 开始 13:43:44 → `[worker] 第 1 轮开始` 13:44:56 → `[review] ok` 13:47:54,PASS 并移到 Done。**投递 → tick 开始 = 3 s;tick 开始 → 开始评论 = 72 s**(这一 tick 先 poll win10 上在途的 GW-5,再经中继启动 oregon;同期 cron 的启动 tick 也要 70–77 s)。真实 Plane 投递约 1 s 到达时,建项 → 开始评论预计约 75 s,而只靠 cron 是平均约 60 s 等待 + 同样的 tick 时长。≤10 s 的目标在"开始评论"这个口径上达不到:瓶颈是中继调用(每次约 10 s,vault + Warpgate SSH)和启动流程,不是触发方式。
|
||||
- GW-16(第一次冒烟):webhook 触发的 tick 里 LXC→中继报 `access-hub: config_private_transport_failed`(LXC 经 tailnet 取 `nas…/identity/agent.json` 15 s 超时;13:38 的 cron tick 也撞上一次),启动失败;下一 cron tick 探测无运行目录 → `GW-16-1-r2` 重新启动,PASS(防重复启动机制按设计工作)。
|
||||
- 重启:tick 运行中 `systemctl restart plane-agentd-webhook`,tick(在 `systemd-run --scope` 里)没被杀、照常结束(heartbeat 13:50:10,errors=[]);接收端约 19 s 后重新监听(取 vault 密钥 + 启动)。
|
||||
|
||||
## 切换记录(2026-10-21,PAD-11:调度器迁到 LXC agentd-01)
|
||||
- 代码:防重复启动(`launching` 标记 + 探测接管/换目录重启)与"中继不通的终端本 tick 跳过"随本次一起上线(4f45d36);LXC 用仓库里的 `config/agentd.yaml`,oregon 回滚用 `config/agentd.oregon.yaml`。
|
||||
- 切换前 LXC 手工 `tick --once`(oregon cron 仍在跑,Plane 上无 Todo 项):空闲、无错误;LXC 经 helper 探测 win10(`codex` 计数)与经中继探测 oregon 运行目录均正常。
|
||||
|
||||
Reference in New Issue
Block a user