第 3 章 · 为什么任意 CLI agent 都能接入
ClawTeam 的招牌卖点是「任何 CLI agent 都能当队员」。本章拆开这个声明背后的四件事:适配器抹平差异、提示词注入协作知识、保活循环、存活探测。
3.1 兼容性契约——门槛 其实很低
README 写明了接入一个新 agent 只需满足四条(README.md:521-527):
- 命令在
PATH上,且能脱离 ClawTeam 独立启动。 - 能在指定工作目录 / worktree 里运行。
- 能接受一个初始任务(命令行参数或交互输入皆可)。
- 如果是交互式的,进程要能在 tmux 里活着。
这条契约之所以低,是因为 ClawTeam 不要求 agent 懂任何协议——协作知识是 spawn 时注入进它提示词的(见 §1.4 与下文)。
3.2 适配器:一个类抹平各家差异
不同 CLI 的「跳过权限确认」flag、「传 prompt」方式、「设工作目录」方式都不一样。这些差异集中在 NativeCliAdapter.prepare_command(clawteam/spawn/adapters.py:34-146)。用一张表看它抹平了什么:
| CLI | 跳过权限的 flag | 传 prompt 的方式 |
|---|---|---|
| claude | --dangerously-skip-permissions(root 下自动省略) | 交互式:启动后注入(post_launch_prompt) |
| codex | --dangerously-bypass-approvals-and-sandbox | 交互式:启动后注入;子命令模式:作为位置参数 |
| gemini | --yolo | -i <prompt>(交互)/ -p(非交互) |
| kimi | --yolo | --print -p <prompt>,并 -w <cwd> 设工作目录 |
| qwen / opencode | --yolo | -p <prompt> |
| nanobot | (normalize 成 nanobot agent) | -m <prompt>,支持 docker 包装 |
| openclaw | 无(设计极简) | --message <prompt> + --session-id |
| pi | 无(设计极简) | 交互式直接追加;否则 -p |
(出处:adapters.py 第 49-140 行;各 is_*_command 判别函数在第 156-237 行,靠 command_basename 取可执行名小写来识别)
两个非显然的细节:
- claude 在 root 下会拒绝
--dangerously-skip-permissions,所以检测到os.getuid()==0时静默省略该 flag,否则 agent 根本起不来(adapters.py:52-55)。 - 命令归一化 在更底层的
normalize_spawn_command里(clawteam/spawn/command_validation.py),例如把裸nanobot提升成nanobot agent(README.md:515提到这点)。
直觉: 适配器就是「一个翻译官」。leader 只管说「用 codex 跑这个任务」,翻译官负责把它翻成 codex 真正认识的那串 flag。新增一个 agent = 多写一个
is_xxx_command+ 在prepare_command里加一个分支。
3.3 提示词注入:让 worker 凭空学会协作
协作知识怎么进到一个「啥都不知道」的 CLI agent 脑子里?分两条路,取决于 CLI 怎么收 prompt:
- 非交互 / 支持
-p的:协作提示词作为命令行参数直接传进去。 - 交互式 TUI(claude/codex):启动时无法用参数稳妥传多行提示词,于是走
post_launch_prompt——先等 TUI 就绪,再用 tmux paste-buffer 把提示词「贴」进输入框并回车(§1.4 已详述_inject_prompt_via_buffer)。
「等 TUI 就绪」本身是个启发式:轮询 pane 文本,看到提示符字符(❯ > ›)或内容连续两轮不变就认为就绪(_wait_for_cli_ready,clawteam/spawn/tmux_backend.py:601-654)。
3.4 保活续跑:worker 别干完一单就跑
agent 的天性是「干完任务就退出」。但 ClawTeam 想要 worker 持续待命,干完继续领新活。这靠两层:
提示层:协作协议明确写「不要干完第一个任务就退出」「保持监控/上报循环在前台」(clawteam/spawn/prompt.py:96-106)。
进程层:tmux 启动的不是裸命令,而是一段 build_keepalive_shell_command 生成的 shell while 循环(clawteam/spawn/keepalive.py:53-91)。逻辑是:
while true:
跑 agent 命令
记下退出码,触发 lifecycle on-exit 钩子
if 退出码==0 且 `lifecycle should-keepalive` 通过:
把命令换成「续跑命令」(如 claude --continue),sleep 1,继续循环
else:
退出
「续跑命令」由 build_resume_command 按 CLI 给出(keepalive.py:11-35):claude 是 --continue、codex 是 resume --last、gemini 是 --resume latest…… 也就是用各家 CLI 自己的会话恢复机制接着上次干。这样一个 worker 可以反复被唤醒续跑,而不是每次都从零开始。
3.5 存活探测:三套办法判断 agent 死活
任务抢锁、死任务回收、leader 唤醒,全都依赖「这个 agent 还活着吗」。is_agent_alive 按后端分三套(clawteam/spawn/registry.py:55-79):
| 后端 | 怎么判断活着 | 兜底 |
|---|---|---|
tmux | tmux list-panes 查 pane_dead 标志;pane 只剩 shell(bash/zsh)也算死 | tmux target 失效时回退查 PID |
subprocess | os.kill(pid, 0) 探测 PID | —— |
wsh | wsh blocks list --json 查 block 是否还在 | —— |
返回三态:True(活)、False(死)、None(查无登记)。三态很重要——「不确定」时抢锁逻辑选择保守不抢(§2.2),宁可让任务多等也不冒重复执行的险。
巧妙之处: tmux 探活会检查
pane_current_command,如果 pane 里跑的只是bash,说明 agent 进程已退、只剩外壳——判定为死(_tmux_pane_alive,第 188-190 行)。这比单看「窗口还在不在」准确得多。
3.6 Profiles / Presets:换供应商不改命令
想用「Claude Code 跑在 Moonshot Kimi 后端」这种非默认供应商组合,不必每次手动 export 一堆环境变量。ClawTeam 提供两层抽象:
- preset:可复用的「供应商模板」(端点、API key 映射等),如内置的
moonshot-cn、minimax-global。 - profile:由 preset 生成、最终被
spawn/launch使用的运行时对象。
spawn 时 --profile <name> 会 apply_profile 把对应的命令前缀和环境变量注入(clawteam/cli/commands.py:3205-3215,调用 clawteam/spawn/profiles.py)。这让「任意 CLI agent」进一步扩展成「任意 CLI agent × 任意供应商端点」。
下一章看自动化编排与全局精华 → 04-internals.md。