acp-bridge 全解 — 多 Agent 网关的调用、交互与进程生命周期
你在开发机上装了一堆 CLI Agent:Kiro、Claude Code、Codex、Qwen……想让团队其他人和其他机器人也能用,还想让它们互相协作。acp-bridge 做的就是这件事:把本地 CLI Agent 通过 ACP(Agent Client Protocol) 封装成 HTTP 服务,再叠加进程池、pipeline 编排、IM 驱动的异步工作流。本文基于其源码与文档(2026-09 克隆,main 分支)整理,重点回答三个问题:多个 agent 怎么调用、彼此怎么交互、任务过程中 agent 是不是 daemon 常驻。
$ pstree -t | grep -E 'bridge|acp|cli' systemd ── acp-bridge :18010 ← 唯一常驻的服务进程(daemon) ├─ kiro-cli acp (pid 4021) state=idle ← 按需拉起的子进程 ├─ claude-agent-acp (pid 5187) state=busy ← 正在执行某个 session └─ … 空闲超过 TTL / 池满 LRU / 内存吃紧 → 进程组 SIGKILL,用完即走
§00TL;DR
先给三个答案
| 问题 | 答案 |
|---|---|
| 怎么调用多个 agent? | 所有 agent 在 config.yaml 注册(或自动探测 PATH 里的 CLI),统一挂在 :18010 的 HTTP API 后面。每个 agent 就是一个名字:POST /runs 指定 agent_name 即可调用;多 agent 协作走 POST /pipelines,支持串行 / 并行 / 竞速 / 圆桌讨论四种编排模式。 |
| 怎么交互? | 对外是 HTTP(JSON、SSE 流式、webhook 回调);对内 Bridge 与每个 agent 子进程之间是 stdio 上的换行分隔 JSON-RPC 2.0:initialize 握手 → session/new 建会话 → session/prompt 发 prompt,期间 agent 以通知流回传思考 / 工具调用 / 文本片段,还能反向请求 Bridge(权限批准、代理文件读写)。 |
| agent 是 daemon 吗? | 不是。唯一常驻的是 acp-bridge 服务本身(systemd 托管)。每个 agent CLI 是按需 fork 的子进程,键为 (agent, session_id):第一次请求时拉起,空闲超过 TTL(默认 24h)、池满 LRU、内存超 80% 或健康检查失联时被整组 kill。这是一种"温池(warm pool)",不是常驻服务。 |
§01定位
它是什么:一座双向翻译的桥
名字里的 "bridge" 很准确:ACP 协议有两种传输形态——编辑器(如 Zed)与 agent 之间常见的 stdio 形态,和网络服务需要的 HTTP 形态。acp-bridge 站在两者中间:
- 向上(HTTP 侧):用
acp-sdk的 Server + uvicorn 起服务(默认:18010),把每个配置好的 agent 注册为一个 ACP agent——Bridge 自己对外就是一个 ACP agent 提供方,所以 ACP SDK 客户端、OpenClaw 这类支持 ACP 的宿主都能直接接入。 - 向下(stdio 侧):为每个
(agent, session)维护一个 CLI 子进程,用 stdio JSON-RPC 与之对话。
在两个形态之间,它补上了 CLI agent 原生没有的东西:进程池复用、会话保活、多 agent 编排、异步任务、IM 推送、鉴权、沙箱与审计。
全栈里的位置
agent 自身还要调 LLM。默认各 CLI 用自己的模型上游;项目还给出一条完整自托管栈(LiteLLM 聚合 + AWS Bedrock Mantle 网关):
# README 给出的全栈拓扑(LLM 链路) Clients → acp-bridge (:18010) ↓ LLM 调用 litellm (:4000) # 聚合多模型 ↓ mantle-proxy (:4010) # SigV4 签名转发 ↓ bedrock-mantle.{region}.api.aws # GPT / Claude / 开源模型统一入口
典型场景
| 团队 AI 网关 | 一台 EC2 跑 Kiro + Claude + Codex,全员走 HTTP 调用,本地零安装 |
| IM 驱动开发 | Discord / 飞书里发一条消息 → agent 执行 → 结果推送回聊天频道 |
| 异步代码任务 | 提交一个重构 / 评审 job,人走开,完成时 webhook 通知 |
| 多 agent 编排 | 串行接力、并行对比、竞速取优,或让多个 agent 圆桌辩论 |
| Prompt 即服务 | 预置 prompt 模板,非技术同事填变量即用 |
| Agent 市场 | 同一个 harness-factory 二进制 + 不同 profile = 评审员、DevOps 助手、翻译官…… |
§02接入
Agent 怎么接进来
任何 CLI agent 只要满足两者之一就能挂上网关。这决定了"调用多个 agent"的第一层含义:异构 agent 被抹平成同一个 HTTP 接口上的不同名字。
ACP 模式推荐
agent 实现标准 ACP 接口(多数现代 CLI 原生支持或经适配器)。享受进程复用、多轮上下文、结构化事件(思考/工具/状态)、SSE 流式——整座进程池只为此模式服务。
例:kiro-cli 直接 acp --trust-all-tools;Claude Code 经 claude-agent-acp 适配器;Qwen Code 加 --acp。
PTY 模式兼容回退
不支持 ACP 的 agent(如 Trae Agent)配置 mode: "pty"。每次请求新起一个子进程,逐行读 stdout——无复用、无上下文、无结构化事件,是功能完整性让路给兼容性。
对比之下更能看清 ACP 模式的价值:进程复用正是会话上下文保留的关键。
零配置自动探测
没有 config.yaml 也能跑:启动时扫描 PATH,发现已知 CLI 就按默认参数注册(src/auto_detect.py 的 KNOWN_AGENTS),并生成随机鉴权 token:
| 探测到的命令 | 注册名 | 模式 | 启动参数 |
|---|---|---|---|
kiro-cli | kiro | acp | acp --trust-all-tools |
claude-agent-acp | claude | acp | (适配器即 ACP 服务) |
codex-acp | codex | acp | (经 ACP 适配器;也保留 PTY 直跑方式) |
qwen | qwen | acp | --acp |
opencode | opencode | acp | acp |
hermes | hermes | acp | acp |
trae-cli | trae | pty | (无 ACP,逐行读输出) |
harness-factory | harness-* | acp | 按 profile 派生多个 agent |
AWS DevOps Agent 刻意不参与零配置探测——它的身份、AgentSpace 与调查策略必须显式声明,走 fail-closed 专用启动器。
一体化多面:harness-factory
另一个"多 agent"维度:同一个 Rust/Go 二进制 + 不同 profile = 不同角色 agent(reader / executor / reviewer / analyst / developer…),还能在运行时通过 POST /harness 动态创建。轻量 agent 不必装十套 CLI,一套二进制按 profile 拉起即可。
自己写一个新 agent?
接入面被刻意压到最小——实现三个 JSON-RPC 方法即可:initialize(握手)、session/new(建会话)、session/prompt(收 prompt、流式发通知、返回结果)。仓库提供 examples/echo-agent.py 参考实现和 test/test_agent_compliance.sh 合规测试脚本。
§03调用链
一次调用的完整旅程
以最基础的 POST /runs 为例,从 HTTP 请求到 agent 子进程的完整链路如下(源码:main.py → src/agents.py → src/acp_client.py):
几个值得注意的工程细节:
- fork 即立进程组:
asyncio.create_subprocess_exec(..., start_new_session=True),让 Bridge 能用os.killpg一次杀掉 agent 及其全部孙进程(CLI 工具常会再 spawn 语言服务器等子进程)。 - 握手即认证:
initialize响应里若带authMethods,Bridge 自动替你完成authenticate(openai / api-key),agent 无感登录。 - 流式优先:
session/prompt在 Bridge 内是一个异步生成器——边收通知边往外吐,HTTP 层可以同步汇聚,也可以直接转成 SSE 让前端实时看到 agent 的思考与工具调用。 - cwd 沙箱前置:如果请求带了自定义工作目录,先过三级信任沙箱校验(防
cwd=/越权),不合法直接拒绝。
线上电报长什么样
Bridge → agent(每行一条 JSON,stdio)
→ req{"jsonrpc":"2.0","id":1,"method":"initialize", "params":{"protocolVersion":1, "clientCapabilities":{"fs":{"readTextFile":true,"writeTextFile":true}}, "clientInfo":{"name":"acp-bridge","version":"0.46.0"}}} ← res{"id":1,"result":{"agentInfo":{"name":"kiro-cli","version":"…"}, "authMethods":[{"id":"openai"}],"capabilities":{…}}} → req{"id":2,"method":"session/new","params":{"cwd":"/tmp","mcpServers":[]}} → req{"id":3,"method":"session/prompt", "params":{"sessionId":"…","prompt":[{"type":"text","text":"帮我重构这个模块"}]}} ⇠ note{"method":"session/update","params":{"update":{"sessionUpdate":"tool_call", "title":"read_file","kind":"read",…}}} // 思考、工具、文本片段都是通知 ← res{"id":3,"result":{"stopReason":"end_turn"}} // 这一轮结束
§04协议
交互协议细读
线路规则
| 传输 | stdio:stdin 收请求,stdout 发响应与通知,一进程一管道 |
| 格式 | 换行分隔的 JSON-RPC 2.0,每条消息占一行,UTF-8 |
| 缓冲 | 1MB 行缓冲(默认 64KB 不够装 agent 的大响应) |
| stderr | 仅调试日志,Bridge 专门开任务排空,不参与协议 |
正向:通知流
agent 执行期间通过 session/update 通知回传过程,Bridge 按类型分捡(这也是 SSE step_progress 事件的数据源):
| sessionUpdate | 含义 | 对应 SSE kind |
|---|---|---|
agent_message_chunk | 回复正文片段 | message.part |
agent_thought_chunk | 思考片段 | message.thinking |
tool_call | 工具调用开始 | tool.start |
tool_call_update | 工具调用结束(成功 / 失败) | tool.done |
plan | 计划 / 状态摘要 | status |
反向:agent 也能请求 Bridge
JSON-RPC 是双向的,子进程同样可以发带 id 的请求,_read_loop 里有一组自动应答逻辑:
session/request_permission→ 自动回proceed_always。工具审批在无人值守的服务端没法等人点按钮;对 Claude 系 agent 这一步是必须的,否则它一直挂起等待批准。这也是"服务化 CLI agent"与"人坐终端前"最本质的交互差异之一。fs/read_text_file/fs/write_text_file→ Bridge 代理执行。agent 每次文件读写都以请求形式发给 Bridge,Bridge 先过三级信任沙箱(allowed_roots + 黑名单,连接自己的 cwd 是隐式合法根),再落盘并写审计记录;拒绝时把EACCES: sandbox denied当作文件内容回给 agent——让 agent 自己读懂被拒原因。文件 IO 跑在线程池里,大文件不阻塞读循环。
超时与取消
session/prompt带 idle 超时(默认 300s):持续没收到非思考类事件就发session/cancel并报agent_timeout。思考片段故意不重置计时器——防止 agent 陷入无限思考循环拖死会话。- 会话管理三兄弟:
session/new建会话、session/load恢复历史(如 OpenGame--continue)、session/list列出某 cwd 下可恢复的会话。
§05入口
五个入口与外围
"调用 agent"在 acp-bridge 里不是一个端点,而是五种按交互形态划分的入口(官方文档称 5 Interaction Modes):
| 入口 | 形态 | 阻塞 | 上下文 | 多 agent | 适用 |
|---|---|---|---|---|---|
POST /runs | 同步单发(一问一答) | 是 | 无 | 否 | 一次性快速任务 |
/chat 会话 | 进入对话后消息透传,/chat end 退出 | 是 | 有(进程复用保上下文) | 否 | 多轮结对开发 |
POST /jobs | 异步后台,立即返回 job id | 否 | 无 | 否 | 长任务,完成推 webhook / IM |
POST /pipelines | 多 agent 编排(§06) | 看模式 | 看模式 | 是 | 接力 / 并行 / 竞速 / 圆桌 |
/run(sessions_spawn) | OpenClaw 原生 API,runtime="acp" | 否 | 有 | 否 | IM 宿主线程绑定 + 流式 |
外围还有三条接入路径,殊途同归到同一套 handler:
- MCP Server(
mcp-server/):把 Bridge agent 包成 MCP 工具,让 Claude Desktop、Cursor 等 MCP 客户端也能调用。 - IM 机器人:Discord / 飞书 bot 收消息 → 调 HTTP API → 结果格式化推回频道,这是 README 里"让龙虾(OpenClaw)和爱马仕(Hermes)沟通"视频演示的主线。
- Web UI(
--ui):自带聊天界面,人直接在浏览器里用。
§06编排
多 Agent 编排:pipelines 四模式
这是"调用多个 agent"的核心答案。POST /pipelines 提交一个编排声明,Bridge 负责调度、传上下文、汇总产物:
sequence串行接力
上游输出注入下游 prompt典型:代码评审 → 生成测试 → QA 验收,每步吃上一步的结果。
parallel并行扇出
同一 prompt 发多个 agent,汇总典型:同一个问题听三家答案,交叉印证。
race竞速
最快者胜,其余取消典型:延迟敏感的一次性任务,谁先完成用谁的。
conversation圆桌讨论
多 agent 轮流发言,互相接话典型:架构辩论、协作设计,直到说出 DONE 或轮数耗尽。
conversation 是怎么转起来的
圆桌模式的实现(pipeline.py::_run_conversation)最能体现"agent 之间如何交互"——有趣的是,agent 之间从不直接对话,全靠 Bridge 当传话人 + 各自的会话记忆:
- 每个参与者分到独立 session(
conv-{pipeline_id}-{agent}),因此各自的子进程里保留着自己的对话上下文。 - 发言按参与者数组轮询(round-robin)。某个 agent 的第一轮,收到完整铺垫:主题 + 全体参与者介绍 + A2A 协作规则(按主题语言自动选中/英文模板)+ 共享工作目录;之后的每一轮,prompt 只有一行:
[claude]: (claude 刚才说的话)——主题和前文都由它自己的 session 记着,不必重发。(PTY agent 没有记忆,所以每轮都重发全量。) - 终止条件:
stop_conditions(默认有人说出DONE)、max_turns(默认 10)、no_progress_threshold(连续两轮无进展)。 - 共享工作目录
shared_cwd:所有参与者的子进程 cwd 指向同一个目录,讨论产物(代码、文档)直接落在里面,后续步骤接力使用。
跨调用组合:工作区继承 + 自动链
- workspace 继承:上一个 pipeline 返回
shared_cwd,下一个提交时塞进context.shared_cwd,多个 API 调用拼成多阶段工作流——比如先让三个 agent 圆桌讨论游戏设计,再并行让 kiro 写前端、claude 写后端。 next自动链:pipeline 完成后自动触发下一个,steps_from_output: true时从上一段产出的output.tasks[]动态生成步骤,配step_prompt_template模板填充({shared_cwd}、{module}、{files})。- 收敛循环(
next.when,v0.44):next带when条件就从"单次接力"升级成"循环到收敛"——比如 QA agent 打分verdict.overall < 80就loop_back_to修复步重跑,最多max_rounds轮。条件表达式走 AST 白名单求值,绝不用eval:只允许比较、布尔、属性访问和字面量;指标缺失按 False 处理,不会误触发循环。轮次计数通过保留变量{{_loop_round}}注入 prompt。 - 结构化产出:conversation 配
output_schema: true,Bridge 从最后一轮发言里抽取 JSON 块挂到pipeline.context.output,给下一步当机器可读输入。
人在环路(Human-in-the-Loop)
- 暂停 / 恢复:
POST /pipelines/{id}/pause在下一轮前挂起,observe 中间产物后再 resume。 - 插话:
POST /pipelines/{id}/inject把你的消息作为[Human]回合塞进记录,下个 agent 回应它;暂停中插话会自动唤醒。 - 重跑:
rerun支持prompt_override(以「修正指令」前缀注入)和from_step部分重跑,复用原shared_cwd,跑过的步骤产物原地保留。
§07生命周期
进程池与生命周期:agent 到底是不是 daemon?
不是 daemon。整个系统里唯一常驻的进程是 acp-bridge 自身(systemd 单元托管,bridge-ctl.sh / stack-ctl.sh 管理)。所有 agent CLI 都是它按需 fork、有生有灭的子进程。
准确的说法是温池(warm pool):进程的存活期 = 会话需要它的时间 + 一段保活窗口。请求来了才拉起(asyncio.create_subprocess_exec),同一会话的后续轮次复用同一进程以保留上下文;空闲超 TTL、池满被 LRU 挤掉、内存吃紧、心跳失联,任何一条触发就整组 SIGKILL。任务执行"过程中"进程当然活着(否则没法保持上下文),但没有任何机制让它在没人用的时候继续常驻——这和"每个 agent 一个 systemd 服务 / 每个 agent 一个容器常驻"是两种完全不同的资源模型。
拿到一条连接的决策路径
每次请求进来,AcpProcessPool.get_or_create()(带 asyncio.Lock 防并发超卖)按下面的顺序决定"复用还是新建":
(agent, session_id) 已有存活连接 → 直接复用,上下文原样保留pool_exhausted;有名额则 fork 新进程组进程怎么被回收:五条清理线
| 机制 | 周期 / 阈值 | 动作 |
|---|---|---|
| 空闲 TTL | session_ttl_hours 默认 24h(可按 agent 覆盖 idle_ttl),清理循环每 60s 扫一遍 | kill 空闲超时连接,释放名额 |
| 健康检查 ping | 每 60s 对所有 idle 连接发 no-op JSON-RPC(10s 超时) | 无响应 → kill 摘除 |
| busy 卡死保护 | busy 超过 busy_timeout(默认 360s) | 判定卡死,强杀 |
| OOM 保护 | 读 /proc/meminfo,内存用量超 memory_limit_percent(默认 80%) | 主动逐出 idle 连接,抢在 OS OOM killer 之前 |
| ghost 清理 | 启动时 + 清理循环,对照 pidfile /tmp/acp-bridge-pids | SIGKILL 上一次 Bridge 崩溃遗留的孤儿 agent 进程 |
另有三道兜底:Bridge 收到关闭信号时 pool.shutdown() 杀光全部子进程;atexit 注册了 killpg 安全网;中途崩掉的连接在下次请求时被识别为 stale,自动重建新进程(上下文会丢,并明确通知用户)。所有 kill 都是 os.killpg 进程组级别——agent 自己拉起的子进程一棵端走,不留僵尸。
连接的四态
两个特例(依然不是 daemon)
- 心跳保温(可选):配置 heartbeat 后,Bridge 会周期性地用一个环境感知 prompt 唤醒 agent,让它"知道"团队环境的变化。这算主动延长保活窗口,但进程仍在池的统一管辖下,同样受 TTL / LRU / OOM 约束。
- Lambda 突发:
pool: lambda的 agent 把执行甩到 AWS Lambda 上的 harness-factory(应对 100+ 并发),那是 serverless 形态,更谈不上 daemon。
§08追问
能交互吗?能记住吗?——会话与上下文的边界
把 §03 的调用链和 §07 的生命周期合起来,就能正面回答两个最常被追问的问题。
「通过 ACP 能和 agent 交互吗?」——能,全功能且双向
- Bridge 与 agent 子进程之间是完整的 ACP 对话:
initialize→session/new→session/prompt,agent 以通知流实时回传思考片段、工具调用、文本增量。HTTP 侧既能同步拿最终结果,也能走 SSE 实时围观过程。 - 协议是双向的:agent 执行中可以反过来请求 Bridge——工具权限自动放行、文件读写代理 + 沙箱(§04)。
- 前提是 agent 支持 ACP;不支持的走 PTY 兼容模式——能调用,但没有上下文、没有结构化事件(§02)。
「多次对话能保留上下文吗?」——能,但上下文是进程内存级的
能,这正是 ACP 模式相对 PTY 的核心优势(process-pool 文档原话)。机制:进程池以 (agent, session_id) 为键,同一 session 的后续请求命中同一条连接、复用同一个子进程,对话上下文由 agent 进程自己持有——/chat 会话、conversation 圆桌(每人独立 session,首轮之后只传一行接话)都依赖这一点。但要清楚:这个上下文不持久化,四种情况会丢:
| 场景 | 后果 |
|---|---|
| 进程被正常回收 | 空闲超 TTL(默认 24h)、池满 LRU 逐出、内存超 80%、busy 卡死 360s——进程一杀,上下文即失(§07 五条清理线) |
| 进程崩溃 | Bridge 自动重建子进程,但上下文丢失,并明确通知用户 |
| 同 agent 进程被复用 | 池满时 Bridge 拿同 agent 的空闲进程 session/new 重置给新会话(_reuse,省一次 fork),旧会话上下文随之清空 |
| PTY 模式 | 每次请求新起进程,完全没有上下文 |
补救通道:ACP 的 session/load 可按 ID 恢复会话(Bridge 已实现,如 OpenGame 的 --continue),但依赖 agent 侧自己做了会话持久化,并非普遍能力。
实践建议 — 多轮对话在 TTL 窗口内连续进行,上下文稳;隔天或长间隔续聊,要么接受丢上下文重开,要么依赖 agent 自己的持久化恢复。
§09可靠性
熔断、故障转移与断点恢复
进程是短命的,服务却是长期的——可靠性全部内建在池层与编排层,对 agent 零要求:
每个 agent 一个熔断器
- CLOSED → OPEN → HALF_OPEN → CLOSED:滑动窗口内连续 5 次失败或失败率 50% 即熔断,OPEN 期间该 agent 的调用立即拒绝,持续 30s;之后放 3 个探测请求,成功才恢复。
429限流不计入失败——那是客户端侧问题,不是 agent 的错。
自动 fallback:带评分的替补链
- agent 调用失败自动沿 fallback 链换人重试(最多 3 次)。替补不是硬编码顺序,而是打分选出来:熔断 OPEN 的直接排除;有空闲连接的 +1.5× 加成;再看近 1 小时成功率、平均响应时长;近 15 分钟成功率下滑还要吃趋势惩罚。pipeline 的步骤 fallback 会把原定 agent 记为
original_agent并留fallback_history,可追溯。
pipeline 断点恢复(v0.35)
Bridge 重启后,处于 pending / running / paused 的 pipeline 按 mode 差异化恢复:
sequence | 保留已完成步骤,从第一个未完成步续跑 |
parallel | 只重跑未完成的步 |
race | 整体重跑(半截竞速没有有效冠军) |
conversation | 标记失败(对话 session 活在被杀的子进程里,无法续) |
生命周期事件落 SQLite,重启后 SSE 迟连接也能重放历史;每类数据(pipeline / 聊天 / prompt 日志 / 用量)各有保留期,由清理循环统一淘汰,防库无限膨胀。
§10横向扩展
A2A Mesh:多台 Bridge 互为peer
单机进程池之上,还可以把多台 acp-bridge 组成去中心化 mesh——"调用多个 agent"的第三层含义:跨机器调用别人的 agent,像调本地一样。设计哲学一句话:不信任任何远端 agent 的自述,只信可验证的输入→输出链路(校验和、hop 上限、token 认证)。
/.well-known/agent.json Agent Card);gossip 式 announce(默认 300s)互换节点与 peer 列表,无中心注册表。超过 3× 间隔(900s)没露面的 peer 标记 unhealthy,其 agent 从路由表摘除。POST /a2a 调本节点的 agent,独立 mesh token 鉴权,与全局 API token 分离。/runs、/jobs、/pipelines 照常按名字调用,Bridge 自动转发到属主节点。接入 mesh 的任一节点,等于用上了全网所有 agent。shared_cwd 工作区经 S3 打包往返中继,多步流水线照样跨机跑。混合内网 / 公网部署用 dual 模式,peer 按子网远近自动挑地址。§11观测
实时观测:三条通道
| 通道 | 方式 | 适合 |
|---|---|---|
| SSE 事件流 | GET /pipelines/{id}/events,事件:pipeline_started / step_started / step_progress / step_completed / step_failed / step_fallback / pipeline_done + 15s 心跳 | 程序化客户端,实时看 agent 思考与工具调用 |
| 轮询 | GET /pipelines/{id} 全量快照;/steps/{i}/live 正在跑的缓冲 + 工具列表 | Shell 脚本、SSE 被代理缓冲的环境 |
| webhook 推送 | 提交时带 target + channel,每步完成即格式化推送到 IM | Discord / 飞书频道,人只看结论 |
step_progress 就是 §04 那张通知表的实时投影——agent 子进程每发一条 session/update,观察者几乎同时看到。产物(声明了 steps[].artifact 的文件)完成后可自动传 S3,以预签名链接附在通知里。
§12结语
三条设计取舍
回看全文,acp-bridge 的所有机制都围绕三个判断展开:
- 进程边界 = 会话边界 = 信任单位。一个
(agent, session)一个进程组,上下文天然隔离,kill 干净利落;沙箱、审计、熔断、逐出全都以这个粒度运转。温池模型在"保留上下文"和"不养闲进程"之间取了精确的平衡——这正是"agent 不是 daemon"的工程含义。 - 协议面压到最小。接入一个新 agent 只需三个 JSON-RPC 方法;复杂性(进程池、fallback、编排、观测)全部住在 Bridge 侧。agent 保持"傻而纯",平台才能统一演进——v0.18 加池锁、v0.35 加恢复、v0.44 加收敛循环,没有一次要求 agent 侧配合。
- 服务化必须回答"人不在场"的问题。权限请求自动放行、文件读写代理 + 沙箱、空闲超时主动 cancel、失败自动换 agent、崩溃自动恢复——每一个都是"终端里人坐着的默认假设"在服务端不再成立后,补上的显式机制。