最近在设计一个本地 Agent Host 时,我遇到了一个很典型的架构冲突:桌面应用希望用 Tauri 的原生层建立稳定的本地可信边界,但 Codex、Qoder、MCP 等 Agent 能力又高度依赖 Node.js 与 TypeScript 生态。
把所有能力塞进一个进程当然最快,但代价也很直接:UI、数据库、凭据、文件系统和 Agent Runtime 混成一团,一个 Provider SDK 崩溃或越权,就可能拖垮整个应用。
我最后采用的是 Sidecar,也就是边车模式。
一句话总结
Sidecar 是由主应用启动和监管、通过明确协议协作的独立辅助进程。它适合隔离异构运行时和高风险能力,但进程隔离不等于安全沙箱,权限仍然必须由主应用强制执行。
Sidecar 到底是什么
这个名字来自摩托车边车:主车决定方向,边车跟随主车一起运行,但拥有独立空间。
放到软件架构里,主应用负责生命周期、权限和核心状态;Sidecar 负责一组边界清晰的辅助能力。实现上它通常就是子进程、独立容器或本地服务,但“子进程”是操作系统概念,“Sidecar”描述的是架构角色。
| 概念 | 关注点 |
|---|---|
| Child Process | 谁创建进程、PID、stdin/stdout、退出码 |
| Sidecar | 为什么拆分、职责边界、协议、权限和故障治理 |
| Microservice | 独立部署、网络边界、团队和服务自治 |
| Plugin | 可扩展能力,可能仍与宿主运行在同一进程 |
一个进程只有在具备“随主应用交付、由主应用监管、职责受限、通过协议协作”这些特征时,才更接近 Sidecar,而不只是随手启动的脚本。
为什么本地 Agent 特别适合边车模式
本地 Agent 天然跨越多个技术栈:桌面壳需要窗口、更新、Keychain 和系统权限;Agent Runtime 需要 Provider SDK、MCP、工具调用和流式事件;数据层又需要 SQLite、索引与事务一致性。
如果全部交给 Node 主进程,接入生态很舒服,但 Node 同时成了窗口、数据库、凭据和执行权限的可信根。反过来,如果全部重写为原生实现,首版成本高,Provider SDK 更新也要长期追赶。
边车提供了第三条路:原生 Core 保留控制权,Node Agent Host 只承载 Node 生态中不可替代的部分。
| 组件 | 主要职责 | 不应该拥有的能力 |
|---|---|---|
| React Webview | UI、交互、无特权状态 | 原始 Secret、任意 Shell、数据库连接 |
| 原生 Core | 权限、生命周期、更新、审计、IPC 校验 | Provider 业务实现细节 |
| 原生数据服务 | SQLite、同步、索引、事务 | UI 和 Provider Token |
| Node Sidecar | Codex/Qoder SDK、ACP、MCP Adapter | 最终授权决策、主数据库所有权 |
一次 Agent 请求怎样穿过边界
Sidecar 设计真正难的不是“把 Node 启起来”,而是定义每一步谁有权做什么。
典型链路是:
- Webview 提交声明式任务,例如“在当前 workspace 分析这组文件”。
- 原生 Core 校验调用来源、workspace scope、参数长度、组织策略和用户审批。
- Core 给任务生成受限 capability profile,再通过版本化 IPC 发给 Sidecar。
- Sidecar 调用 Provider SDK,并把 token、tool call、progress 和 error 作为有序事件流返回。
- Core 对事件做审计和脱敏,再通过 Tauri Channel 推给 UI。
低频控制请求可以用 NDJSON framed protocol;高频流也可以使用本地 Socket。关键不是选哪一种传输,而是必须有 protocolVersion、requestId、事件序号、取消语义、超时和背压。
不要把协议消息和普通 stdout 日志混在一起。否则一条调试日志就可能破坏 framing,让控制面变得不可预测。
进程隔离不等于安全沙箱
这是 Sidecar 最容易被误解的地方。
把 Node 放进独立进程,只解决了部分故障隔离:Sidecar 崩溃后可以单独拉起,内存泄漏不会直接污染原生 Core。但如果它继承了完整环境变量、用户目录和网络权限,它仍然能够读取不该读取的文件,或者绕过审批直接访问外部服务。
因此我会把原生 Core 设计为 Permission Broker:
- Frontend 不能直接启动 Sidecar,只能提交声明式 Intent。
- Sidecar 只接收完成当前任务所需的最小输入,不接收完整配置和全部 Secret。
- 文件、进程、网络和命令权限按 workspace 与任务 scope 收紧。
- 启动时校验 binary hash、签名、协议版本和 capability profile。
- 所有高风险工具调用经过 Core 审批与审计,Sidecar 不能自己给自己授权。
Tauri capability 主要约束 Webview → 原生 Core,不会自动沙箱已经启动的原生插件或 Node Sidecar。把平台 ACL 当成完整安全方案,是一个危险的边界误判。
生命周期治理比启动命令重要
生产级 Sidecar 至少需要处理下面这些状态:
1 | starting → handshaking → ready → busy → draining → stopped |
主应用需要回答:握手失败怎么办?任务执行中 Sidecar 崩溃是否重试?取消后怎样确保子进程和工具进程都被清理?升级时协议不兼容如何阻止启动?连续崩溃是否进入熔断?
我的判断是:Sidecar 可以独立重启,但主数据库事务和资产 revision 不能由它持有。否则所谓故障隔离只是把崩溃位置挪了一个进程,数据一致性仍被绑在 Agent Runtime 上。
什么时候值得用,什么时候不要用
Sidecar 值得用:
- 必须复用另一种语言或运行时的成熟 SDK。
- 任务容易崩溃、泄漏内存或需要单独限流。
- 能力涉及 Shell、网络、第三方插件等较高风险边界。
- 希望 Agent Runtime 可单独重启,而不影响 UI 和核心数据。
不值得用:
- 只是几个纯函数或轻量业务逻辑。
- 无法定义稳定协议,双方需要共享大量可变内存状态。
- 团队没有能力维护打包、签名、IPC、可观测性和崩溃恢复。
- 拆出去以后仍给 Sidecar 全部文件、网络、凭据和数据库权限。
最后一种尤其常见:付出了多进程复杂度,却没有获得真实边界。
我的判断框架
我不会因为“Sidecar 是一种成熟模式”就默认采用它。我的判断顺序是:
- 是否存在必须保留的异构运行时或高风险能力?
- 能否用小而稳定的协议描述输入、输出和事件?
- 宿主能否真正掌握权限、生命周期和核心数据?
- 故障隔离收益是否大于打包、签名和调试成本?
四个问题都能给出明确答案,Sidecar 才是架构;否则它只是多启动了一个进程。
对本地 Agent 来说,我倾向于采用“可信 Core + 受控 Sidecar”:Core 决定谁能做什么,Sidecar 负责把某一种运行时的能力做到最好。边车可以执行任务,但不能决定规则。








