Files
plane-agentd/docs/executor-contract.md
T
2026-10-21 07:04:32 +00:00

52 lines
5.2 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.
# 执行器契约
agentd 不常驻:一轮运行比起它的那次 tick 活得久,所以执行器的一切都必须能从**可 JSON 序列化的 handle** 恢复(handle 存在 `state/agentd-state.json` 的 `inflight.handle` 里)。
## 接口(`agentd/executors/base.py`)
| 成员 | 说明 |
|---|---|
| `resume: bool` | 能否续线程。`False` 时每轮都是新线程(有前序轮次时附交接摘要)。 |
| `start(run: Run) -> handle` | 起一轮,立即返回;不得阻塞到运行结束。 |
| `poll(handle) -> "running" \| "finished"` | 便宜的完成检测。 |
| `collect(handle) -> ExecResult` | 只在 `finished` 后调用,可重复调用(幂等):Plane 回帖失败时下一 tick 会再 collect。 |
| `kill(handle)` | 超时时结束整个进程树。 |
| `forget(handle)`(可选) | collect 之后清理(如删除计划任务)。 |
`Run(run_id, repo, prompt, model, effort, tier, thread_id, script)`:`thread_id` 非空即续该线程。
`ExecResult(last_message, exit_code, thread_id, log_paths, tail)`:`last_message` 原样回帖,首行应为 `[review]/[question]/[blocked]/[progress]`;`thread_id` 供下一轮续接;`tail` 是失败说明里的"最后一帧"(stderr 尾部/最后事件)。
执行器**不接触 Plane**:不持有 token、不改工作项。回帖、状态流转、重试、超时、线程重建都由 core 统一处理。
## RunnerExecutor(现有三个适配器的共同形态)
1. 在终端上建运行目录 `<run_root>/<run_id>`(清空旧文件),写 `prompt.md`。
2. 用配置里的命令模板生成命令:`run`(新线程)或 `resume`(续线程)。占位符:`{repo} {model} {effort} {tier} {out} {thread} {perm} {script} {run_dir}`;未知占位符原样保留。`{tier}` 展开为 `tier_arg`(如 `-c 'service_tier="fast"'`),无 tier 时为空。模板首词若在终端 `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 …`)。
4. transport 分离启动(Linux `setsid -f bash run.sh`,Windows 一次性计划任务 `agentd-<run_id>`)。
5. `poll` = `exit.txt` 是否存在;`collect` 由各适配器解析:
| 适配器 | stdout | 最后消息 | 线程 ID |
|---|---|---|---|
| `codex` | `events.jsonl` | `last.md`(`-o {out}`) | `thread.started` 事件的 `thread_id` |
| `claude-code` | `out.json` | JSON 的 `result`(`is_error` 时为空,进失败流程) | JSON 的 `session_id` |
| `shell` | `stdout.txt` | `last.md`(脚本可写 `{out}`),否则 stdout 尾部 6000 字 | 无(`resume` 恒 false) |
配置里的 `thread_from` / `last_message` 目前只是说明性字段,解析逻辑在适配器里。
## 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 = 不可达)。
- `local`:Linux 本机文件 + `setsid -f`;kill = 对 `pid.txt` 的进程组发 SIGTERM。
- `warpgate-ssh`:Windows,经 `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`。
## 新增一个执行器
1. `agentd/executors/<name>.py`:继承 `RunnerExecutor`,设 `stdout_name`,实现 `collect`(必要时覆盖 `resume`/`command`)。完全不同的形态(如 HTTP API)可直接继承 `Executor`,只要 handle 可序列化。
2. 在 `agentd/executors/__init__.py:make_executor` 里登记 adapter 名;`config/schema.json` 的 `executor.adapter` enum 和 `config.semantic_errors` 的白名单同步加上。
3. 配置 `executors.<name>`:`adapter`、`run`、`resume`、`models`、`default_effort` 等;把它加进相应终端的 `executors`,需要时在 `routing.by_label` 加 `exec:<name>`。
4. 在 `tests/` 加一个用假二进制(`bins` 指向临时脚本)的本机测试,参照 `tests/test_local_runner.py`。
## 新增一个终端
1. `terminals.<name>`:`transport`、`os`、`launch`、`repo`、`plane_user`(该终端回帖身份的 Plane 显示名)、`token_key`(Vault 键名,同名环境变量)、`concurrency`、`capabilities`、`executors`,可选 `bins`、`run_root`、`busy_processes`(这些进程在跑而 agentd 没有在途运行时不派单)。
2. 项目 `routing.by_assignee` 加 `<plane_user>: <name>`,`default_executor` 加默认执行器。
3. cron 的 `agent-access.py exec` 加 `--secret-env <token_key>=<token_key>`(`agentd check-config` 会打印完整参数)。
4. `agentd smoke <PROJECT> <name>` 验证一轮往返。
新的 transport 类型(如 Linux 远程机)需在 `agentd/transports/` 实现上面的接口并在 `make_transport` 登记。