Docs: LXC deployment as done (agentd-01), rollback to oregon, double-start guard, PAD-11 cutover record
This commit is contained in:
@@ -7,6 +7,6 @@ Plane 看板驱动的通用 agent 调度服务:在 Plane 建工作项并指派
|
|||||||
- 凭据全部经 Agent Vault 注入内存,仓库和配置里只有键名。
|
- 凭据全部经 Agent Vault 注入内存,仓库和配置里只有键名。
|
||||||
|
|
||||||
## 使用
|
## 使用
|
||||||
- 契约与扩展:`docs/executor-contract.md`;安装、cron、日志、故障:`docs/runbook.md`;迁到无 agent CLI 的 LXC:`docs/deploy-lxc.md`
|
- 契约与扩展:`docs/executor-contract.md`;安装、cron、日志、故障:`docs/runbook.md`;LXC 部署(调度器 2026-10-21 起跑在 `agentd-01`):`docs/deploy-lxc.md`
|
||||||
- 入口:`cli/agentd tick|new|show|comment|state|assign|smoke|check-config`(在 `agent-access.py exec --secret-env …` 下运行)
|
- 入口:`cli/agentd tick|new|show|comment|state|assign|smoke|check-config`(在 `agent-access.py exec --secret-env …` 下运行)
|
||||||
- 测试:`python3 -m unittest`(离线,不访问 Plane/win10)
|
- 测试:`python3 -m unittest`(离线,不访问 Plane/win10)
|
||||||
|
|||||||
+57
-52
@@ -1,78 +1,83 @@
|
|||||||
# 在精简 LXC 上部署 agentd(design §9 阶段 3)
|
# 在精简 LXC 上部署 agentd(design §9 阶段 3)
|
||||||
|
|
||||||
目标:调度器跑在一台**不装任何 agent CLI** 的 Debian 12 LXC(`agentd-01`)上;所有终端(win10、oregon、mac…)都经 Warpgate SSH 远程启动(`transport: warpgate-ssh`)。LXC 只做调度、回帖、通知。
|
调度器跑在一台**不装任何 agent CLI** 的 Debian 12 LXC(`agentd-01`)上;所有终端(win10、oregon、mac…)都经 Warpgate SSH 远程启动(`transport: warpgate-ssh`)。LXC 只做调度、回帖、通知。
|
||||||
|
|
||||||
|
实际部署(2026-10-21,PAD-11):`agentd-01` = PVE CT 600,Debian 12,tailnet `100.69.78.124`,Warpgate target `agentd-01`,登录 **root**(Vault skill `ssh-agentd-01`;没有另建部署用户,agentd 以 root 运行)。下面是实际做过的步骤;从 oregon 发命令都经 `agent-access.py … run --target agentd-01 --timeout N -- bash -c '…'`。
|
||||||
|
|
||||||
## 1. 系统包
|
## 1. 系统包
|
||||||
|
LXC 交付时已有 `python3`(3.11)、`python3-yaml`、`openssh-client`、`git`、`cron`(active);win10 helper 需要的 `iconv gzip base64 sha256sum` 也都在。从零装:
|
||||||
```bash
|
```bash
|
||||||
apt-get update
|
apt-get update && apt-get install -y python3 python3-yaml openssh-client git cron ca-certificates
|
||||||
apt-get install -y python3 python3-yaml openssh-client git cron ca-certificates curl
|
|
||||||
# win10 的 helper 脚本另需(Debian 12 默认已有):bash coreutils(base64/sha256sum) gzip libc-bin(iconv)
|
|
||||||
adduser --disabled-password --gecos '' agentd # 普通用户运行 agentd,不用 root
|
|
||||||
```
|
```
|
||||||
Python ≥ 3.10(Debian 12 是 3.11)。`jsonschema` 可选,没有就用内置子集校验器。
|
`jsonschema` 可选,没有就用内置子集校验器。
|
||||||
|
|
||||||
## 2. 网络
|
## 2. 网络
|
||||||
| 去向 | 用途 |
|
| 去向 | 用途 |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `nas.tail9531b.ts.net:8443`(tailnet) | Agent Vault 身份配置 `identity/agent.json`(含 vault token 与 Warpgate 主机公钥)→ LXC 必须加入 tailnet(`tailscale up`,LXC 需 `/dev/net/tun`) |
|
| `nas.tail9531b.ts.net:8443`(tailnet) | Agent Vault 身份配置 `identity/agent.json`(含 vault token 与 Warpgate 主机公钥)→ LXC 已在 tailnet |
|
||||||
| Vault(身份配置里的地址) | 取 Plane token、Warpgate 密码 |
|
| Vault(身份配置里的地址) | 取 Plane token、Warpgate 密码 |
|
||||||
| Warpgate SSH `115.28.210.101:2222` | 所有终端的中继 |
|
| Warpgate SSH `115.28.210.101:2222` | 所有终端的中继(LXC → win10 / oregon) |
|
||||||
| `https://plane.kappiee.com` | Plane API |
|
| `https://plane.kappiee.com` | Plane API |
|
||||||
|
| `https://git.kappiee.com` | `git pull`(公开仓库,无需凭据) |
|
||||||
|
|
||||||
## 3. Agent Vault 客户端与身份
|
## 3. Agent Vault 客户端
|
||||||
|
只复制程序文件(客户端没有落盘的 token 缓存;oregon 目录里的 `agent-service-check.py` 是运维工具,不需要):
|
||||||
```bash
|
```bash
|
||||||
sudo -u agentd mkdir -p ~agentd/.local/share/server-access-client
|
# 在 oregon 上:两个文件 base64 后经中继写到 LXC(一次 run 调用,sha256 与 oregon 一致)
|
||||||
# 从 oregon 复制(权限 700,只给 agentd 用户):
|
/root/.local/share/server-access-client/agent-access.py # 700 root
|
||||||
scp oregon:~/.local/share/server-access-client/{agent-access.py,agent-access-askpass.py} \
|
/root/.local/share/server-access-client/agent-access-askpass.py # 700 root,必须与 agent-access.py 同目录且可执行
|
||||||
agentd-01:~agentd/.local/share/server-access-client/
|
# 在 LXC 上验证:
|
||||||
chmod 700 ~agentd/.local/share/server-access-client/*
|
cd /root/.local/share/server-access-client
|
||||||
cd ~agentd/.local/share/server-access-client && sudo -u agentd python3 -I agent-access.py \
|
python3 -I agent-access.py --config https://nas.tail9531b.ts.net:8443/identity/agent.json status # "authenticated": true
|
||||||
--config https://nas.tail9531b.ts.net:8443/identity/agent.json list # authenticated: true,能看到 win10 / oregon 等 target
|
python3 -I agent-access.py --config https://nas.tail9531b.ts.net:8443/identity/agent.json list # 含 win10、oregon、agentd-01
|
||||||
```
|
```
|
||||||
- 身份是 tailnet 上的 URL(`--config https://…/identity/agent.json`),不落盘;也可以换成本地私有 JSON 文件(`chmod 600`),把 `vault.config` 指过去。
|
- 身份是 tailnet 上的 URL(`--config https://…/identity/agent.json`),不落盘。
|
||||||
- **Warpgate known_hosts 不用配**:客户端每次用身份配置里的 `warpgate.host_key` 生成临时 known_hosts(`StrictHostKeyChecking=yes`),不读 `~/.ssh/known_hosts`。只要求有 `ssh` 可执行文件。
|
- **Warpgate known_hosts 不用配**:客户端每次用身份配置里的 `warpgate.host_key` 生成临时 known_hosts。LXC → oregon 一次中继约 9 s。
|
||||||
- `askpass` 必须与 `agent-access.py` 同目录且可执行。
|
|
||||||
- Warpgate 侧:该身份(`agent` 用户)要有每个终端 target 的权限(现有:`win10`、`oregon`)。
|
|
||||||
|
|
||||||
## 4. 代码与配置
|
## 4. 代码与配置
|
||||||
```bash
|
```bash
|
||||||
sudo -u agentd git clone https://git.kappiee.com/kappiee/plane-agentd.git ~agentd/plane-agentd
|
cd /opt/plane-agentd && git pull # 仓库已 clone 在 /opt/plane-agentd(https://git.kappiee.com/kappiee/plane-agentd.git)
|
||||||
# win10 仍用 helper 脚本(见 design §9 TODO),复制这两个:
|
mkdir -p /var/lib/plane-agentd /var/log/plane-agentd && chmod 700 /var/lib/plane-agentd
|
||||||
sudo -u agentd mkdir -p ~agentd/helpers
|
python3 -m unittest && python3 cli/agentd check-config
|
||||||
scp oregon:/home/ubuntu/Documents/wjj/project/gwent-design/tools/{win10-ps.sh,win10-put.sh} agentd-01:~agentd/helpers/
|
|
||||||
# 看板协作协议(拼进每轮 prompt,由 agentd 在本机读取):
|
|
||||||
scp oregon:/home/ubuntu/Documents/wjj/project/gwent/design/board-protocol.md agentd-01:~agentd/board-protocol.md
|
|
||||||
cp ~agentd/plane-agentd/config/agentd.yaml ~agentd/plane-agentd/config/agentd.local.yaml # git-ignored,优先于 agentd.yaml
|
|
||||||
```
|
```
|
||||||
`config/agentd.local.yaml` 里改:
|
不需要本地配置文件,全部在仓库里(部署可复现):
|
||||||
- `vault.client: ~/.local/share/server-access-client/agent-access.py`(`~` 在本机展开)
|
- `config/agentd.yaml` = agentd-01 的实时配置:`state_dir: /var/lib/plane-agentd`(状态、心跳、锁),`log_dir: /var/log/plane-agentd`(`agentd.log`),`helpers_dir: tools/win10`,`vault.client: ~/.local/share/server-access-client/agent-access.py`(root 下即 `/root/...`)。终端 win10(warpgate-ssh/windows/schtasks)和 oregon(warpgate-ssh/linux/nohup,`run_root: /home/ubuntu/.gwent-board/agentd-runs`,`bins.claude`);没有 local 终端。
|
||||||
- `agentd.state_dir: ~/state`(JSON 状态、日志、心跳);`agentd.helpers_dir: ~/helpers`
|
- `tools/win10/win10-ps.sh`、`win10-put.sh`:win10 helper(vault 客户端目录取 `AGENT_ACCESS_HOME`,默认 `~/.local/share/server-access-client`;身份取 `AGENT_ACCESS_CONFIG`)。相对 `helpers_dir` 按仓库根解析。
|
||||||
- `projects.GWENT.protocol_file: ~/board-protocol.md`(**它不在终端上读,是 LXC 本机路径**;协议更新时重新复制)
|
- `config/protocols/gwent-board-protocol.md`:看板协议(拼进每轮 prompt,LXC 本机读取)。来源是 gwent 仓库 `design/board-protocol.md`,**协议更新时复制过来提交**,然后 LXC `git pull`。
|
||||||
- 所有终端都是 `transport: warpgate-ssh`:
|
- `config/agentd.oregon.yaml`:oregon 作调度器时的配置(回滚用,见 runbook)。
|
||||||
- win10:`target: win10, os: windows, launch: schtasks`(不变)
|
- 相对路径(`state_dir`/`log_dir`/`helpers_dir`/`protocol_file`)都按仓库根解析;远程终端的 `repo`/`run_root`/`bins` 必须是远程绝对路径。
|
||||||
- oregon:`target: oregon, os: linux, launch: nohup, repo: /home/ubuntu/Documents/wjj/project/gwent, run_root: /home/ubuntu/.gwent-board/agentd-runs, bins: {claude: /home/ubuntu/.local/bin/claude}`
|
|
||||||
- 远程 POSIX 终端的 `repo`、`run_root` 必须是绝对路径(在远程展开,`~` 不行);`bins` 写远程的绝对路径(SSH 非登录 shell 只有系统 PATH)。
|
## 5. 冒烟与切换(实际顺序)
|
||||||
|
1. LXC 上手工跑一次(oregon cron 仍在跑,LXC 未排程;先确认 Plane 上没有 Todo 项可派,否则两个调度器会同时派单):
|
||||||
```bash
|
```bash
|
||||||
cd ~agentd/plane-agentd && python3 -m unittest && python3 cli/agentd check-config
|
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 \
|
||||||
|
--timeout 250 -- python3 /opt/plane-agentd/cli/agentd tick --once # 空闲:无输出,heartbeat errors=[]
|
||||||
```
|
```
|
||||||
|
2. oregon:备份 crontab(`~/.gwent-board/crontab.backup-20261021-pad11`),删掉 `agentd-tick.sh` 那一行;`flock ~/.gwent-board/agentd.lock true` 等正在跑的 tick 结束。
|
||||||
|
3. 状态:`~/.gwent-board/agentd/agentd-state.json`(oregon)→ `/var/lib/plane-agentd/agentd-state.json`(LXC,600;base64 经中继写入,比对 sha256)。在途运行的 handle 是终端上的绝对路径,新调度器照常 poll/collect。
|
||||||
|
4. LXC 上 root 的 crontab(只有这一行):
|
||||||
|
```
|
||||||
|
*/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 /root/.local/share/server-access-client/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`。
|
||||||
|
注意:经中继装 crontab 时别在 `set -e` 下写 `(crontab -l | grep -v …; echo …) | crontab -`——没有旧 crontab 时 grep 返回 1,子 shell 提前退出,装进去的是空 crontab。直接 `printf '%s\n' '<行>' | crontab -`。
|
||||||
|
5. 验证:`/var/lib/plane-agentd/heartbeat.json` 的 `at` 每 2 分钟更新。
|
||||||
|
6. 冒烟经 LXC 调度器:在 oregon 上 `cli/agentd smoke GWENT oregon --watch`(`--watch` = 只建项并等待,不在本机 tick;oregon 上 `cli/agentd` 默认读 `config/agentd.yaml`,CLI 只需要 Plane token)。
|
||||||
|
|
||||||
## 5. cron
|
- token 只经 `exec --secret-env` 进 agentd 进程内存;agentd 起的中继子进程环境里去掉了所有 `token_key`(`Config.scrubbed_env()`)。
|
||||||
`cron/agentd-tick.sh` 里写死了 oregon 的路径;在 LXC 上用同样的形状(单行 crontab 或复制脚本改路径):
|
|
||||||
```
|
|
||||||
*/2 * * * * flock -n $HOME/state/cron.lock timeout 900 python3 -I $HOME/.local/share/server-access-client/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 850 -- python3 $HOME/plane-agentd/cli/agentd tick --once >> $HOME/state/agentd-cron.log 2>&1
|
|
||||||
```
|
|
||||||
- token 只经 `exec --secret-env` 进 agentd 进程内存;agentd 起的中继子进程环境里去掉了所有 `token_key`(`Config.scrubbed_env()`),远程执行器在终端上由 sshd 起,本来就拿不到 LXC 的环境。
|
|
||||||
- `--secret-env` 的完整列表:`cli/agentd check-config` 最后一行。
|
- `--secret-env` 的完整列表:`cli/agentd check-config` 最后一行。
|
||||||
- **切换时先停 oregon 上的 cron**(`crontab -e` 注释掉 `agentd-tick.sh`),再在 LXC 上启用;两个调度器同时跑会重复派单。状态文件 `~/.gwent-board/agentd/agentd-state.json` 从 oregon 复制到 LXC 的 `state_dir`,在途运行(handle 里是终端上的绝对路径)照常被新调度器 poll/collect。
|
- **任何时候只能有一个调度器排程**(oregon 的 crontab 里不能有 `agentd-tick.sh`)。
|
||||||
|
|
||||||
## 6. 日志与心跳
|
## 6. 日志与心跳(LXC)
|
||||||
| 路径(LXC) | 内容 |
|
| 路径 | 内容 |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `~/state/agentd.log` | 每 tick 的动作、错误 |
|
| `/var/log/plane-agentd/agentd.log` | 每 tick 的动作、错误 |
|
||||||
| `~/state/heartbeat.json` | `at` 超过 10 分钟不更新 = 调度器没在跑 |
|
| `/var/log/plane-agentd/cron.log` | cron 输出(空闲时为空;`skip: another tick holds …` 无害) |
|
||||||
| `~/state/agentd-state.json` | 全部工作项状态 |
|
| `/var/lib/plane-agentd/heartbeat.json` | `at` 超过 10 分钟不更新 = 调度器没在跑;`down` = 本 tick 中继不通而跳过的终端;`launching` = 启动未确认的运行 |
|
||||||
| `~/state/agentd-cron.log` | cron 输出(空闲时为空;`skip: another tick holds …` 无害) |
|
| `/var/lib/plane-agentd/agentd-state.json` | 全部工作项状态 |
|
||||||
| 终端上 `<run_root>/<run_id>/` | 每轮的 `prompt.md run.sh/run.ps1 pid.txt launch.pid exit.txt stderr.txt` + 执行器输出 |
|
| `/var/lib/plane-agentd/agentd-state.json.lxc-smoke` | 切换前手工冒烟 tick 产生的空状态(可删) |
|
||||||
|
| 终端上 `<run_root>/<run_id>/` | 每轮的 `prompt.md run.sh/run.ps1 pid.txt launch.pid exit.txt stderr.txt` + 执行器输出;重新启动的运行在 `<run_id>-r2/` |
|
||||||
|
|
||||||
## 7. 加一个终端(例:mac)
|
## 7. 加一个终端(例:mac)
|
||||||
1. Warpgate 加 SSH target(如 `mac`),给 `agent` 身份授权;`agent-access.py … run --target mac --timeout 30 -- bash -c 'echo ok; uname'` 能通。
|
1. Warpgate 加 SSH target(如 `mac`),给 `agent` 身份授权;`agent-access.py … run --target mac --timeout 30 -- bash -c 'echo ok; uname'` 能通。
|
||||||
@@ -93,5 +98,5 @@ cd ~agentd/plane-agentd && python3 -m unittest && python3 cli/agentd check-confi
|
|||||||
executors: [claude-code]
|
executors: [claude-code]
|
||||||
```
|
```
|
||||||
远程 shell 是目标账户的登录 shell(bash/zsh 均可,agentd 发的是 `bash -c '<script>'`);终端需有 `bash base64 nohup pgrep pkill`(Linux 另需 `setsid`,macOS 需 `perl`)。
|
远程 shell 是目标账户的登录 shell(bash/zsh 均可,agentd 发的是 `bash -c '<script>'`);终端需有 `bash base64 nohup pgrep pkill`(Linux 另需 `setsid`,macOS 需 `perl`)。
|
||||||
4. Plane:建用户 `mac`,token 存 Vault 键 `PLANE_MAC_API_TOKEN`;项目 `routing.by_assignee` 加 `mac: mac`,`default_executor` 加 `mac: claude-code`;cron 加 `--secret-env PLANE_MAC_API_TOKEN=PLANE_MAC_API_TOKEN`。
|
4. Plane:建用户 `mac`,token 存 Vault 键 `PLANE_MAC_API_TOKEN`;项目 `routing.by_assignee` 加 `mac: mac`,`default_executor` 加 `mac: claude-code`;`cron/agentd-tick-lxc.sh` 加 `--secret-env PLANE_MAC_API_TOKEN=PLANE_MAC_API_TOKEN`。
|
||||||
5. `cli/agentd check-config`,然后 `cli/agentd smoke GWENT mac`。
|
5. 提交、推送、LXC `git pull`;`cli/agentd check-config`,然后 `cli/agentd smoke GWENT mac --watch`。
|
||||||
|
|||||||
+3
-2
@@ -94,6 +94,7 @@ docs/ design.md executor-contract.md runbook.md
|
|||||||
## 9. 部署位置
|
## 9. 部署位置
|
||||||
- Plane VM(plane-01)只跑 Plane:它是设备化节点(固定版本、每日备份停写路径),不放 agentd。
|
- Plane VM(plane-01)只跑 Plane:它是设备化节点(固定版本、每日备份停写路径),不放 agentd。
|
||||||
- 阶段 1–2:agentd 跑在 oregon(已有 Vault 客户端、Warpgate 中继脚本、Claude Code、现有 worker 状态),cron 拉起;配置页面同进程,只监听 tailnet 地址。
|
- 阶段 1–2:agentd 跑在 oregon(已有 Vault 客户端、Warpgate 中继脚本、Claude Code、现有 worker 状态),cron 拉起;配置页面同进程,只监听 tailnet 地址。
|
||||||
- 阶段 3 起:迁到 PVE 上的精简 LXC `agentd-01`(1 vCPU / 1–2 GB,tailnet + 内网 10.77.10.0/24,与 Plane/Temporal 同网段,webhook 走内网)。**LXC 上不装任何 agent CLI**,只有 Python 3.10+、PyYAML、Agent Vault 客户端、ssh 客户端;所有终端(win10、oregon、mac…)一律远程启动(`transport: warpgate-ssh`),LXC 只负责调度、回帖、通知。对外访问经 Warpgate HTTP target,复用既有登录。
|
- 阶段 3 起(**2026-10-21 PAD-11 已切换**):迁到 PVE 上的精简 LXC `agentd-01`(CT 600,root,仓库 `/opt/plane-agentd`,状态 `/var/lib/plane-agentd`,日志 `/var/log/plane-agentd`;1 vCPU / 1–2 GB,tailnet + 内网 10.77.10.0/24,与 Plane/Temporal 同网段,webhook 走内网)。**LXC 上不装任何 agent CLI**,只有 Python 3.10+、PyYAML、Agent Vault 客户端、ssh 客户端;所有终端(win10、oregon、mac…)一律远程启动(`transport: warpgate-ssh`),LXC 只负责调度、回帖、通知。对外访问经 Warpgate HTTP target,复用既有登录。
|
||||||
- 远程启动的通用实现(P1-6,已实现):Linux/macOS 经 Warpgate SSH(`agent-access.py run --target <终端 target> -- bash -c …`)上传 `prompt.md`/`run.sh` 并一次调用内启动,`setsid nohup` 脱离会话(macOS 用 perl `POSIX::setsid`),轮询 `exit.txt`,从终端上的输出文件取回最后消息/线程 ID,kill 按进程组 + 会话;中继目标名由终端配置给出。oregon 已切到这条路径(调度器暂仍在 oregon,启动经 Warpgate 回到本机)。部署步骤:`docs/deploy-lxc.md`。
|
- 远程启动的通用实现(P1-6,已实现):Linux/macOS 经 Warpgate SSH(`agent-access.py run --target <终端 target> -- bash -c …`)上传 `prompt.md`/`run.sh` 并一次调用内启动,`setsid nohup` 脱离会话(macOS 用 perl `POSIX::setsid`),轮询 `exit.txt`,从终端上的输出文件取回最后消息/线程 ID,kill 按进程组 + 会话;中继目标名由终端配置给出。oregon 已切到这条路径(调度器暂仍在 oregon,启动经 Warpgate 回到本机)。部署步骤:`docs/deploy-lxc.md`。
|
||||||
- Windows 仍用 helper 脚本 `win10-ps.sh` / `win10-put.sh`(写死 `--target win10`)+ 计划任务。**TODO(统一)**:Windows 也改走 `relay()`(argv = `powershell.exe -EncodedCommand …`,文件同样 base64 分块),删掉对 helper 脚本与 `helpers_dir` 的依赖,LXC 上就不必再复制这两个脚本。
|
- 防重复启动(PAD-11):启动调用之前先把计划中的运行(run id、终端、handle、记账字段)作为 `launching` 标记落盘;启动调用失败/超时或 agentd 中途退出时,下一 tick 探测运行目录(`exit.txt`/`launch.pid`/`pid.txt`):有 → 接管;无 → 同一 run id 换新目录 `<run_id>-r2` 重启(不清空旧目录);探测不通 → 保留标记并占住终端槽位。transport 拒绝清空含 pid 文件的运行目录。某终端中继失败时本 tick 余下操作跳过该终端,其他终端照常。
|
||||||
|
- Windows 仍用 helper 脚本 `win10-ps.sh` / `win10-put.sh`(仓库 `tools/win10/`,写死 `--target win10`)+ 计划任务。**TODO(统一)**:Windows 也改走 `relay()`(argv = `powershell.exe -EncodedCommand …`,文件同样 base64 分块),删掉对 helper 脚本与 `helpers_dir` 的依赖,LXC 上就不必再复制这两个脚本。
|
||||||
|
|||||||
@@ -6,13 +6,15 @@ agentd 不常驻:一轮运行比起它的那次 tick 活得久,所以执行
|
|||||||
| 成员 | 说明 |
|
| 成员 | 说明 |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `resume: bool` | 能否续线程。`False` 时每轮都是新线程(有前序轮次时附交接摘要)。 |
|
| `resume: bool` | 能否续线程。`False` 时每轮都是新线程(有前序轮次时附交接摘要)。 |
|
||||||
| `start(run: Run) -> handle` | 起一轮,立即返回;不得阻塞到运行结束。 |
|
| `handle(run) -> handle` | 不接触终端,算出 `start(run)` 将返回的 handle(core 在启动前把它写进 `launching` 标记)。 |
|
||||||
|
| `start(run: Run) -> handle` | 起一轮,立即返回;不得阻塞到运行结束。抛异常不代表一定没启动:core 下一 tick 用 `probe` 判断。 |
|
||||||
|
| `probe(handle) -> [names]` | 运行目录里的启动证据(`exit.txt`/`launch.pid`/`pid.txt` 中存在的);空 = 没启动;终端不可达抛 `TransportError`。 |
|
||||||
| `poll(handle) -> "running" \| "finished"` | 便宜的完成检测。 |
|
| `poll(handle) -> "running" \| "finished"` | 便宜的完成检测。 |
|
||||||
| `collect(handle) -> ExecResult` | 只在 `finished` 后调用,可重复调用(幂等):Plane 回帖失败时下一 tick 会再 collect。 |
|
| `collect(handle) -> ExecResult` | 只在 `finished` 后调用,可重复调用(幂等):Plane 回帖失败时下一 tick 会再 collect。 |
|
||||||
| `kill(handle)` | 超时时结束整个进程树。 |
|
| `kill(handle)` | 超时时结束整个进程树。 |
|
||||||
| `forget(handle)`(可选) | collect 之后清理(如删除计划任务)。 |
|
| `forget(handle)`(可选) | collect 之后清理(如删除计划任务)。 |
|
||||||
|
|
||||||
`Run(run_id, repo, prompt, model, effort, tier, thread_id, script)`:`thread_id` 非空即续该线程。
|
`Run(run_id, repo, prompt, model, effort, tier, thread_id, script, dir_name)`:`thread_id` 非空即续该线程;`dir_name` 是运行目录名/计划任务名(缺省 = `run_id`,重新启动时为 `<run_id>-r2` 等)。
|
||||||
`ExecResult(last_message, exit_code, thread_id, log_paths, tail)`:`last_message` 原样回帖,首行应为 `[review]/[question]/[blocked]/[progress]`;`thread_id` 供下一轮续接;`tail` 是失败说明里的"最后一帧"(stderr 尾部/最后事件)。
|
`ExecResult(last_message, exit_code, thread_id, log_paths, tail)`:`last_message` 原样回帖,首行应为 `[review]/[question]/[blocked]/[progress]`;`thread_id` 供下一轮续接;`tail` 是失败说明里的"最后一帧"(stderr 尾部/最后事件)。
|
||||||
|
|
||||||
执行器**不接触 Plane**:不持有 token、不改工作项。回帖、状态流转、重试、超时、线程重建都由 core 统一处理。transport 启动 runner/中继脚本时传入去掉所有 `token_key`/`webhook_secret_key` 变量的环境(`Config.scrubbed_env()`);新 transport 必须同样处理。
|
执行器**不接触 Plane**:不持有 token、不改工作项。回帖、状态流转、重试、超时、线程重建都由 core 统一处理。transport 启动 runner/中继脚本时传入去掉所有 `token_key`/`webhook_secret_key` 变量的环境(`Config.scrubbed_env()`);新 transport 必须同样处理。
|
||||||
@@ -24,7 +26,7 @@ agentd 不常驻:一轮运行比起它的那次 tick 活得久,所以执行
|
|||||||
- `kill(handle)` 既用于超时,也用于取消(工作项在运行中被移出 In Progress);必须能在运行未结束时调用。取消后 core 会尝试 `collect(handle)` 取线程 ID,所以 `collect` 在未结束的运行上也不能抛出致命错误(抛异常会被忽略)。
|
- `kill(handle)` 既用于超时,也用于取消(工作项在运行中被移出 In Progress);必须能在运行未结束时调用。取消后 core 会尝试 `collect(handle)` 取线程 ID,所以 `collect` 在未结束的运行上也不能抛出致命错误(抛异常会被忽略)。
|
||||||
|
|
||||||
## RunnerExecutor(现有三个适配器的共同形态)
|
## RunnerExecutor(现有三个适配器的共同形态)
|
||||||
1. 在终端上建运行目录 `<run_root>/<run_id>`(清空旧文件),写 `prompt.md`。transport 有 `start_run` 时(远程 POSIX),建目录、写 `prompt.md` 与 runner、启动合并成一次中继调用。
|
1. 在终端上建运行目录 `<run_root>/<dir_name>`(清空旧文件;目录里已有 `pid.txt`/`launch.pid` 时拒绝并报 `HASPID`),写 `prompt.md`。transport 有 `start_run` 时(远程 POSIX),建目录、写 `prompt.md` 与 runner、启动合并成一次中继调用。
|
||||||
2. 用配置里的命令模板生成命令:`run`(新线程)或 `resume`(续线程)。占位符:`{repo} {model} {effort} {tier} {out} {thread} {perm} {script} {run_dir}`;未知占位符原样保留。`{tier}` 展开为 `tier_arg`(如 `-c 'service_tier="fast"'`),无 tier 时为空。tier 来源:`tier:<x>` 标签或 `routing.by_label` 的 `tier`,否则执行器的 `tier_defaults[<model>]`;`tier:none` 表示不加档位参数。模板首词若在终端 `bins` 里,替换为绝对路径(Windows 前加 `&`)。参数请用单引号,bash 和 PowerShell 都认。
|
2. 用配置里的命令模板生成命令:`run`(新线程)或 `resume`(续线程)。占位符:`{repo} {model} {effort} {tier} {out} {thread} {perm} {script} {run_dir}`;未知占位符原样保留。`{tier}` 展开为 `tier_arg`(如 `-c 'service_tier="fast"'`),无 tier 时为空。tier 来源:`tier:<x>` 标签或 `routing.by_label` 的 `tier`,否则执行器的 `tier_defaults[<model>]`;`tier:none` 表示不加档位参数。模板首词若在终端 `bins` 里,替换为绝对路径(Windows 前加 `&`)。参数请用单引号,bash 和 PowerShell 都认。
|
||||||
3. transport 生成 runner 脚本(Linux `run.sh`,Windows `run.ps1`):进入 repo,写 `pid.txt`/`started.txt`,把 `prompt.md` 从 stdin 喂给命令,stdout → `stdout_name`,stderr → `stderr.txt`,结束写 `exit.txt`(`exit=N at …`)。
|
3. transport 生成 runner 脚本(Linux `run.sh`,Windows `run.ps1`):进入 repo,写 `pid.txt`/`started.txt`,把 `prompt.md` 从 stdin 喂给命令,stdout → `stdout_name`,stderr → `stderr.txt`,结束写 `exit.txt`(`exit=N at …`)。
|
||||||
4. transport 分离启动(本机 `setsid -f bash run.sh`;远程 Linux `setsid nohup bash run.sh &`,macOS 用 `perl POSIX::setsid`,`$!` 记入 `launch.pid`;Windows 一次性计划任务 `agentd-<run_id>`)。
|
4. transport 分离启动(本机 `setsid -f bash run.sh`;远程 Linux `setsid nohup bash run.sh &`,macOS 用 `perl POSIX::setsid`,`$!` 记入 `launch.pid`;Windows 一次性计划任务 `agentd-<run_id>`)。
|
||||||
@@ -39,7 +41,7 @@ agentd 不常驻:一轮运行比起它的那次 tick 活得久,所以执行
|
|||||||
配置里的 `thread_from` / `last_message` 目前只是说明性字段,解析逻辑在适配器里。
|
配置里的 `thread_from` / `last_message` 目前只是说明性字段,解析逻辑在适配器里。
|
||||||
|
|
||||||
## Transport 接口(`agentd/transports/`)
|
## Transport 接口(`agentd/transports/`)
|
||||||
`os`、`join`、`quote`、`default_run_root`、`mkdir(path, clean)`、`write_text`、`read_text`(不存在返回 None)、`grep_first(path, needle)`、`tail(path, n)`、`runner(run_dir, cwd, command, stdout_name)`、`launch(task, script, run_dir)`、`kill(task, run_dir)`、`forget(task)`、`process_count(name)`(None = 不可达);可选 `start_run(task, run_dir, files, script_name)`(一次完成建目录 + 写文件 + 启动)。
|
`os`、`join`、`quote`、`default_run_root`、`mkdir(path, clean)`(clean 遇到 pid 文件必须拒绝)、`probe(run_dir, names)`(存在的文件名列表,一次调用)、`write_text`、`read_text`(不存在返回 None)、`grep_first(path, needle)`、`tail(path, n)`、`runner(run_dir, cwd, command, stdout_name)`、`launch(task, script, run_dir)`、`kill(task, run_dir)`、`forget(task)`、`process_count(name)`(None = 不可达);可选 `start_run(task, run_dir, files, script_name)`(一次完成建目录 + 写文件 + 启动)。
|
||||||
- `local`:Linux 本机文件 + `setsid -f`;kill = 对 `pid.txt` 的进程组发 SIGTERM。用于调度器与执行器同机的部署和测试。
|
- `local`:Linux 本机文件 + `setsid -f`;kill = 对 `pid.txt` 的进程组发 SIGTERM。用于调度器与执行器同机的部署和测试。
|
||||||
- `warpgate-ssh` + `os: linux|darwin`(`PosixWarpgateTransport`):每个操作是一次 `agent-access.py --config <vault.config> run --target <target> -- bash -c '<script>'`(`relay()`,目标名取终端 `target`,缺省为终端名;客户端把 argv `shlex.join` 后交给目标账户的登录 shell,stdin 为 /dev/null)。文件:base64 放进 `printf %s '<b64>' | base64 -d`,超过 60000 字符分块追加再解码,回显字节数校验;读文件/`grep_first`/`tail` 的结果以 `B64=` 标记回传。runner 与本机相同(`run.sh`),`setsid nohup` 脱离 SSH 会话(pid = pgid = sid);kill = 对该进程组 `kill -TERM -- -<pid>` + `pkill -TERM -s <pid>`,最多等 5 s 后 KILL,`exit.txt` 已存在时不发信号(防 pid 复用);`process_count` = `pgrep -x`。`repo`/`run_root` 必须是远程绝对路径。每次调用约 10–14 s,所以启动只用一次调用。
|
- `warpgate-ssh` + `os: linux|darwin`(`PosixWarpgateTransport`):每个操作是一次 `agent-access.py --config <vault.config> run --target <target> -- bash -c '<script>'`(`relay()`,目标名取终端 `target`,缺省为终端名;客户端把 argv `shlex.join` 后交给目标账户的登录 shell,stdin 为 /dev/null)。文件:base64 放进 `printf %s '<b64>' | base64 -d`,超过 60000 字符分块追加再解码,回显字节数校验;读文件/`grep_first`/`tail` 的结果以 `B64=` 标记回传。runner 与本机相同(`run.sh`),`setsid nohup` 脱离 SSH 会话(pid = pgid = sid);kill = 对该进程组 `kill -TERM -- -<pid>` + `pkill -TERM -s <pid>`,最多等 5 s 后 KILL,`exit.txt` 已存在时不发信号(防 pid 复用);`process_count` = `pgrep -x`。`repo`/`run_root` 必须是远程绝对路径。每次调用约 10–14 s,所以启动只用一次调用。
|
||||||
- `warpgate-ssh` + `os: windows`(`WindowsWarpgateTransport`):经 `win10-ps.sh`(PowerShell `-EncodedCommand`)和 `win10-put.sh`(分块上传 + SHA-256 校验);文件读取用 base64 标记回传;kill = `taskkill /T /F /PID <pid.txt>` + `schtasks /End /Delete`。脚本目录由 `agentd.helpers_dir`(或终端 `helpers_dir`)配置;脚本目前写死 `--target win10`(TODO:改走同一个 `relay()`,argv 为 `powershell.exe -NoProfile -NonInteractive -EncodedCommand <b64>`,去掉 helper 脚本)。路径都放进 PowerShell 单引号字面量(`'` 双写);`schtasks /TR` 里脚本路径用 `\"…\"` 包裹,依赖 Windows PowerShell 5.1 的旧式原生参数传递(PowerShell 7.3+ 的 Standard 模式下需改写)。runner 进不了 repo 时写 `exit=97` 结束。
|
- `warpgate-ssh` + `os: windows`(`WindowsWarpgateTransport`):经 `win10-ps.sh`(PowerShell `-EncodedCommand`)和 `win10-put.sh`(分块上传 + SHA-256 校验);文件读取用 base64 标记回传;kill = `taskkill /T /F /PID <pid.txt>` + `schtasks /End /Delete`。脚本目录由 `agentd.helpers_dir`(或终端 `helpers_dir`)配置;脚本目前写死 `--target win10`(TODO:改走同一个 `relay()`,argv 为 `powershell.exe -NoProfile -NonInteractive -EncodedCommand <b64>`,去掉 helper 脚本)。路径都放进 PowerShell 单引号字面量(`'` 双写);`schtasks /TR` 里脚本路径用 `\"…\"` 包裹,依赖 Windows PowerShell 5.1 的旧式原生参数传递(PowerShell 7.3+ 的 Standard 模式下需改写)。runner 进不了 repo 时写 `exit=97` 结束。
|
||||||
|
|||||||
+30
-12
@@ -12,20 +12,22 @@ python3 -m unittest # 离线单测
|
|||||||
配置里只写 Vault 键名(`token_key`)。agentd 读同名环境变量,token 只由 `agent-access.py exec --secret-env KEY=KEY` 注入进程内存;不要 export 到 shell、不要写文件。agentd 起的所有子进程(本机 runner/执行器、win10 中继脚本)拿到的环境都去掉了全部 `token_key` 与 `webhook_secret_key` 变量。
|
配置里只写 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 推荐):
|
**调度器 2026-10-21(PAD-11)起跑在 LXC `agentd-01`**(root,仓库 `/opt/plane-agentd`),部署细节见 `docs/deploy-lxc.md`。任何时候只能有一个调度器排程。
|
||||||
|
单次 tick(在 LXC 上):
|
||||||
```bash
|
```bash
|
||||||
cd ~/.local/share/server-access-client && python3 -I agent-access.py \
|
cd /root/.local/share/server-access-client && python3 -I agent-access.py \
|
||||||
--config https://nas.tail9531b.ts.net:8443/identity/agent.json exec \
|
--config https://nas.tail9531b.ts.net:8443/identity/agent.json exec \
|
||||||
--secret-env PLANE_OREGON_API_TOKEN=PLANE_OREGON_API_TOKEN \
|
--secret-env PLANE_OREGON_API_TOKEN=PLANE_OREGON_API_TOKEN \
|
||||||
--secret-env PLANE_WINDOW10_API_TOKEN=PLANE_WINDOW10_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
|
--timeout 900 -- python3 /opt/plane-agentd/cli/agentd tick --once
|
||||||
```
|
```
|
||||||
crontab(oregon 实际在用,每 2 分钟):
|
crontab(LXC root,每 2 分钟):
|
||||||
```
|
```
|
||||||
*/2 * * * * /home/ubuntu/Documents/wjj/project/plane-agentd/cron/agentd-tick.sh
|
*/2 * * * * /opt/plane-agentd/cron/agentd-tick-lxc.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 撞锁,无害。
|
`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 撞锁,无害。
|
||||||
实际配置 `config/agentd.yaml`(已提交,不含密钥):`state_dir: ~/.gwent-board/agentd`,所以下表的 `state/…` 在 oregon 上是 `~/.gwent-board/agentd/…`。
|
实际配置 `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。
|
暂停一个终端(不再派新运行、也不探测,在途运行照常收尾):把它的 `concurrency` 设为 0。
|
||||||
|
|
||||||
常驻替代:`... exec ... -- cli/agentd tick --loop --interval 120`(vault 会话超时要够长,cron 更稳)。
|
常驻替代:`... exec ... -- cli/agentd tick --loop --interval 120`(vault 会话超时要够长,cron 更稳)。
|
||||||
@@ -44,8 +46,9 @@ cli/agentd show GWENT GW-12 3 # 状态/指派/标签 + 最近 3 条评论
|
|||||||
cli/agentd comment GWENT 12 reply.md # 或 - 读 stdin
|
cli/agentd comment GWENT 12 reply.md # 或 - 读 stdin
|
||||||
cli/agentd state GWENT 12 Todo # Plane 状态名或配置键(todo/review…)
|
cli/agentd state GWENT 12 Todo # Plane 状态名或配置键(todo/review…)
|
||||||
cli/agentd assign GWENT 12 window10
|
cli/agentd assign GWENT 12 window10
|
||||||
cli/agentd smoke GWENT oregon # 建一个要求回复 "[review] ok" 的工作项,tick 到结束,PASS 则移到 Done
|
cli/agentd smoke GWENT oregon --watch # 建一个要求回复 "[review] ok" 的工作项,等排程中的调度器跑完,PASS 则移到 Done
|
||||||
cli/agentd smoke GWENT win10 --labels model:sol # --labels 给冒烟项加标签(路由到模型/档位)
|
cli/agentd smoke GWENT win10 --labels model:sol --watch # --labels 给冒烟项加标签(路由到模型/档位)
|
||||||
|
# 不带 --watch = 本机自己 tick(只在调度器所在机器上、且它的 cron 停着时用)
|
||||||
```
|
```
|
||||||
CLI 以 `--as <terminal>` / `agentd.cli_terminal` / 第一个有 token 的终端的身份操作。
|
CLI 以 `--as <terminal>` / `agentd.cli_terminal` / 第一个有 token 的终端的身份操作。
|
||||||
|
|
||||||
@@ -53,8 +56,8 @@ CLI 以 `--as <terminal>` / `agentd.cli_terminal` / 第一个有 token 的终端
|
|||||||
| 路径 | 内容 |
|
| 路径 | 内容 |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `state/agentd-state.json` | 每项目每工作项:轮次、线程 ID 与所在终端/执行器、线程轮数、各轮标签、已见评论、重试计数/下次重试、在途运行(handle、deadline) |
|
| `state/agentd-state.json` | 每项目每工作项:轮次、线程 ID 与所在终端/执行器、线程轮数、各轮标签、已见评论、重试计数/下次重试、在途运行(handle、deadline) |
|
||||||
| `state/agentd.log` | 每次 tick 的动作与错误 |
|
| `<log_dir>/agentd.log`(默认 `state/`) | 每次 tick 的动作与错误 |
|
||||||
| `state/heartbeat.json` | 最近一次 tick 的时间、在途运行、错误;超过 10 分钟不更新 = agentd 没在跑 |
|
| `state/heartbeat.json` | 最近一次 tick 的时间、在途运行、启动未确认的运行(`launching`)、本 tick 跳过的不可达终端(`down`)、错误;超过 10 分钟不更新 = agentd 没在跑 |
|
||||||
| `state/agentd.lock` | tick 互斥锁 |
|
| `state/agentd.lock` | tick 互斥锁 |
|
||||||
| `state/runs/<run_id>/` | `transport: local` 终端的运行目录:`prompt.md run.sh pid.txt started.txt exit.txt stderr.txt` + 执行器输出 |
|
| `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` |
|
| oregon `/home/ubuntu/.gwent-board/agentd-runs/<run_id>/` | 远程 Linux 终端(`run_root`):同上,另有 `launch.pid`(启动时的 `$!`,与 `pid.txt` 相同);claude 输出 `out.json` |
|
||||||
@@ -80,7 +83,10 @@ Todo + 指派给终端用户 → agentd 选最小编号、终端有空位、串
|
|||||||
| `类型=env` | 匹配 `retry.env_patterns`(焦点丢失/弹窗/断网等),转 Waiting,同串行组暂停到该项离开 Waiting |
|
| `类型=env` | 匹配 `retry.env_patterns`(焦点丢失/弹窗/断网等),转 Waiting,同串行组暂停到该项离开 Waiting |
|
||||||
| `win10 unreachable; skipping` / `busy (N codex running…)` | 中继不通,或 win10 上有不是 agentd 起的 codex(`busy_processes`);等它结束 |
|
| `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>"` 查看 |
|
| `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'` |
|
| `relay oregon exit 255: …` / `relay timeout` / `<terminal> unreachable, skipping it for the rest of this tick` | 中继(Vault、Warpgate、目标 sshd)不通;该终端本 tick 余下的 poll/启动全部跳过(`heartbeat.down`),其他终端照常。poll 失败下一 tick 再试。手工:`agent-access.py --config … run --target oregon --timeout 30 -- bash -c 'echo ok'` |
|
||||||
|
| `run GW-n-k: unconfirmed launch found …; adopting it` | 启动调用失败/超时(或 agentd 在启动中途退出),但终端上已有 `exit.txt`/`launch.pid`/`pid.txt`:接管该运行,不重复启动;开始评论注明"启动未确认、已接管" |
|
||||||
|
| `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` |
|
| `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 次 |
|
| 远程 Linux 终端每步都慢 | 每次中继调用约 10–14 s(取 Vault 凭据 + SSH 握手)。一轮:启动 1 次(prompt 超过约 45 KB 时多几次分块上传)、每 tick poll 1 次、收尾 claude 约 3 次 |
|
||||||
| 工作项在运行中被人改了状态 | 运行还没结束:下一 tick 取消(kill + `[worker] 运行已取消…` 评论),不改状态。已经结束(`exit.txt` 已写):结果照常回帖,但不再改状态/重试 |
|
| 工作项在运行中被人改了状态 | 运行还没结束:下一 tick 取消(kill + `[worker] 运行已取消…` 评论),不改状态。已经结束(`exit.txt` 已写):结果照常回帖,但不再改状态/重试 |
|
||||||
@@ -88,6 +94,18 @@ Todo + 指派给终端用户 → agentd 选最小编号、终端有空位、串
|
|||||||
| 想手工终止一轮 | 把工作项移出 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` |
|
| 想手工终止一轮 | 把工作项移出 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 |
|
| `已连续 N 轮 [progress]` | 达到 `max_progress_rounds`:看最近几轮是否在原地打转;拆分任务或补充说明后移回 Todo |
|
||||||
|
|
||||||
|
## 回滚到 oregon(LXC 调度器出问题时)
|
||||||
|
1. 停 LXC:`agent-access.py … run --target agentd-01 -- bash -c 'crontab -r; flock /var/lib/plane-agentd/cron.lock true'`(删 crontab,等正在跑的 tick 结束)。
|
||||||
|
2. 状态拷回 oregon:LXC `/var/lib/plane-agentd/agentd-state.json` → oregon `~/.gwent-board/agentd/agentd-state.json`(base64 经中继读回,比对 sha256)。在途运行照常被 oregon 接着 poll。
|
||||||
|
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-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 运行目录均正常。
|
||||||
|
- 切换时 oregon 刚收尾 GW-11-1(11:53 由 oregon 派出、11:57 `[review]`),无在途运行。11:59 删除 oregon crontab 行(备份 `~/.gwent-board/crontab.backup-20261021-pad11`,oregon crontab 现为空),状态文件(sha256 `106c2bce…`)复制到 LXC,12:00 装 LXC crontab,12:02:09 第一个 cron tick(heartbeat 更新,errors=[])。
|
||||||
|
- 冒烟(oregon 上 `smoke … --watch`,由 LXC cron 派发):GW-12 oregon claude-code/sonnet 12:04:20 启动 → 12:06:47 `[review] ok` PASS;GW-13 win10 codex/gpt-6.1-sol/fast 12:05:58 启动 → 12:07:45 `[review] ok` PASS。续线程:GW-12 评论"回复 `[review] ok 2`"后移回 Todo,12:08:20 第 2 轮续 session `2348d52f…`(两轮 `out.json` 的 `session_id` 相同),12:10:44 `[review] ok 2`。两项均移到 Done。
|
||||||
|
|
||||||
## 切换记录(2026-10-21,P1-5)
|
## 切换记录(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 `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 无在途运行(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` 起。
|
||||||
|
|||||||
Reference in New Issue
Block a user