# 运行手册 ## 安装 ```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 # 离线单测 ``` 配置里只写 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 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 更稳)。 cron 的 PATH 很短:本机执行器的二进制用终端 `bins` 写绝对路径(如 `claude: /home/ubuntu/.local/bin/claude`)。 ### 执行器二进制的解析(`bins` 或 PATH) 命令模板的首词(`codex`、`claude`…)若在该终端的 `bins` 里,替换为那里的绝对路径;否则**原样交给终端的 shell 按 PATH 查找**。没有任何其他探测或回退: - oregon(local):runner 是 `bash run.sh`,环境继承 agentd 进程(cron 下 PATH 只有 `/usr/bin:/bin`),所以 `claude` 必须写进 `bins`。**示例配置的 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 ` 验证。 人工操作(同样在 `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 ` / `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//` | 本机终端的运行目录:`prompt.md run.sh pid.txt started.txt exit.txt stderr.txt` + 执行器输出 | | `\runtime\agentd\\`(win10) | 同上,`run.ps1`;codex 输出 `events.jsonl last.md` | ## 工作项生命周期 Todo + 指派给终端用户 → agentd 选最小编号、终端有空位、串行组空闲的项 → In Progress,评论 `[worker] 第 k 轮开始…` → 执行器结束后回帖 `[] 第 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-"` 查看 | | 工作项在运行中被人改了状态 | 运行还没结束:下一 tick 取消(kill + `[worker] 运行已取消…` 评论),不改状态。已经结束(`exit.txt` 已写):结果照常回帖,但不再改状态/重试 | | Plane 回帖失败 | 在途记录保留,下一 tick 重新 collect 并回帖(可能重复一条评论) | | 想手工终止一轮 | 把工作项移出 In Progress(如 Waiting)即可,下一 tick agentd 自己 kill 并释放槽位。中继不通、kill 一直失败时:在终端上手工结束(本机 `kill -TERM -`,win10 `taskkill /T /F /PID `),停 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--`,agentd 用 `runtime\agentd\GW--`。 - 冒烟: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 ` 结束了整棵树(runner powershell → codex.exe → code-mode-host / command-runner → powershell),`Get-Process codex` 归零,计划任务被删除。 - 差异:旧 worker 对没有 model 标签的项用 sol + `service_tier="fast"`;agentd 只在有 `model:sol` 标签时加 fast,无标签项跑 sol/medium 不加 fast。需要 fast 就打 `model:sol`。 - win10 上 codex 的 stderr 常见 websocket 连接被拒(`os error 10061`,`wss://chatgpt.com/...`)后回退成功;只有运行失败时它才出现在失败说明的尾部,不影响分类(不匹配临时性模式)。