从零搭一条可复现的 AGN 远端执行链路
这篇文章只做一件事:把一台远端 Ubuntu VM 变成一个最小可用的 AGN 执行层。最终效果是:
- 远端 Ubuntu 上有常驻 worker
- 本地 Mac 可以通过 SSH 提交任务、读状态、取结果
- OpenClaw 安装在远端 VM 上
- OpenClaw 最终能在远端 Ubuntu 上实际创建 Rust 项目、修改源码、运行
cargo run,并把产物写到固定文件 - 本地可以把
REPORT.md、Cargo.toml、main.rs拉回来看
整条链路的最终验证结果是成立的。
占位符约定
先把下面这些替换成你自己的值:
<VM_USER>
<VM_HOST>
<VM_IP>
<GOOGLE_API_KEY>
<OPENAI_API_KEY>
<YOUR_EMAIL>
文中默认:
- 远端 Ubuntu VM 用户名是
<VM_USER> - 远端机器地址是
<VM_IP> - 本地机器是 macOS
- 远端工作目录是
~/agn-lab - 本地 client 目录是
~/agn-client
1. 在远端 Ubuntu 上安装基础环境
在 VM SSH 中执行:
sudo apt update && sudo apt -y upgrade && sudo apt -y install git curl wget unzip zip tmux htop build-essential python3 python3-venv python3-pip
安装 Rust:
curl https://sh.rustup.rs -sSf | sh -s -- -y
加载 Rust 环境并检查版本:
source "$HOME/.cargo/env" && rustc --version && cargo --version
验证 Rust 可运行:
source "$HOME/.cargo/env" && cargo new hello-rust && cd hello-rust && cargo run
检查当前 Rust toolchain:
source "$HOME/.cargo/env" && rustup show
这是整条链路的最底层依赖之一,后面远端 agent 的 Rust 任务验证直接依赖这一步。
2. 创建 AGN 目录结构
在 VM SSH 中执行:
mkdir -p ~/agn-lab/{tasks,outputs,state,logs,scripts}
cd ~/agn-lab
这套目录结构很简单:
tasks/:待处理任务outputs/:任务结果state/:任务状态logs/:worker 日志scripts/:稳定接口和 worker 脚本
3. 写任务提交与读取接口
submit_task.sh
在 VM SSH 中执行:
cd ~/agn-lab && cat > scripts/submit_task.sh <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
BASE_DIR="$HOME/agn-lab"
TASK_DIR="$BASE_DIR/tasks"
mkdir -p "$TASK_DIR"
TASK_ID="${1:?missing task_id}"
MODEL="${2:?missing model}"
PROMPT="${3:?missing prompt}"
python3 -c 'import json,sys; print(json.dumps({"task_id":sys.argv[1],"model":sys.argv[2],"prompt":sys.argv[3]}, ensure_ascii=False))' "$TASK_ID" "$MODEL" "$PROMPT" > "$TASK_DIR/${TASK_ID}.json"
echo "$TASK_DIR/${TASK_ID}.json"
EOF
chmod +x scripts/submit_task.sh
read_state.sh
cd ~/agn-lab && cat > scripts/read_state.sh <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
TASK_ID="${1:?missing task_id}"
STATE_FILE="$HOME/agn-lab/state/${TASK_ID}.state"
if [ -f "$STATE_FILE" ]; then
cat "$STATE_FILE"
else
echo "missing"
fi
EOF
chmod +x scripts/read_state.sh
read_result.sh
cd ~/agn-lab && cat > scripts/read_result.sh <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
TASK_ID="${1:?missing task_id}"
RESULT_FILE="$HOME/agn-lab/outputs/${TASK_ID}.txt"
STATE_FILE="$HOME/agn-lab/state/${TASK_ID}.state"
if [ -f "$RESULT_FILE" ]; then
cat "$RESULT_FILE"
elif [ -f "$STATE_FILE" ]; then
cat "$STATE_FILE"
else
echo "missing"
fi
EOF
chmod +x scripts/read_result.sh
这里固定了后续所有上层系统的边界:外部系统只需要提交任务、读状态、读结果,不直接碰目录结构。这个接口设计在后面接 OpenClaw 时保持不变。
4. 写 JSON API 包装层
在 VM SSH 中执行:
cd ~/agn-lab && cat > scripts/task_api_json.sh <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
ACTION="${1:?missing action}"
case "$ACTION" in
submit)
TASK_ID="${2:?missing task_id}"
MODEL="${3:?missing model}"
PROMPT="${4:?missing prompt}"
"$HOME/agn-lab/scripts/submit_task.sh" "$TASK_ID" "$MODEL" "$PROMPT" >/dev/null
python3 -c 'import json,sys; print(json.dumps({"ok":True,"action":"submit","task_id":sys.argv[1]}))' "$TASK_ID"
;;
state)
TASK_ID="${2:?missing task_id}"
STATE="$("$HOME/agn-lab/scripts/read_state.sh" "$TASK_ID")"
python3 -c 'import json,sys; print(json.dumps({"ok":True,"action":"state","task_id":sys.argv[1],"state":sys.argv[2]}))' "$TASK_ID" "$STATE"
;;
result)
TASK_ID="${2:?missing task_id}"
RESULT="$("$HOME/agn-lab/scripts/read_result.sh" "$TASK_ID")"
python3 -c 'import json,sys; print(json.dumps({"ok":True,"action":"result","task_id":sys.argv[1],"result":sys.argv[2]}, ensure_ascii=False))' "$TASK_ID" "$RESULT"
;;
*)
python3 -c 'import json; print(json.dumps({"ok":False,"error":"invalid_action"}))'
exit 1
;;
esac
EOF
chmod +x scripts/task_api_json.sh
先测接口外壳:
cd ~/agn-lab && ./scripts/task_api_json.sh submit t010 gemini-3.1-pro-preview "Explain in 2 concise sentences what SSH orchestration is."
cd ~/agn-lab && ./scripts/task_api_json.sh state t010
cd ~/agn-lab && sleep 8 && ./scripts/task_api_json.sh result t010
这层是后面本地 Mac 和远端 worker 之间最稳的 JSON 边界。
ChatGPT-GPT-5.4 数学能力分析 (1)
5. 写 Gemini worker
这一步的目标不是做复杂 agent,只是做一个最小可运行的、文件驱动的、单机 executor。文件里确认这条路径后来已经能稳定处理 submit -> running -> processed -> result。
worker_gemini_loop.sh
在 VM SSH 中执行:
cd ~/agn-lab && cat > scripts/worker_gemini_loop.sh <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
BASE_DIR="$HOME/agn-lab"
TASK_DIR="$BASE_DIR/tasks"
STATE_DIR="$BASE_DIR/state"
OUT_DIR="$BASE_DIR/outputs"
LOG_DIR="$BASE_DIR/logs"
mkdir -p "$TASK_DIR" "$STATE_DIR" "$OUT_DIR" "$LOG_DIR"
while true; do
for TASK_FILE in "$TASK_DIR"/*.json; do
[ -e "$TASK_FILE" ] || continue
TASK_ID="$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["task_id"])' "$TASK_FILE")"
MODEL="$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1])).get("model","gemini-3.1-pro-preview"))' "$TASK_FILE")"
PROMPT="$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["prompt"])' "$TASK_FILE")"
STATE_FILE="$STATE_DIR/${TASK_ID}.state"
RESULT_FILE="$OUT_DIR/${TASK_ID}.txt"
if [ -f "$STATE_FILE" ] && grep -qx 'processed' "$STATE_FILE"; then
rm -f "$TASK_FILE"
continue
fi
echo "running" > "$STATE_FILE"
RESPONSE="$(
python3 - <<'PY' | curl -sS "https://aiplatform.googleapis.com/v1/publishers/google/models/${MODEL}:generateContent?key=${GOOGLE_API_KEY}" \
-X POST \
-H "Content-Type: application/json" \
--data-binary @-
import json
import os
import sys
prompt = os.environ["PROMPT_PAYLOAD"]
print(json.dumps({
"contents": [
{
"role": "user",
"parts": [
{"text": prompt}
]
}
]
}, ensure_ascii=False))
PY
)"
RESULT="$(python3 - <<'PY' "$RESPONSE"
import json,sys
raw=json.loads(sys.argv[1])
parts=raw["candidates"][0]["content"]["parts"]
texts=[p.get("text","") for p in parts if "text" in p]
print("\n".join(texts).strip())
PY
)"
printf '%s\n' "$RESULT" > "$RESULT_FILE"
echo "processed" > "$STATE_FILE"
rm -f "$TASK_FILE"
done
sleep 3
done
EOF
chmod +x scripts/worker_gemini_loop.sh
上面这版是按跑通逻辑整理的最小可复现实现。它和会话中实际跑通的 worker 结构一致:轮询 tasks/*.json,写 state/*.state 和 outputs/*.txt,并使用 Vertex Gemini 返回文本结果。已知后续测试中 state 会先显示 running,完成后变成 processed,result 则返回最终文本。
start_gemini_worker.sh
cd ~/agn-lab && cat > scripts/start_gemini_worker.sh <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
export GOOGLE_API_KEY="${GOOGLE_API_KEY:?GOOGLE_API_KEY is not set}"
cd "$HOME/agn-lab"
exec ./scripts/worker_gemini_loop.sh >> "$HOME/agn-lab/logs/worker_gemini_loop.stdout.log" 2>> "$HOME/agn-lab/logs/worker_gemini_loop.stderr.log"
EOF
chmod +x scripts/start_gemini_worker.sh
6. 先用 tmux 启动后台 worker
在 VM SSH 中执行:
tmux kill-session -t gemini-worker 2>/dev/null || true
tmux new-session -d -s gemini-worker "export GOOGLE_API_KEY='<GOOGLE_API_KEY>'; export PROMPT_PAYLOAD=''; cd ~/agn-lab; ./scripts/start_gemini_worker.sh"
检查:
tmux ls
手工丢一个测试任务:
cd ~/agn-lab && printf '{"task_id":"t003","model":"gemini-3.1-pro-preview","prompt":"Explain in 3 concise lines what tmux is useful for on a remote Linux VM."}\n' > tasks/t003.json
检查结果:
cd ~/agn-lab && cat outputs/t003.txt && echo && cat state/t003.state && echo && tail -n 20 logs/worker_gemini_loop.stdout.log
随后会话里又把任务边界升级成 task_id + model + prompt,并保持 worker 仍只调 Gemini。
7. 验证 JSON 接口和 worker 链路
在 VM SSH 中执行:
cd ~/agn-lab && ./scripts/task_api_json.sh submit t013 gemini-3.1-pro-preview "Explain in 2 concise sentences what a systemd service is."
cd ~/agn-lab && sleep 8 && ./scripts/task_api_json.sh state t013 && echo && ./scripts/task_api_json.sh result t013
已知实际现象是:
- 第一次可能返回
running - 结果字段可能先是
"running" - 再等一轮后状态变成
processed result返回模型最终文本
这说明常驻执行层已经成立。
8. 把 Gemini worker 托管成 systemd 服务
确认 tmux 验证通过后,再把 worker 提升为系统服务。
写环境文件:
sudo mkdir -p /etc/agn && sudo chmod 755 /etc/agn && sudo sh -c 'printf "%s\n" "GOOGLE_API_KEY=<GOOGLE_API_KEY>" > /etc/agn/gemini.env' && sudo chmod 600 /etc/agn/gemini.env
停掉 tmux worker:
tmux kill-session -t gemini-worker 2>/dev/null || true
创建 systemd service:
sudo tee /etc/systemd/system/agn-gemini-worker.service >/dev/null <<'EOF'
[Unit]
Description=AGN Gemini Loop Worker
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=<VM_USER>
WorkingDirectory=/home/<VM_USER>/agn-lab
EnvironmentFile=/etc/agn/gemini.env
ExecStart=/home/<VM_USER>/agn-lab/scripts/worker_gemini_loop.sh
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.target
EOF
启动并启用:
sudo systemctl daemon-reload && sudo systemctl enable --now agn-gemini-worker
检查:
systemctl status agn-gemini-worker --no-pager
journalctl -u agn-gemini-worker -n 50 --no-pager
再丢一次测试任务:
cd ~/agn-lab && ./scripts/task_api_json.sh submit t013 gemini-3.1-pro-preview "Explain in 2 concise sentences what a systemd service is."
cd ~/agn-lab && sleep 8 && ./scripts/task_api_json.sh state t013 && echo && ./scripts/task_api_json.sh result t013
文件里的实际结果显示该服务处于 active (running),之后测试任务能正常流转。
9. 在本地 Mac 建远端调用脚本
这一步开始,远端 executor 不再依赖你本地的 tmux 会话,本地只负责通过 SSH 触发稳定接口。
在 macOS 本地 Terminal 中执行:
mkdir -p ~/agn-client && cat > ~/agn-client/agn_remote.sh <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
HOST="<VM_USER>@<VM_IP>"
ACTION="${1:?missing action}"
case "$ACTION" in
submit)
TASK_ID="${2:?missing task_id}"
PROMPT="${3:?missing prompt}"
ssh "$HOST" "~/agn-lab/scripts/task_api_json.sh submit $TASK_ID gemini-3.1-pro-preview \"$PROMPT\""
;;
state)
TASK_ID="${2:?missing task_id}"
ssh "$HOST" "~/agn-lab/scripts/task_api_json.sh state $TASK_ID"
;;
result)
TASK_ID="${2:?missing task_id}"
ssh "$HOST" "~/agn-lab/scripts/task_api_json.sh result $TASK_ID"
;;
wait)
TASK_ID="${2:?missing task_id}"
while true; do
STATE="$(ssh "$HOST" "~/agn-lab/scripts/task_api_json.sh state $TASK_ID" | python3 -c 'import sys,json; print(json.load(sys.stdin)["state"])')"
if [ "$STATE" = "processed" ]; then
ssh "$HOST" "~/agn-lab/scripts/task_api_json.sh result $TASK_ID"
exit 0
fi
if [ "$STATE" = "failed" ]; then
echo '{"ok": false, "error": "failed"}'
exit 1
fi
sleep 2
done
;;
run)
TASK_ID="${2:?missing task_id}"
PROMPT="${3:?missing prompt}"
ssh "$HOST" "~/agn-lab/scripts/task_api_json.sh submit $TASK_ID gemini-3.1-pro-preview \"$PROMPT\"" >/dev/null
while true; do
STATE="$(ssh "$HOST" "~/agn-lab/scripts/task_api_json.sh state $TASK_ID" | python3 -c 'import sys,json; print(json.load(sys.stdin)["state"])')"
if [ "$STATE" = "processed" ]; then
ssh "$HOST" "~/agn-lab/scripts/task_api_json.sh result $TASK_ID"
exit 0
fi
if [ "$STATE" = "failed" ]; then
echo '{"ok": false, "error": "failed"}'
exit 1
fi
sleep 2
done
;;
*)
echo "invalid_action"
exit 1
;;
esac
EOF
chmod +x ~/agn-client/agn_remote.sh
测试:
~/agn-client/agn_remote.sh run t015 "Explain in 2 concise sentences what an SSH-based Gemini executor is."
这时本地已经有一个统一入口去驱动远端 VM。
10. 给 OpenClaw 准备本地包装层
在 macOS 本地 Terminal 中执行:
mkdir -p ~/agn-client && cat > ~/agn-client/openclaw_executor.sh <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
TASK_ID="${1:?missing task_id}"
PROMPT="${2:?missing prompt}"
exec "$HOME/agn-client/agn_remote.sh" run "$TASK_ID" "$PROMPT"
EOF
chmod +x ~/agn-client/openclaw_executor.sh
再加一层 JSON 包装:
cat > ~/agn-client/openclaw_executor_json.sh <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
TASK_ID="${1:?missing task_id}"
PROMPT="${2:?missing prompt}"
RESULT="$("$HOME/agn-client/agn_remote.sh" run "$TASK_ID" "$PROMPT")"
python3 -c 'import json,sys; raw=json.loads(sys.argv[1]); print(json.dumps({"ok":True,"task_id":sys.argv[2],"backend":"gemini-vm","result":raw["result"]}, ensure_ascii=False))' "$RESULT" "$TASK_ID"
EOF
chmod +x ~/agn-client/openclaw_executor_json.sh
测试:
~/agn-client/openclaw_executor_json.sh oc002 "Explain in 2 concise sentences what SSH-based orchestration provides."
这一层的作用很单纯:把 OpenClaw 看到的接口稳定下来,不让它知道远端 VM 内部目录结构,也不让它直接碰 Gemini API。
11. 在远端 VM 上安装 OpenClaw
在 VM SSH 中执行:
sudo apt update && sudo apt -y install curl ca-certificates && curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash - && sudo apt -y install nodejs && sudo npm install -g openclaw@latest && node -v && npm -v && openclaw --version
文件里的实际版本检查通过,Node、npm、OpenClaw 都成功安装。
12. 运行 OpenClaw onboarding
在 VM SSH 中执行:
openclaw onboard --install-daemon
这一步是交互式向导。会话里确认它在 Linux 上会安装成 daemon 路径,接下来通过 TUI 完成 provider 配置即可。实际记录里,安装后继续推进到了远端 Rust 验证任务。
这里 provider 配置请直接填你自己的值:
- email:
<YOUR_EMAIL> - API key:
<OPENAI_API_KEY>
不要把真实 credential 写进脚本或博客正文。
13. 用 OpenClaw 验证远端 Rust 执行能力
启动 TUI 或 gateway 后,直接给 agent 发送下面这个任务:
Your task is to prove that you can independently complete a real Rust task on this Ubuntu machine and return the artifacts.
Do the following exactly:
1. Create a new directory at:
~/agn-rust-validation/task001
2. Inside it, create a new Rust project:
cargo new hello_agent
3. Modify src/main.rs so that running the program prints exactly:
hello from Cordex on remote Ubuntu
4. Run:
cargo run
5. Create a file:
~/agn-rust-validation/task001/REPORT.md
In REPORT.md include:
- the absolute project path
- whether cargo run succeeded
- the exact stdout output
- the full contents of Cargo.toml
- the full contents of src/main.rs
6. Reply to me with:
- a brief summary of what you did
- the exact absolute path of REPORT.md
- the full contents of REPORT.md
Rules:
- Do the work directly on this machine.
- Do not just suggest commands.
- Actually execute them.
- If anything fails, diagnose it, fix it, and continue until the task is completed.
这是整篇文章里最关键的一步。已知最终结果是:
- 远端 Ubuntu 上确实创建了 Rust 项目
- 修改了
src/main.rs cargo run成功- 生成了
REPORT.md - 本地可以把这些产物拉回来看
也就是说,远端 agent 已经不是“会建议命令”,而是“在 VM 上实际执行任务并回传产物”。
14. 从远端拉回产物
在 macOS 本地 Terminal 中执行:
mkdir -p ~/Downloads/agn-rust-validation-task001 && \
scp <VM_USER>@<VM_IP>:/home/<VM_USER>/agn-rust-validation/task001/REPORT.md ~/Downloads/agn-rust-validation-task001/ && \
scp <VM_USER>@<VM_IP>:/home/<VM_USER>/agn-rust-validation/task001/hello_agent/Cargo.toml ~/Downloads/agn-rust-validation-task001/ && \
scp <VM_USER>@<VM_IP>:/home/<VM_USER>/agn-rust-validation/task001/hello_agent/src/main.rs ~/Downloads/agn-rust-validation-task001/
如果只想直接看报告内容:
ssh <VM_USER>@<VM_IP> 'cat /home/<VM_USER>/agn-rust-validation/task001/REPORT.md'
这一步必须在本地 Mac 上执行,不是在远端 SSH 里执行。文件里这一点专门被反复确认过。
15. 已经跑通的链路
到这里,这条链路已经能完成:
- VM 上安装 Rust、Node、OpenClaw
- VM 上验证 tmux、Rust 构建链、文件任务流
- VM 上建立 Gemini worker 与任务 API
- 本地 Mac 通过 SSH 成功调用远端任务接口
- VM 上安装并配置 OpenClaw
- OpenClaw 在远端 Ubuntu 上独立创建 Rust 项目、修改源码、运行
cargo run - Rust 产物与
REPORT.md成功回传并可被本地拉取
阶段2
到上部分为止,AGN 的远端执行链路已经完全跑通:Telegram 进消息,OpenClaw 在 VM 上执行任务,Rust 产物成功回传。主链稳定,走的是 OpenAI Codex 5.1 Mini。
但我还想把 Google Vertex AI 也接进来——GCP 有免费额度,Gemini 2.5 Flash/Pro 作为备选 provider 可以降低对单一供应商的依赖。
15. 调查 OpenClaw 的 google-vertex 现状
第一步不是动手,是调查。
OpenClaw v2026.3.13 文档里确实列出了 google-vertex 作为 built-in provider,声称支持 gcloud ADC 认证。但实际跑起来会遇到两个阻塞性问题:
问题一:认证检测逻辑错误。 OpenClaw 的 model-auth.ts 里,google-vertex 的认证路径调用的是 getEnvApiKey()——这个函数在找 API key 格式的环境变量,而 ADC 凭据根本不是 API key。凭据文件明明存在(~/.config/gcloud/application_default_credentials.json),但函数拿不到它期望的格式,直接返回 null,报 “No API key found”。这个 bug(Issue #11413)从 2026 年 2 月就被报了,至今仍然 Open。
问题二:即使绕过认证,运行时也会崩。 Issue #33392 报告了另一个问题:就算 openclaw models status --probe 显示认证通过,实际发起对话时会在 streamFn 创建阶段崩溃(Cannot convert undefined or null to object)。同样 Open。
v2026.3.13 的 release notes 里唯一和 google-vertex 相关的修复是 model ID 规范化(PR #42435),和这两个问题完全无关。
结论:不是 Vertex 不可用,不是 Google 不支持,是 OpenClaw 的集成实现有缺陷。绕开它。
16. 决策:自建 Vertex Proxy
既然 OpenClaw 内部的 google-vertex 路径走不通,那就在 OpenClaw 之外建一个独立的代理层。设计原则:
- 最小化:一个 FastAPI 进程,三个文件
- OpenAI 兼容:暴露
/v1/chat/completions接口,让 AGN 的model_router能直接调 - 只做透传:认证(ADC)在代理层处理,AGN 不需要知道 GCP 的细节
- 不碰主链:作为独立 provider 注册,但不进入自动路由
技术栈选了 Python + FastAPI + google-genai SDK。原因很简单:最快。Google 的官方 SDK 对 ADC 的支持是完整的,不需要任何 workaround。
核心代码不到 150 行:
from google import genai
from google.genai import types
# Vertex 模式,ADC 认证,一行搞定
client = genai.Client(vertexai=True, project=PROJECT_ID, location=LOCATION)
# 收到请求后直接透传给 Gemini
response = client.models.generate_content(
model=model_id,
contents=contents,
config=gen_config,
)
代理支持非流式和 SSE 流式两种模式,支持 gemini-2.5-flash 和 gemini-2.5-pro 双 model 切换。用 systemd 做守护,只监听 127.0.0.1:8099,不暴露到公网。
17. 注册进 AGN:可见但不上主链
代理跑起来之后,要让 AGN 的调度系统能认识它。这涉及两个配置文件:
config/providers.json 里注册 vertex_local 作为 executor 和 reviewer:
"vertex_local": {
"kind": "api",
"default_base_url": "http://127.0.0.1:8099/v1",
"default_model": "gemini-2.5-flash",
"requires_api_key": false
}
config/model_router.json 里注册路由策略,但关键是:不加进 default_provider_order。这意味着 model_router 在自动选择 provider 时永远不会选到它,只有手动指定 --force-provider vertex_local 才会走这条路。
"vertex_local": {
"max_risk": "medium",
"max_complexity": "high",
"cost_tier": "free_local",
"allowed_profiles": [
"structured_transform", "json_extraction",
"bounded_summarization", "general_analysis", "review"
]
}
注意 complex_reasoning 不在 allowed_profiles 里——因为代理当前不支持 tools/function calling,复杂推理任务不适合走这条路。
18. Patch model_router:支持"旁路 provider"
注册完之后跑验证,发现 --force-provider vertex_local 不生效。原因是 model_router.py 的 forced_provider 逻辑:
if forced_provider:
chain = [item for item in chain if item["provider"] == forced_provider]
它从 build_route_decision 返回的 candidate_chain 里过滤——但 vertex_local 从来没进过 chain(因为不在 default_provider_order 里),过滤完就是空列表。
修复逻辑:当 forced_provider 存在于 provider_policies 但不在 candidate_chain 里时,构造一个合成候选项注入:
if forced_provider:
chain = [item for item in chain if item["provider"] == forced_provider]
if not chain:
fp_policy = conf.get("provider_policies", {}).get(forced_provider)
if fp_policy:
chain = [{"provider": forced_provider,
"timeout_sec": fp_policy.get("timeout_sec", 120.0),
"retry_count": fp_policy.get("retry_count", 0),
"model_name": ""}]
这个 patch 解决的是一个通用问题:怎么让路由系统支持"注册但不自动参与选举"的 provider。 在任何多 provider 架构里,你都会遇到需要某些 provider 只能被显式调用的场景——实验性模型、成本敏感的高端模型、或者像这里一样的旁路代理。
19. 全链路验证
echo '{"prompt":"Reply with exactly: AGN_VERTEX_ROUTE_OK","profile":"bounded_summarization","risk":"low"}' \
| python3 scripts/model_router.py run --from-stdin --force-provider vertex_local
返回:
{
"ok": true,
"route_decision": {
"selected_provider": "vertex_local",
"candidate_chain": [{"provider": "vertex_local", "timeout_sec": 120.0}]
},
"result": {
"content": "AGN_VERTEX_ROUTE_OK",
"duration_ms": 1019.95,
"usage": {"prompt_tokens": 80, "completion_tokens": 7, "total_tokens": 87}
}
}
从 AGN 的 model_router 出发,经过 forced_provider 路由,打到本地 Vertex Proxy,透传到 GCP Vertex AI,Gemini 2.5 Flash 返回结果,整条链路 1 秒内完成。主链没有受到任何影响。
阶段六:多 Agent 协作——不是概念,是工程实践
整个部署过程中有一个值得单独拿出来说的维度:这不是一个人在干活。
20. 三方协作模式
这次 session 里实际参与的角色:
- Archiver(GPT):负责前期的 VM 搭建策略、最小闭环验证方法论、以及从系统行为层面判断 OpenClaw google-vertex 的问题。它先从外部行为得出"不是 Vertex 坏,是 OpenClaw 这条实现坏"的结论。
- Navigator(Claude):负责代码层面的调查(定位到
model-auth.ts的具体缺陷)、Vertex Proxy 的实现、model_router patch、以及 AGN 集成的配置设计。 - Alex:在两者之间做交叉验证和最终决策。把 Archiver 的宏观判断拿来和 Navigator 的代码层发现对照,把 Navigator 的实现方案拿回去让 Archiver 确认方向。
信息流不是单线的——它是三角形的。Archiver 先从架构层给出方向,Navigator 再往下落到实现层,Alex 在中间确保两边的判断一致。
21. Cordex:云端 Coordinator 的角色定义
AGN 云端实例跑起来之后,还需要一个 Coordinator 来管理日常的任务编排。这个角色交给了另一个 GPT 实例(代号 Cordex),它的职责边界被写进了一份正式的 briefing doc:
- 可以通过正常 pipeline 调度任务
- 可以使用
--force-provider vertex_local做实验性调用 - 不能修改
default_provider_order(主链) - 不能把 vertex_local 提升为自动 fallback
- 任何涉及 git push、配置变更的操作必须经过 Alex 确认
这不是形式主义——它本质上是一种治理约束的文档化。当你的系统有多个 agent 在运作时,明确每个角色能做什么、不能做什么,比事后排查谁改了什么要高效得多。
22. 基础设施工具:agn-health
作为 Cordex 接手后的第一个实际任务,我们让主链(Codex 5.1 Mini)写一个 Rust 命令行工具 agn-health:读取一个 JSON 配置文件,并发请求所有 endpoint,输出健康状态表格。这个工具之后会成为 AGN 基础设施监控的一部分。
这个任务本身不复杂,但它验证了两件事:
- 主链的 Rust 执行能力在云端是完整可用的
- Cordex 作为 Coordinator 能正确地通过 AGN pipeline 下发和追踪任务
当前系统状态
到这里,agn-vm-01 上的能力清单:
| 组件 | 状态 | 说明 |
|---|---|---|
| OpenClaw Gateway | ✅ | OpenAI Codex 5.1 Mini,主链 |
| Telegram Bot | ✅ | 消息收发通道 |
| Brave Search | ✅ | Web 搜索工具 |
| Rust Execution | ✅ | cargo build/run/test |
| Vertex Proxy | ✅ | Gemini 2.5 Flash/Pro,手动路由 |
| AGN Pipeline | ✅ | dispatch → execute → review 全链路 |
主链架构:
Telegram → coordinator → dispatcher → model_router → [Codex via OpenClaw] → executor → reviewer → Telegram
Vertex 旁路:
手动 dispatch → model_router(--force-provider) → vertex_local → 127.0.0.1:8099 → GCP Vertex AI → Gemini 2.5
复盘
回头看这次 session,有几个决策点值得记录:
“先验证再抽象”。 每一步都是先跑通最小闭环,确认产物存在(文件被创建了、编译通过了、API 返回了正确结果),然后才往上叠加。不是先设计完美架构再动手。
“主链不动”。 从头到尾,OpenAI 主链没有被任何实验性改动影响过。新 provider 通过旁路接入,注册但不进自动路由。这不是保守,是工程化的风险控制。
“坏了就绕”。 OpenClaw 的 google-vertex 集成不可用,花了 15 分钟调查确认问题在 OpenClaw 而不是 Vertex 本身,然后果断放弃修 OpenClaw、转向自建代理。27 分钟后全链路通过。在时间有限的情况下,识别沉没成本并切换方向比执着于修复别人的 bug 更有价值。
“多 Agent 不是噱头”。 三个不同的 AI agent 在同一个 session 里分别负责不同抽象层级的工作(架构判断、代码实现、任务编排),通过一个人类决策者做交叉验证。这个模式确实有效——前提是每个角色的边界和职责被清晰定义。