一、背景
Devin CLI 是 Cognition 推出的命令行编码智能体,支持 ACP(Agent Client Protocol)协议,可被任何 ACP-aware 客户端作为子进程驱动,通过 stdin/stdout 交换 JSON-RPC 消息。
multica 是一个多智能体运行时管理器,能在同一台机器上统一调度多种编码智能体(Claude、Codex、Copilot、Hermes 等)。它在 Windows 上以 daemon 形式常驻,启动时会扫描 PATH,确认至少存在一个它认识的 agent CLI,然后按 profile 配置 exec 对应的可执行文件。
实际工作中常见这样一种拓扑:
- 工作机是 Windows,multica 装在这里,方便用 Web UI 统一管理。
- Devin CLI 装在局域网内一台 Linux 上(开发环境、算力、凭证都在那边),不想在 Windows 上再装一份。
目标是让 Windows 上的 multica 把 Linux 上的 Devin 当成一个本地运行时来调度。
二、原理
核心难点是:multica daemon 只会用 exec.Command 在本机启动可执行文件,不会跨机调用;而 Devin CLI 在另一台 Linux 上。解决办法是利用 ACP 协议”JSON-RPC over stdio”的特性——stdio 可以被 SSH 透明转发,于是跨机调用在协议层等价于本地调用。
整条链路如下:
1 | Windows: multica daemon ──exec──> hermes.cmd ──ssh──> Linux: devin acp |
分三层解决三个问题:
1. 协议层:ACP over stdio 天然可转发
devin acp 子命令启动一个 ACP server,通过 stdin/stdout 收发 JSON-RPC 消息,不依赖任何 socket、端口、PTY。SSH 的默认行为就是把远端进程的 stdio 接到本地,所以 ssh user@host devin acp 在协议上和直接 devin acp 完全等价。multica 只看到本地一个子进程的 stdio,无需任何改造。
2. 安全层:SSH 强制命令限制密钥能力
daemon 是无人值守后台进程,不能用交互式密码登录,必须用密钥。但这把密钥如果泄露,攻击者就能 SSH 进 Linux 执行任意命令,风险不可接受。解决办法是在 Linux 的 ~/.ssh/authorized_keys 里给这把公钥加 command="..." 强制命令:
1 | no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,command="/home/guotao/.local/bin/devin acp" ssh-ed25519 AAAA... devin-multica-bridge |
这样无论 SSH 客户端在命令行写什么,远端都只执行 devin acp,其它一律忽略。配合 no-pty,no-port-forwarding,... 等限制,这把钥匙被锁死成”只能启动 Devin ACP server”的单功能钥匙,泄露风险可控。
3. 发现层:借白名单名字过 daemon 预检
multica daemon 启动时有一个硬编码白名单预检:claude, codebuddy, codex, copilot, opencode, deveco, openclaw, hermes, pi, cursor-agent, kimi, kiro-cli, agy, qodercli, traecli, grok。PATH 上一个都不认识就直接退出,profile 配置再正确也救不回来。devin / devin-acp 不在白名单里。
解决办法是借 hermes 这个名字——它本身是个通用协议族名,不是某个具体产品,适合作为自定义运行时的载体。把 wrapper 文件命名为 hermes.cmd,profile 的 --command-name 也用 hermes,daemon 预检就能找到、放行。wrapper 内部内容不变,仍然是 SSH 转发到 devin acp。
三、完整步骤
Linux 侧(一次性准备)
1. 确认 sshd 运行且 devin 可用
1 | systemctl status sshd | head -5 |
2. 生成专用密钥对(无 passphrase)
1 | ssh-keygen -t ed25519 -f /tmp/devin-multica -N "" -C "devin-multica-bridge" -q |
3. 装入 authorized_keys,带强制命令与限制
1 | mkdir -p ~/.ssh && chmod 700 ~/.ssh |
4. 修复家目录权限(sshd StrictModes 要求)
sshd 默认开启 StrictModes,家目录若对 group/other 可写(如 777),会拒绝使用 authorized_keys。收窄到 755 即可,无需 sudo:
1 | chmod g-w,o-w ~ |
5. 验证强制命令生效
1 | # 用这把钥匙发任意危险命令,应被强制改成 devin acp |
期望输出是 Devin ACP server 的启动日志(Starting ACP server),而不是 shadow 文件内容——说明强制命令把任意命令都拦截了。
6. 记录连接信息
- 主机:局域网 IP(如
192.168.9.101,优先)/ 公网 IP(如113.98.232.61) - 用户:
guotao - 端口:22
- 私钥文件:
/tmp/devin-multica(内容要传到 Windows)
Windows 侧(PowerShell,每步可复现)
第 1 步:保存私钥
1 | $key = @' |
第 2 步:锁紧私钥权限
Windows 自带 ssh.exe 和 Linux 一样严格,私钥若能被其他用户读会拒绝使用:
1 | $k = "$env:USERPROFILE\.ssh\devin-multica" |
第 3 步:建 wrapper 目录并加入 PATH
1 | $dir = "$env:USERPROFILE\.local\bin" |
第 4 步:创建 hermes.cmd wrapper
关键:文件名必须是 hermes(在 multica 白名单里),不是 devin-acp。 IP 用你实测能连通的那个,局域网优先。
1 | $linuxHost = "192.168.9.101" # 不通就换公网 IP |
要点:
- wrapper 里不带任何远端命令参数——Linux 侧的强制命令会自动把它变成
devin acp,无论 SSH 客户端发什么。 -o BatchMode=yes保证 daemon 无人值守时不会因任何交互提示卡死。-o ConnectTimeout=10防止网络问题时 daemon 永久挂起。
第 5 步:测试 wrapper 端到端
1 | & "$env:USERPROFILE\.local\bin\hermes.cmd" |
期望输出:
1 | PTY allocation request failed on channel 0 |
然后挂起等输入,Ctrl+C 退出。看到这个就说明 SSH + 强制命令 + ACP over stdio 全链路通了。
常见问题:
Permission denied (publickey)→ 第 2 步权限没锁紧,重做 icaclsConnection timed out→ 换 IP 重写 wrapperUNPROTECTED PRIVATE KEY FILE→ 同上,权限问题
第 6 步:把 $multica 永久化(可选但推荐)
1 | if (-not (Test-Path $PROFILE)) { New-Item -ItemType File -Path $PROFILE -Force | Out-Null } |
第 7 步:创建 multica runtime profile
关键:--command-name hermes,不是 devin-acp。 daemon 预检按白名单扫 PATH,hermes 在白名单里,devin-acp 不在。
1 | & $multica runtime profile create ` |
记下返回的 profile-id(下面用 <ID> 代替)。
第 8 步:钉死绝对路径(绕开 daemon PATH 解析)
daemon 被桌面端拉起时继承的 PATH 比交互式 PowerShell 窄,用 set-path 给绝对路径最稳。单行写,不要用反引号续行(反引号后不能有任何字符,否则续行失效):
1 | & $multica runtime profile set-path <ID> --path "C:\Users\Administrator\.local\bin\hermes.cmd" |
第 9 步:重启 daemon 并验证
1 | & $multica daemon restart |
daemon status 应显示 running,agents 列表里出现 hermes/Devin。
第 10 步:Web UI 确认
打开 Multica Web UI → Runtimes,应看到 Devin 自定义运行时显示「在线」。
四、踩过的两个坑
坑 1:PowerShell 反引号续行
反引号 ` 后面不能有任何字符(包括空格),否则续行不生效,命令被拆成两半,第二行被当成新语句报 Missing expression after unary operator '--'。长命令一律单行写,或用括号续行。
坑 2:command-name 必须是白名单里的名字
daemon 启动预检是硬编码白名单扫描,devin-acp 不在名单里,无论怎么 set-path 都过不了预检,daemon 直接退出 1。借 hermes 这个通用协议族名字,wrapper 内容不变,就能过预检。这是整个方案能成立的关键一招。
五、验证清单
| 项 | 命令 | 预期 |
|---|---|---|
| 私钥权限 | icacls "$env:USERPROFILE\.ssh\devin-multica" | 只有你自己一条 |
| wrapper 能跑 | & "$env:USERPROFILE\.local\bin\hermes.cmd" | ACP server 启动日志 |
| profile 已注册 | & $multica runtime profile list | COMMAND_NAME=hermes |
| daemon 运行 | & $multica daemon status | running |
| Web UI | Multica → Runtimes | Devin 显示在线 |
六、安全说明
- 专用密钥对独立于个人 SSH 密钥,泄露影响面只限于”启动 Devin ACP server”。
- 强制命令 +
no-pty,no-port-forwarding,no-X11-forwarding,no-agent-forwarding把这把钥匙锁成单功能钥匙,无法用于其它任何操作。 - 私钥在 Windows 上用 icacls 锁到仅本人可读,避免被同机其它用户盗用。
- 通信走 SSH 加密通道,ACP 协议内容(含 Devin 凭证)不裸传。
- 如需进一步收紧,可在 Linux 侧用
iptables/ufw限制 22 端口来源 IP 为 Windows 工作机的固定地址。