把CodeBuddy接入多智能体网络
## **一、背景:为什么要把CodeBuddy塞进"运维网络"** 我们维护着一套跑在自有内网的**多节点 AI Agent 协作网络**:一台主控负责调度,若干节点分别承担网关隔离、模型推理、记忆检索、桌面自动化等角色,节点之间靠 SSH、文件通道和定时任务协同。 腾讯公司QClaw已发布停运公告后,我们决定**提前启动迁移准备** —— 把原本由它承担的职能(定时派活、GUI 测试调度、深度研究任务)逐步转移到替代方案上,避免临期被动。 评估下来,腾讯的 **CodeBuddy Code**(CLI,`@tencent-ai/codebuddy-code`)是合适的候选: - 内核是完整的 Agent Runtime(工具调用、多轮、子 Agent、MCP) - 自带 headless 模式、HTTP 网关、Web UI、ACP 协议 - 有桌面版(WorkBuddy),具备窗口/截图/鼠标操作能力 **但"能装"和"能无人值守地被调度"是两回事。** 下面的排查过程,记录了我们从"桌面能用、脚本跑不通"到"完全本地化落地"的完整路径。 --- ## **二、现象:** 第一轮验证就出现了桌面侧和脚本侧的对照: **桌面侧**:在 WorkBuddy 桌面里发消息,Agent 正常执行、正常回复。 **脚本侧**:用同样的网关接口发任务,返回 `202 accepted` 并给出 `runId`,但任务永远停在"进行中": ``` {"data":{"runId":"","status":"accepted"}} # 之后无论等多久,查询状态始终 active:true ``` 日志里最先暴露的是一行不起眼的报错: ``` [Error] Failed to read settings file for scope user: ENOENT ``` 继续追,发现状态机走了一半就停住: ``` RUN_PREPARING → RUN_ACCEPTED → AGENT_STARTED → ...(此后没有任何输出) [WorkflowMemProbe] tick runId=(none) ``` **任务被受理了、Agent 被拉起来了,但模型调用从未发生。** --- ## **三、根因:两套认证体系,一道会话隔离墙** ### **3.1 第一层:凭据根本不在 CLI 手里** 我们把范围缩小到认证。关键日志: ``` [FileAuthenticationStorage] [AtRestEncryption] unavailable adapter=credential-fields resource=auth/.info [CliDispatcher] [CredentialBootstrap] CBC receiver {"route":"standalone","policy":"unspecified",...} [ExternalLinkAuthenticationProvider] fetch auth accounts error: 401 [AuthenticationManager] Failed to refresh token: account list is empty ``` 三条信息拼起来就是答案: 1. **`CredentialBootstrap` 的 `route` 是 `standalone**` —— 说明这个进程**没有拿到宿主注入的凭据**,是"野生"启动的。 1. **`AtRestEncryption unavailable**` —— 凭据存储采用了**静态加密**(在 Windows 平台,这类静态加密通常会走系统的 DPAPI,但这里只是我们的推断),而当前进程**无法解密**。 1. **`fetch auth accounts error: 401**` —— 于是它去问服务端,服务端说你没凭据。 ### **3.2 第二层:为什么"桌面 spawn 的进程"也没用** 这里有个容易踩的坑:我们一度认为"只要用桌面自己拉起的 sidecar 就能借用登录态",于是去接管了桌面的 **prewarm 预热进程池**(见第五节)。结果拿到了一个免密、状态为"已认证"的网关: ``` {"authEnabled":false,"authenticated":true} ``` **但发任务依然卡在 `AGENT_STARTED`。** `authenticated:true` 只表示"这个进程认为自己是登录态"——**我们理解**,真正的凭据注入发生在桌面进程与它 spawn 的子进程之间,一旦从外部(比如 WSL)通过 IPC 接管,这条注入链就断了。(与 §3.3 同理,此为推断,我们未做对照实验。) ### **3.3 第三层:跨系统的会话隔离** 我们还试过从 WSL 直接启动 Windows 侧的进程去跑 CLI,甚至用交互式计划任务在用户会话里启动。**结果一致:`Authentication required`。** 日志里能确定的只有一件事:**静态加密模块在该进程内不可用**(`[AtRestEncryption] unavailable`),随后凭据刷新失败。 我们**推测**,这类静态加密通常依赖当前登录会话持有的用户凭据材料(密钥派生自用户口令),而由 WSL 侧拉起的 Windows 进程往往处在非交互式上下文,因此**可能**拿不到解密所需的那部分材料。但这只是推测——**我们没有做对照实验去证实这一层机制**,就不在本文下结论了。 不过,真正有实操价值的一点是确定的:**绕开这个问题的正确做法,不是去改造登录会话**(换启动方式、调计划任务参数),**而是换一条路——让 CLI 自己完成一次登录。** 这正是下一节的内容。 ### **3.4 关键转折:CLI 有它自己的认证,而且完全独立** 排查到这里,我们做了一次思路切换:**不要试图复用桌面的登录态,而是让 CLI 自己登录。** 验证发现——**CodeBuddy CLI 在 Linux 侧的认证是纯文件存储**(`~/.codebuddy/`),不涉及任何操作系统级加密,自然也没有会话隔离问题。 **这一步是整个问题的解。** --- ## **四、解法:三步走通独立 CLI** ### **4.1 安装(公网可得,无需内部源)** ``` npm i -g @tencent-ai/codebuddy-code --registry=https://registry.npmmirror.com codebuddy --version ``` > 提示 1:包名是 `@tencent-ai/codebuddy-code`。桌面端里内置的那份走的是另一条路径,**不要混淆** —— 前者是公开发行渠道,后者是桌面私有集成。 提示 2:上面用 `--registry` 参数只影响本次安装。若写成 `npm config set registry ...` 会**全局覆盖** npm 源,装完记得改回,或改用前者。 ### **4.2 登录(这一步有个大坑)** CLI 提供 `/login` 交互命令,但我们实测发现: **用 pty 驱动 TUI 注入按键极不稳定** —— 菜单时好时坏,登录 URL 会因终端宽度不够被折行截断(`state=` 参数只取到一半)。折腾了几轮之后我们换了条路: ``` # 仅绑本机回环;仅为打通登录流程,完成登录后请改回启用鉴权(见 §6.3) codebuddy --serve --port 8322 --auth none ``` 然后在**同机浏览器**打开 `http://localhost:8322/`。 > **环境说明**:本文 CLI 装在 Linux 侧的开发机(WSL 环境),浏览器在 Windows 侧。WSL2 默认会把 `localhost` 转发到 Windows,所以直接访问即可,无需额外配置。如果在纯远程服务器上操作,用 SSH 端口转发(`ssh -L 8322:127.0.0.1:8322`)也能达到同样效果 —— **不要**把该端口直接暴露到网络上(见 §6.3 安全边界)。 **Web UI 里有完整的登录入口**,点一下、扫码/SSO 走完,凭据就落到用户目录(`~/.codebuddy/`)了。整个过程不需要和 TUI 搏斗。 > 补充:如果你确实需要走 TUI 拿登录链接,**先把终端宽度设到 200 列以上**(`stty cols 220`),否则 URL 必被截断。 ### **4.3 验证** ``` $ codebuddy -p "reply with exactly: PONG" PONG $ codebuddy -p "run the shell command 'echo HELLO' and tell me its output" -y Output: `HELLO` (exit code 0) ``` **工具调用(Bash)通了** —— 这是 Agent 能力的核心,也是后面一切的基础。 --- ## **五、进阶:prewarm 进程池能接管吗?** CodeBuddy 有一个"预热进程"机制:提前冷启动若干进程待命,需要时唤醒,省掉冷启动时间。该机制由产品官方提供(具体行为请查阅官方文档)。 我们做了一次**边界验证**:外部程序能否激活这些预热进程、并借它拿到可用的 Agent 能力? **结论是"能接管,但不能绕过认证"**: - **能拿到的**:一个仅绑本机回环地址的 HTTP 网关(端口由系统分配)。从进程控制的角度看,预热进程确实可以被外部激活并转为服务形态。 - **拿不到的**:**它不具备可用的登录凭据**。用它提交任务,状态机同样会停在 `AGENT_STARTED`,模型调用永远不会发出(原因见 3.2)。 **所以这条路不能作为认证的替代方案。** 想打通,还是要让 CLI 自己完成一次登录。我们把它记在这里,是为了给同样想"少走一步"的人省下一次尝试 —— 这个方向看起来很像捷径,实际是死路。 > 说明:本文不展开该机制的具体协议与调用细节。若你需要了解,请以官方文档为准。 --- ## **六、落地:把它变成调度网络里的一个执行节点** 认证通了之后,我们把 CLI 封装成常驻网关 + 命令行客户端,接入现有的调度体系。 ### **6.1 两种调用形态** **形态 A:一次性调用**(适合低频、独立任务) ``` codebuddy -p "任务文本" # 只读问答 codebuddy -p "任务文本" -y # 允许工具调用 ``` **形态 B:常驻网关**(适合高频、多任务复用) ``` # 仅绑本机回环;生产环境请按官方文档启用鉴权(见下方安全边界) codebuddy --serve --port 8322 curl -s -H ": " \ http://127.0.0.1:8322/api/v1/health curl -s -X POST http://127.0.0.1:8322/api/v1/runs \ -H ": " -H "Content-Type: application/json" \ -d '{ "id":"", "type":"message", "source":{"platform":"generic","sender":{"id":"scheduler"}, "conversation":{"id":"","type":"direct"}}, "payload":{"text":"任务文本"} }' # → {"data":{"runId":"...","status":"accepted"}} ``` ### **6.2 两个必须知道的接口细节** **坑一:网关要求携带产品自身的请求标识头。** 缺了它,请求直接 403,而且错误信息不会告诉你原因 —— 对着 403 排查半天才发现是缺头。具体头字段名与取值请以官方文档为准。 **坑二:任务正文不在 REST 响应里。** `/api/v1/runs/{id}` 只返回 `active` 状态,流式接口在任务完成后会直接 404。正文要从**会话记录**里取: ``` ls -t ~/.codebuddy/projects/*/*.jsonl | head -1 # 逐行 JSON,过滤 type=message && role=assistant 的 text 字段 ``` 这一条官方文档没写,但对接时不解决它就没法拿到结果。 ### **6.3 网关的能力面** 从运行日志看,这个网关的能力远不止"发任务":它同时还暴露了会话管理、统计、追踪、常驻服务状态、定时任务、IM 通道接入等一组接口。 其中几个值得注意: - **IM 通道**:接口层面声明支持 `generic` / `wecom` / `wechat-kf`(企业微信、微信客服)—— 意味着它**自带 IM 通道接入能力**,可以直接对接到群聊/客服工作流 - **常驻服务**:可注册为系统服务、开机自启,并查询运行状态 - **定时任务**:自带调度能力 - **ACP**:Agent Client Protocol 长连接,适合需要流式交互的场景 > ⚠️ **安全边界(重要)** 这个网关**能执行本机 shell 工具调用**(见 §4.3 的验证),因此它实质上等同于一条**本机命令执行入口**。无论用哪种方式启动,都请守住三条底线: 1. **只绑回环地址**(`127.0.0.1`)。**切勿**改为 `0.0.0.0`,也不要通过端口转发、反向代理把端口服到内网或公网。 1. **生产环境务必启用鉴权**。本文为聚焦认证排查问题,示例中使用了关闭鉴权的参数;那**仅适用于完全隔离的本机调试**。长期运行请按官方文档启用访问控制。 1. **一旦实例挂上了 IM 通道**(企业微信等),访问控制的要求会成倍上升 —— 因为消息入口本身就是外部输入源。此时应叠加网络隔离与反代鉴权,而不是依赖网关自身的单层防护。 --- ## **七、成本归零:接到本地 GPU** 真正让这套方案"值得长期跑"的,是模型侧的调整。 CodeBuddy 支持通过 `~/.codebuddy/models.json` 注入自定义模型(OpenAI 兼容协议): ``` { "models": [{ "id": "local-model", "name": "local-model", "vendor": "Custom", "url": "http://:8000/v1", "supportsToolCall": true, "supportsImages": false, "supportsReasoning": false, "maxInputTokens": 128000, "maxOutputTokens": 16384 }] } ``` 配好之后,**整个 Agent 的推理就跑在内网的 GPU 节点上了**。 ### **实测数据** > 采集环境:本地 GPU 节点(内网 HTTP 服务,OpenAI 兼容接口),客户端与服务端同网段;单并发、无其他负载。数字为观察值,非严格基准测试。 |指标|数值|说明| | --- | --- | --- | |单轮 -p 调用(3 次)|2.63s / 2.69s / 3.84s|含进程冷启动| |网关模式端到端|约 5s 量级|含一次工具调用;因客户端轮询间隔粗(5s),无法给出亚秒级精度,仅供参考| |首 Token 延迟(TTFT)|0.5 – 1.5s|随上下文长度浮动| |单次请求 prompt 规模|≈ 29K tokens|Agent 系统提示的固定开销| |单次请求 completion|17 – 43 tokens|短任务场景| |本地模型端点连通|88ms|内网 HTTP 探测| **两个观察**: 1. **固定开销不小**。CodeBuddy 本体的系统提示约 **29K tokens**,即便只回一句"OK",也要先吃掉这个上下文。本地推理免费,但延迟由这部分决定(TTFT 约 0.5–1.5s)。 1. **调用成本为零**。相比走云端 API 按 token 计费,本地接管后只有电费和 GPU 折旧。对于"高频、短输出"的调度类任务,这是关键的经济性优势。 --- ## **八、经验总结** **五条最值得记的结论**: 1. **桌面能跑 ≠ 脚本能跑**。桌面应用把凭据注入子进程时,注入链绑定在桌面进程上下文里(此为我们的推断,见 §3.2/§3.3);一旦你从外部接管进程,这条链就断了。 1. **OS 级加密可能依赖登录会话上下文**。我们观察到的现象是"能读到加密文件、但解不开",日志只证明静态加密模块不可用。与其在这条路上死磕(改造启动方式、换计划任务参数),不如换条路 —— **让程序自己完成一次认证**,往往更快。 1. **遇到"认证拿不到",先想"能不能让它自己登录"**,而不是死磕"怎么复用别人的登录态"。我们绕了三层才走到这一步,而它是最短路径。 1. **自动化登录优先走 Web UI**。用 pty 驱动 TUI 注入按键,在真实项目里是不稳定源;有 Web UI 就用 Web UI。 1. **官方文档没写的东西,往往才是集成时最耗时的**:REST 不返回任务正文、必需的请求标识头不出现在错误信息里、预热进程接管后拿不到凭据 —— 这些只能靠实测。 **适用场景建议**: |场景|推荐形态| | --- | --- | |脚本化、无人值守的 Agent 调用|独立 CLI(本文方案)| |需要窗口/截图/鼠标等桌面操作|桌面版(独立 CLI 不具备)| |高频短任务|常驻网关 + 本地模型| |低频长任务|一次性 -p 调用即可| --- ## **九、写在最后** 这套方案的实质,是**把一个面向"编码"的 Agent,改造成面向"调度"的通用执行节点**:它不再需要人坐在 IDE 前面,而是作为一个可编程的进程,被上层网络按需唤醒、派活、回收。 对我们来说,这是在原工具停运前完成的职能承接准备;对更广泛的场景来说,它提供了一个思路 —— **当下主流的编码 Agent,其内核能力(工具调用、多轮推理、MCP)已经足够通用,缺的往往只是"如何把它安全地接入你自己的自动化体系"这一层封装。** 而这一层,官方文档通常只讲到一半。 --- *声明:本文所有操作均在自有测试环境完成,不涉及任何未授权访问。文中内网地址、主机名、凭据、进程号与身份信息均已做脱敏处理;涉及的产品接口行为基于特定版本实测,后续版本可能变化,请以官方文档为准。文中所述为个人技术实践,不代表任何组织立场;实际部署请遵守产品许可协议与所在组织的信息安全规定。*